node.module 75.5 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 22
use Drupal\entity\Entity\EntityDisplay;
use Drupal\file\Entity\File;
23
use Drupal\user\UserInterface;
24

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

190
/**
191
 * Implements hook_entity_display_alter().
192
 */
193
function node_entity_display_alter(EntityDisplay $display, $context) {
194
  // Hide field labels in search index.
195 196 197 198 199 200 201
  if ($context['entity_type'] == 'node' && $context['view_mode'] == 'search_index') {
    foreach ($display->getComponents() as $name => $options) {
      if (isset($options['label'])) {
        $options['label'] = 'hidden';
        $display->setComponent($name, $options);
      }
    }
202 203 204
  }
}

205
/**
206
 * Entity URI callback.
207
 *
208
 * @param \Drupal\Core\Entity\EntityInterface $node
209
 *   A node entity.
210 211
 *
 * @return array
212
 *   An array with 'path' as the key and the path to the node as its value.
213
 */
214
function node_uri(EntityInterface $node) {
215
  return array(
216
    'path' => 'node/' . $node->id(),
217
  );
218 219
}

220
/**
221
 * Implements hook_admin_paths().
222 223
 */
function node_admin_paths() {
224
  if (\Drupal::config('node.settings')->get('use_admin_theme')) {
225 226 227 228 229 230
    $paths = array(
      'node/*/edit' => TRUE,
      'node/*/delete' => TRUE,
      'node/*/revisions' => TRUE,
      'node/*/revisions/*/revert' => TRUE,
      'node/*/revisions/*/delete' => TRUE,
231 232
      'node/*/translations' => TRUE,
      'node/*/translations/*' => TRUE,
233 234 235 236 237
      'node/add' => TRUE,
      'node/add/*' => TRUE,
    );
    return $paths;
  }
238 239
}

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

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

Dries's avatar
 
Dries committed
267
/**
268
 * Determines the type of marker to be displayed for a given node.
Dries's avatar
 
Dries committed
269
 *
Dries's avatar
 
Dries committed
270 271 272 273
 * @param $nid
 *   Node ID whose history supplies the "last viewed" timestamp.
 * @param $timestamp
 *   Time which is compared against node's "last viewed" timestamp.
274
 *
275 276
 * @return
 *   One of the MARK constants.
Dries's avatar
 
Dries committed
277
 */
278
function node_mark($nid, $timestamp) {
Dries's avatar
 
Dries committed
279
  global $user;
280
  $cache = &drupal_static(__FUNCTION__, array());
Dries's avatar
 
Dries committed
281

282
  if ($user->isAnonymous() || !module_exists('history')) {
283 284
    return MARK_READ;
  }
Dries's avatar
Dries committed
285
  if (!isset($cache[$nid])) {
286
    $cache[$nid] = history_read($nid);
Dries's avatar
 
Dries committed
287
  }
288
  if ($cache[$nid] == 0 && $timestamp > HISTORY_READ_LIMIT) {
289 290
    return MARK_NEW;
  }
291
  elseif ($timestamp > $cache[$nid] && $timestamp > HISTORY_READ_LIMIT) {
292 293 294
    return MARK_UPDATED;
  }
  return MARK_READ;
Dries's avatar
 
Dries committed
295 296
}

297 298 299
/**
 * Returns a list of all the available node types.
 *
300 301
 * This list can include types that are queued for addition or deletion.
 *
302 303
 * @return array
 *   An array of node type entities, keyed by ID.
304
 *
305
 * @see node_type_load()
306 307
 */
function node_type_get_types() {
308
  return entity_load_multiple('node_type');
309 310 311
}

/**
312 313 314
 * Returns a list of available node type names.
 *
 * This list can include types that are queued for addition or deletion.
315
 *
316
 * @return array
317
 *   An array of node type labels, keyed by the node type name.
318 319
 */
function node_type_get_names() {
320
  $cid = 'node_type:names:' . language(Language::TYPE_INTERFACE)->id;
321 322 323 324 325 326 327 328 329
  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) {
330
    $config = \Drupal::config($config_name);
331 332 333 334 335 336 337
    $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;
338 339 340 341 342
}

/**
 * Returns the node type label for the passed node.
 *
343
 * @param \Drupal\Core\Entity\EntityInterface $node
344 345 346 347
 *   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.
348
 *
349 350
 * @todo Add this as generic helper method for config entities representing
 *   entity bundles.
351
 */
352 353 354
function node_get_type_label(EntityInterface $node) {
  $type = entity_load('node_type', $node->bundle());
  return $type ? $type->label() : FALSE;
355 356 357 358 359
}

/**
 * Description callback: Returns the node type description.
 *
360
 * @param \Drupal\node\NodeTypeInterface $node_type
361 362
 *   The node type object.
 *
363
 * @return string
364 365
 *   The node type description.
 */
366
function node_type_get_description(NodeTypeInterface $node_type) {
367 368 369
  return $node_type->description;
}

370
/**
371
 * Menu argument loader: Loads a node type by string.
372 373
 *
 * @param $name
374
 *   The machine name of a node type to load.
375
 *
376
 * @return \Drupal\node\NodeTypeInterface
377
 *   A node type object or NULL if $name does not exist.
378 379
 */
function node_type_load($name) {
380
  return entity_load('node_type', $name);
381
}
382

383
/**
384
 * Adds the default body field to a node type.
385
 *
386
 * @param \Drupal\node\NodeTypeInterface $type
387
 *   A node type object.
388
 * @param $label
389
 *   (optional) The label for the body instance.
390 391 392
 *
 * @return
 *   Body field instance.
393
 */
394
function node_add_body_field(NodeTypeInterface $type, $label = 'Body') {
395
   // Add or remove the body field, as needed.
396
  $field = field_info_field('node', 'body');
397
  $instance = field_info_instance('node', 'body', $type->id());
398
  if (empty($field)) {
399
    $field = entity_create('field_entity', array(
400 401
      'name' => 'body',
      'entity_type' => 'node',
402
      'type' => 'text_with_summary',
403 404
    ));
    $field->save();
405
  }
406
  if (empty($instance)) {
407
    $instance = entity_create('field_instance', array(
408 409
      'field_name' => 'body',
      'entity_type' => 'node',
410
      'bundle' => $type->id(),
411 412
      'label' => $label,
      'settings' => array('display_summary' => TRUE),
413 414
    ));
    $instance->save();
415

416 417
    // Assign widget settings for the 'default' form mode.
    entity_get_form_display('node', $type->type, 'default')
418
      ->setComponent('body', array(
419 420 421 422
        'type' => 'text_textarea_with_summary',
      ))
      ->save();

423 424
    // Assign display settings for the 'default' and 'teaser' view modes.
    entity_get_display('node', $type->type, 'default')
425
      ->setComponent('body', array(
426 427 428 429 430
        'label' => 'hidden',
        'type' => 'text_default',
      ))
      ->save();
    entity_get_display('node', $type->type, 'teaser')
431
      ->setComponent('body', array(
432 433 434 435
        'label' => 'hidden',
        'type' => 'text_summary_or_trimmed',
      ))
      ->save();
436
  }
437

438
  return $instance;
439
}
440

441 442 443 444 445
/**
 * Implements hook_field_extra_fields().
 */
function node_field_extra_fields() {
  $extra = array();
446 447 448 449 450 451 452 453 454 455 456
  $module_language_enabled = module_exists('language');
  $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,
      );
    }
457

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

  return $extra;
481 482
}

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

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

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

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

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

Dries's avatar
 
Dries committed
566
/**
567 568
 * Page callback: Generates an array which displays a node detail page.
 *
569
 * @param \Drupal\Core\Entity\EntityInterface $node
570
 *   A node entity.
571
 * @param $message
572
 *   (optional) A flag which sets a page title relevant to the revision being
573
 *   viewed.  Default is FALSE.
574
 *
575
 * @return
576
 *   A $page element suitable for use by drupal_render().
577 578
 *
 * @see node_menu()
579 580
 *
 * @deprecated Use \Drupal\node\Controller\NodeController::revisionShow()
Dries's avatar
 
Dries committed
581
 */
582
function node_show(EntityInterface $node, $message = FALSE) {
583
  // For markup consistency with other pages, use node_view_multiple() rather than node_view().
584
  $page = array('nodes' => node_view_multiple(array($node->id() => $node), 'full'));
585

586 587
  if ($message) {
    $page['#title'] = t('Revision of %title from %date', array('%title' => $node->label(), '%date' => format_date($node->getRevisionCreationTime())));
588 589
    // Don't use the render cache when a revision is displayed.
    unset($page['nodes'][$node->id()]['#cache']);
590 591
  }

Dries's avatar
 
Dries committed
592
  // Update the history table, stating that this user viewed this node.
593
  global $user;
594
  if (\Drupal::moduleHandler()->moduleExists('history') && $user->isAuthenticated()) {
595 596 597 598 599 600 601 602 603 604 605 606 607 608
    $page['#attached'] = array(
      'js' => array(
        // When the window's "load" event is triggered, mark the node as read.
        // This still allows for Drupal behaviors (which are triggered on the
        // "DOMContentReady" event) to add "new" and "updated" indicators.
        array(
          'data' => 'window.addEventListener("load",function(){Drupal.history.markAsRead(' . $node->id() . ');},false);',
          'type' => 'inline',
        ),
      ),
      'library' => array(
        array('history', 'drupal.history'),
      )
    );
609
  }
Dries's avatar
 
Dries committed
610

611
  return $page;
Dries's avatar
 
Dries committed
612 613
}

614
/**
615
 * Checks whether the current page is the full page view of the passed-in node.
616
 *
617
 * @param \Drupal\Core\Entity\EntityInterface $node
618
 *   A node entity.
619 620 621
 *
 * @return
 *   The ID of the node if this is a full page view, otherwise FALSE.
622
 */
623
function node_is_page(EntityInterface $node) {
624
  $page_node = menu_get_object();
625
  return (!empty($page_node) ? $page_node->id() == $node->id() : FALSE);
626 627
}

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

644 645 646 647 648 649 650 651 652 653 654 655 656
/**
 * 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;
}

657
/**
658
 * Prepares variables for node templates.
659
 *
660
 * Default template: node.html.twig.
661
 *
662 663 664 665 666
 * 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
667 668 669 670
 *   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'...
671 672
 */
function template_preprocess_node(&$variables) {
673
  $variables['view_mode'] = $variables['elements']['#view_mode'];
674
  // Provide a distinct $teaser boolean.
675
  $variables['teaser'] = $variables['view_mode'] == 'teaser';
676 677 678
  $variables['node'] = $variables['elements']['#node'];
  $node = $variables['node'];

679
  $variables['date'] = format_date($node->getCreatedTime());
680 681
  // @todo Change 'name' to 'author' and also convert to a render array pending
  //   http://drupal.org/node/1941286.
682 683
  $username = array(
    '#theme' => 'username',
684
    '#account' => $node->getAuthor(),
685
    '#link_options' => array('attributes' => array('rel' => 'author')),
686 687
  );
  $variables['name'] = drupal_render($username);
688

689
  $uri = $node->uri();
690
  $variables['node_url']  = url($uri['path'], $uri['options']);
691 692
  $variables['label'] = check_plain($node->label());
  $variables['page'] = $variables['view_mode'] == 'full' && node_is_page($node);
693

694
  // Helpful $content variable for templates.
695
  $variables += array('content' => array());
696 697 698
  foreach (element_children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
699

700
  // Make the field variables available with the appropriate language.
701
  field_attach_preprocess($node, $variables['content'], $variables);
702

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

725
  // Add article ARIA role.
726
  $variables['attributes']['role'] = 'article';
727

728
  // Gather node classes.
729
  $variables['attributes']['class'][] = 'node';
730 731
  $variables['attributes']['class'][] = drupal_html_class('node-' . $node->bundle());
  if ($node->isPromoted()) {
732
    $variables['attributes']['class'][] = 'promoted';
733
  }
734
  if ($node->isSticky()) {
735
    $variables['attributes']['class'][] = 'sticky';
736
  }
737
  if (!$node->isPublished()) {
738
    $variables['attributes']['class'][] = 'unpublished';
739
  }
740
  if ($variables['view_mode']) {
741
    $variables['attributes']['class'][] = drupal_html_class('view-mode-' . $variables['view_mode']);
742 743
  }
  if (isset($variables['preview'])) {
744
    $variables['attributes']['class'][] = 'preview';