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

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

11
use Drupal\Component\Utility\Xss;
12
use Drupal\Core\Language\Language;
13
use Drupal\Core\Render\Element;
14
use Drupal\Core\Url;
15
use Symfony\Component\HttpFoundation\Request;
16
use Symfony\Component\HttpFoundation\Response;
17
use Drupal\Core\Cache\Cache;
18 19
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Database\Query\SelectInterface;
20 21
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldInstanceConfig;
22 23
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
24
use Drupal\node\NodeTypeInterface;
25
use Drupal\node\NodeInterface;
26
use Drupal\Core\Entity\EntityInterface;
27 28
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
29
use Drupal\Core\Template\Attribute;
30
use Drupal\file\Entity\File;
31
use Drupal\language\Entity\Language as LanguageEntity;
32
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
33 34
use Drupal\block\Entity\Block;
use Drupal\Core\Session\AccountInterface;
35

36
/**
37
 * Denotes that the node is not published.
38
 */
39
const NODE_NOT_PUBLISHED = 0;
40 41

/**
42
 * Denotes that the node is published.
43
 */
44
const NODE_PUBLISHED = 1;
45 46

/**
47
 * Denotes that the node is not promoted to the front page.
48
 */
49
const NODE_NOT_PROMOTED = 0;
50 51

/**
52
 * Denotes that the node is promoted to the front page.
53
 */
54
const NODE_PROMOTED = 1;
55 56

/**
57
 * Denotes that the node is not sticky at the top of the page.
58
 */
59
const NODE_NOT_STICKY = 0;
60 61

/**
62
 * Denotes that the node is sticky at the top of the page.
63
 */
64
const NODE_STICKY = 1;
65

66
/**
67 68 69 70
 * Denotes that access is allowed for a node.
 *
 * Modules should return this value from hook_node_access() to allow access to a
 * node.
71
 */
72
const NODE_ACCESS_ALLOW = TRUE;
73 74

/**
75 76 77 78
 * Denotes that access is denied for a node.
 *
 * Modules should return this value from hook_node_access() to deny access to a
 * node.
79
 */
80
const NODE_ACCESS_DENY = FALSE;
81 82

/**
83 84 85 86
 * Denotes that access is unaffected for a node.
 *
 * Modules should return this value from hook_node_access() to indicate no
 * effect on node access.
87
 */
88
const NODE_ACCESS_IGNORE = NULL;
89

Dries's avatar
Dries committed
90
/**
91
 * Implements hook_help().
Dries's avatar
Dries committed
92
 */
93
function node_help($route_name, Request $request) {
94 95 96
  // 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.
97 98 99
  if ($route_name != 'node.configure_rebuild_confirm' && $route_name != 'system.batch_page.normal' && $route_name != 'help.page.node' && $route_name != 'help.main'
    && user_access('access administration pages') && node_access_needs_rebuild()) {
    if ($route_name == 'system.status') {
100 101 102
      $message = t('The content access permissions need to be rebuilt.');
    }
    else {
103
      $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')));
104 105 106 107
    }
    drupal_set_message($message, 'error');
  }

108 109
  switch ($route_name) {
    case 'help.page.node':
110 111
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
112
      $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 <a href="!node">the online documentation for the Node module</a>.', array('!node' => 'https://drupal.org/documentation/modules/node', '!field' => \Drupal::url('help.page', array('name' => 'field')))) . '</p>';
113 114
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
115
      $output .= '<dt>' . t('Creating content') . '</dt>';
116
      $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('node.overview_types'))) . '</dd>';
117
      $output .= '<dt>' . t('Creating custom content types') . '</dt>';
118
      $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' => \Drupal::url('node.type_add'), '!field' => \Drupal::url('help.page', array('name' => 'field')))) . '</dd>';
119
      $output .= '<dt>' . t('Administering content') . '</dt>';
120
      $output .= '<dd>' . t('The <a href="!content">Content administration page</a> allows you to review and bulk manage your site content.', array('!content' => \Drupal::url('node.content_overview'))) . '</dd>';
121 122
      $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>';
123
      $output .= '<dt>' . t('User permissions') . '</dt>';
124
      $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>';
125
      $output .= '</dl>';
126
      return $output;
127

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

131 132 133
    case 'field_ui.form_display_overview_node':
    case 'field_ui.form_display_overview_form_mode_node':
      $type = $request->attributes->get('node_type');
134
      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>' ;
135

136 137 138
    case 'field_ui.display_overview_node':
    case 'field_ui.display_overview_view_mode_node':
      $type = $request->attributes->get('node_type');
139
      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>' .
140
        '<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>';
141

142
    case 'node.revision_overview':
143
      return '<p>' . t('Revisions allow you to track differences between multiple versions of your content, and revert back to older versions.') . '</p>';
144

145 146 147
    case 'node.page_edit':
      $node = $request->attributes->get('node');
      $type = $node->getType();
148
      return (!empty($type->help) ? Xss::filterAdmin($type->help) : '');
Dries's avatar
Dries committed
149

150 151 152
    case 'node.add':
      $type = $request->attributes->get('node_type');
      return (!empty($type->help) ? Xss::filterAdmin($type->help) : '');
153
  }
Dries's avatar
Dries committed
154 155
}

156
/**
157
 * Implements hook_theme().
158 159 160
 */
function node_theme() {
  return array(
161
    'node' => array(
162
      'render element' => 'elements',
163
      'template' => 'node',
164
    ),
165
    'node_search_admin' => array(
166
      'render element' => 'form',
167
    ),
168
    'node_add_list' => array(
169
      'variables' => array('content' => NULL),
170
      'file' => 'node.pages.inc',
171
      'template' => 'node-add-list',
172 173
    ),
    'node_preview' => array(
174
      'variables' => array('node' => NULL),
175
      'file' => 'node.pages.inc',
176
      'template' => 'node-preview',
177
    ),
178 179 180 181
    'node_edit_form' => array(
      'render element' => 'form',
      'template' => 'node-edit-form',
    ),
182 183
    'field__node__title' => array(
      'base hook' => 'field',
184
      'template' => 'field--node--title',
185
    ),
186 187 188
  );
}

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

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

220
/**
221
 * Entity URI callback.
222
 *
223
 * @param \Drupal\node\NodeInterface $node
224
 *   A node entity.
225
 *
226 227
 * @return \Drupal\core\Url
 *   A Url object that provides a URL to the view page for $node.
228
 */
229
function node_uri(NodeInterface $node) {
230 231 232
  return new Url('node.view', array(
    'node' => $node->id(),
  ));
233 234
}

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

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

Dries's avatar
Dries committed
262
/**
263
 * Determines the type of marker to be displayed for a given node.
Dries's avatar
Dries committed
264
 *
265
 * @param int $nid
Dries's avatar
Dries committed
266
 *   Node ID whose history supplies the "last viewed" timestamp.
267
 * @param int $timestamp
Dries's avatar
Dries committed
268
 *   Time which is compared against node's "last viewed" timestamp.
269
 *
270
 * @return int
271
 *   One of the MARK constants.
Dries's avatar
Dries committed
272
 */
273
function node_mark($nid, $timestamp) {
274

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

277
  if (\Drupal::currentUser()->isAnonymous() || !\Drupal::moduleHandler()->moduleExists('history')) {
278 279
    return MARK_READ;
  }
Dries's avatar
Dries committed
280
  if (!isset($cache[$nid])) {
281
    $cache[$nid] = history_read($nid);
Dries's avatar
Dries committed
282
  }
283
  if ($cache[$nid] == 0 && $timestamp > HISTORY_READ_LIMIT) {
284 285
    return MARK_NEW;
  }
286
  elseif ($timestamp > $cache[$nid] && $timestamp > HISTORY_READ_LIMIT) {
287 288 289
    return MARK_UPDATED;
  }
  return MARK_READ;
Dries's avatar
Dries committed
290 291
}

292 293 294
/**
 * Returns a list of all the available node types.
 *
295 296
 * This list can include types that are queued for addition or deletion.
 *
297
 * @return \Drupal\node\NodeTypeInterface[]
298
 *   An array of node type entities, keyed by ID.
299
 *
300 301 302
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\node\Entity\NodeType::loadMultiple().
 *
303
 * @see node_type_load()
304 305
 */
function node_type_get_types() {
306
  return NodeType::loadMultiple();
307 308 309
}

/**
310 311 312
 * Returns a list of available node type names.
 *
 * This list can include types that are queued for addition or deletion.
313
 *
314
 * @return string[]
315
 *   An array of node type labels, keyed by the node type name.
316 317
 */
function node_type_get_names() {
318 319 320
  return array_map(function ($bundle_info) {
    return $bundle_info['label'];
  }, \Drupal::entityManager()->getBundleInfo('node'));
321 322 323 324 325
}

/**
 * Returns the node type label for the passed node.
 *
326
 * @param \Drupal\node\NodeInterface $node
327 328 329 330
 *   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.
331
 *
332 333
 * @todo Add this as generic helper method for config entities representing
 *   entity bundles.
334
 */
335
function node_get_type_label(NodeInterface $node) {
336 337
  $type = entity_load('node_type', $node->bundle());
  return $type ? $type->label() : FALSE;
338 339 340 341 342
}

/**
 * Description callback: Returns the node type description.
 *
343
 * @param \Drupal\node\NodeTypeInterface $node_type
344 345
 *   The node type object.
 *
346
 * @return string
347 348
 *   The node type description.
 */
349
function node_type_get_description(NodeTypeInterface $node_type) {
350 351 352
  return $node_type->description;
}

353
/**
354
 * Menu argument loader: Loads a node type by string.
355 356
 *
 * @param $name
357
 *   The machine name of a node type to load.
358
 *
359
 * @return \Drupal\node\NodeTypeInterface
360
 *   A node type object or NULL if $name does not exist.
361 362 363
 *
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\node\Entity\NodeType::load().
364 365
 */
function node_type_load($name) {
366
  return NodeType::load($name);
367
}
368

369
/**
370
 * Adds the default body field to a node type.
371
 *
372
 * @param \Drupal\node\NodeTypeInterface $type
373
 *   A node type object.
374
 * @param $label
375
 *   (optional) The label for the body instance.
376 377 378
 *
 * @return
 *   Body field instance.
379
 */
380
function node_add_body_field(NodeTypeInterface $type, $label = 'Body') {
381
   // Add or remove the body field, as needed.
382 383
  $field = FieldConfig::loadByName('node', 'body');
  $instance = FieldInstanceConfig::loadByName('node', $type->id(), 'body');
384
  if (empty($field)) {
385
    $field = entity_create('field_config', array(
386 387
      'name' => 'body',
      'entity_type' => 'node',
388
      'type' => 'text_with_summary',
389 390
    ));
    $field->save();
391
  }
392
  if (empty($instance)) {
393
    $instance = entity_create('field_instance_config', array(
394 395
      'field_name' => 'body',
      'entity_type' => 'node',
396
      'bundle' => $type->id(),
397 398
      'label' => $label,
      'settings' => array('display_summary' => TRUE),
399 400
    ));
    $instance->save();
401

402 403
    // Assign widget settings for the 'default' form mode.
    entity_get_form_display('node', $type->type, 'default')
404
      ->setComponent('body', array(
405 406 407 408
        'type' => 'text_textarea_with_summary',
      ))
      ->save();

409 410
    // Assign display settings for the 'default' and 'teaser' view modes.
    entity_get_display('node', $type->type, 'default')
411
      ->setComponent('body', array(
412 413 414 415
        'label' => 'hidden',
        'type' => 'text_default',
      ))
      ->save();
416 417 418

    // The teaser view mode is created by the Standard profile and therefore
    // might not exist.
419
    $view_modes = \Drupal::entityManager()->getViewModes('node');
420 421 422 423 424 425 426 427
    if (isset($view_modes['teaser'])) {
      entity_get_display('node', $type->type, 'teaser')
        ->setComponent('body', array(
          'label' => 'hidden',
          'type' => 'text_summary_or_trimmed',
        ))
        ->save();
    }
428
  }
429

430
  return $instance;
431
}
432

433
/**
434
 * Implements hook_entity_extra_field_info().
435
 */
436
function node_entity_extra_field_info() {
437
  $extra = array();
438
  $module_language_enabled = \Drupal::moduleHandler()->moduleExists('language');
439 440 441
  $description = t('Node module element');

  foreach (node_type_get_types() as $bundle) {
442 443
    // Add the 'language' select if Language module is enabled and the bundle
    // has multilingual support.
444
    // Visibility of the ordering of the language selector is the same as on the
445 446 447
    // node/add form.
    if ($module_language_enabled) {
      $configuration = language_get_default_configuration('node', $bundle->type);
448
      if ($configuration['language_show']) {
449
        $extra['node'][$bundle->type]['form']['langcode'] = array(
450 451 452 453 454
          'label' => t('Language'),
          'description' => $description,
          'weight' => 0,
        );
      }
455
    }
456
    $extra['node'][$bundle->type]['display']['langcode'] = array(
457 458 459 460 461
      'label' => t('Language'),
      'description' => $description,
      'weight' => 0,
      'visible' => FALSE,
    );
462
  }
463 464

  return $extra;
465 466
}

467
/**
468 469
 * Updates all nodes of one type to be of another type.
 *
470
 * @param string $old_id
471
 *   The current node type of the nodes.
472
 * @param string $new_id
473
 *   The new node type of the nodes.
474 475
 *
 * @return
476
 *   The number of nodes whose node type field was modified.
477
 */
478
function node_type_update_nodes($old_id, $new_id) {
479
  return \Drupal::entityManager()->getStorage('node')->updateType($old_id, $new_id);
Dries's avatar
Dries committed
480
}
Dries's avatar
Dries committed
481

482
/**
483
 * Loads node entities from the database.
484 485
 *
 * This function should be used whenever you need to load more than one node
486 487
 * 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
488
 *
489 490
 * @param array $nids
 *   (optional) An array of entity IDs. If omitted, all entities are loaded.
491
 * @param bool $reset
492 493
 *   (optional) Whether to reset the internal node_load() cache.  Defaults to
 *   FALSE.
Dries's avatar
Dries committed
494
 *
495
 * @return \Drupal\node\NodeInterface[]
496
 *   An array of node entities indexed by nid.
497
 *
498 499 500
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\node\Entity\Node::loadMultiple().
 *
501
 * @see entity_load_multiple()
502
 * @see \Drupal\Core\Entity\Query\EntityQueryInterface
Dries's avatar
Dries committed
503
 */
504
function node_load_multiple(array $nids = NULL, $reset = FALSE) {
505 506 507 508
  if ($reset) {
    \Drupal::entityManager()->getStorage('node')->resetCache($nids);
  }
  return Node::loadMultiple($nids);
509 510 511
}

/**
512
 * Loads a node entity from the database.
513
 *
514
 * @param int $nid
515
 *   The node ID.
516
 * @param bool $reset
517 518
 *   (optional) Whether to reset the node_load_multiple() cache. Defaults to
 *   FALSE.
519
 *
520 521
 * @return \Drupal\node\NodeInterface|null
 *   A fully-populated node entity, or NULL if the node is not found.
522 523 524
 *
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\node\Entity\Node::load().
525
 */
526
function node_load($nid = NULL, $reset = FALSE) {
527 528 529 530
  if ($reset) {
    \Drupal::entityManager()->getStorage('node')->resetCache(array($nid));
  }
  return Node::load($nid);
531 532 533 534 535
}

/**
 * Loads a node revision from the database.
 *
536
 * @param int $vid
537 538
 *   The node revision id.
 *
539 540
 * @return \Drupal\node\NodeInterface|null
 *   A fully-populated node entity, or NULL if the node is not found.
541 542 543
 */
function node_revision_load($vid = NULL) {
  return entity_revision_load('node', $vid);
Dries's avatar
Dries committed
544 545
}

546
/**
547
 * Deletes a node revision.
548 549 550
 *
 * @param $revision_id
 *   The revision ID to delete.
551 552
 *
 * @return
553
 *   TRUE if the revision deletion was successful; otherwise, FALSE.
554 555
 */
function node_revision_delete($revision_id) {
556
  entity_revision_delete('node', $revision_id);
557 558
}

559
/**
560
 * Checks whether the current page is the full page view of the passed-in node.
561
 *
562
 * @param \Drupal\node\NodeInterface $node
563
 *   A node entity.
564 565 566
 *
 * @return
 *   The ID of the node if this is a full page view, otherwise FALSE.
567
 */
568
function node_is_page(NodeInterface $node) {
569 570 571 572
  $request = \Drupal::request();
  if ($request->attributes->get(RouteObjectInterface::ROUTE_NAME) == 'node.view') {
    $page_node = $request->attributes->get('node');
  }
573
  return (!empty($page_node) ? $page_node->id() == $node->id() : FALSE);
574 575
}

576 577 578 579 580
/**
 * 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.
581
  if (($node = \Drupal::request()->attributes->get('node')) && $node instanceof NodeInterface) {
582 583 584 585
    $variables['attributes']['class'][] = drupal_html_class('node-type-' . $node->getType());
  }
}

586
/**
587
 * Implements hook_preprocess_HOOK() for block templates.
588 589
 */
function node_preprocess_block(&$variables) {
590
  if ($variables['configuration']['provider'] == 'node') {
591
    switch ($variables['elements']['#plugin_id']) {
592
      case 'node_syndicate_block':
593
        $variables['attributes']['role'] = 'complementary';
594 595 596 597 598
        break;
    }
  }
}

599 600 601 602 603 604
/**
 * Implements hook_theme_suggestions_HOOK().
 */
function node_theme_suggestions_node(array $variables) {
  $suggestions = array();
  $node = $variables['elements']['#node'];
605
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
606

607
  $suggestions[] = 'node__' . $sanitized_view_mode;
608
  $suggestions[] = 'node__' . $node->bundle();
609
  $suggestions[] = 'node__' . $node->bundle() . '__' . $sanitized_view_mode;
610
  $suggestions[] = 'node__' . $node->id();
611
  $suggestions[] = 'node__' . $node->id() . '__' . $sanitized_view_mode;
612 613 614 615

  return $suggestions;
}

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

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

647 648 649
  $variables['node_url'] = $node->url('canonical', array(
    'language' => $node->language(),
  ));
650 651
  $variables['label'] = $variables['elements']['title'];
  unset($variables['elements']['title']);
652
  $variables['page'] = $variables['view_mode'] == 'full' && node_is_page($node);
653

654
  // Helpful $content variable for templates.
655
  $variables += array('content' => array());
656
  foreach (Element::children($variables['elements']) as $key) {
657 658
    $variables['content'][$key] = $variables['elements'][$key];
  }
659 660

  // Display post information only on certain node types.
661 662 663 664 665
  // 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']) {
666
    $variables['submitted'] = t('Submitted by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
667
    if (theme_get_setting('features.node_user_picture')) {
668
      // To change user picture settings (e.g. image style), edit the 'compact'
669 670
      // 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.
671
      $variables['user_picture'] = user_view($node->getOwner(), 'compact');
672 673 674 675
    }
    else {
      $variables['user_picture'] = array();
    }
676 677
  }
  else {
678
    $variables['submitted'] = '';
679
    $variables['user_picture'] = '';
680
  }
681

682
  // Add article ARIA role.
683
  $variables['attributes']['role'] = 'article';
684

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

Dries's avatar
Dries committed
706
/**
707
 * Implements hook_permission().
Dries's avatar
Dries committed
708
 */
709
function node_permission() {
710
  $perms = array(
711 712 713 714 715
    'bypass node access' => array(
      'title' => t('Bypass content access control'),
      'description' => t('View, edit and delete all content regardless of permission restrictions.'),
      'restrict access' => TRUE,
    ),
716 717
    'administer content types' => array(
      'title' => t('Administer content types'),
718
      'restrict access' => TRUE,
719 720
    ),
    'administer nodes' => array(
721
      'title' => t('Administer content'),
722
      'restrict access' => TRUE,
723
    ),
724
    'access content overview' => array(
725 726 727 728
      'title' => t('Access the Content overview page'),
      'description' => user_access('access content overview')
        ? t('Get an overview of <a href="@url">all content</a>.', array('@url' => url('admin/content')))
        : t('Get an overview of all content.'),
729
    ),
730 731 732 733 734
    'access content' => array(
      'title' => t('View published content'),
    ),
    'view own unpublished content' => array(
      'title' => t('View own unpublished content'),
735
    ),
736 737
    'view all revisions' => array(
      'title' => t('View all revisions'),
738
    ),
739 740 741
    'revert all revisions' => array(
      'title' => t('Revert all revisions'),
      'description' => t('Role requires permission <em>view revisions</em> and <em>edit rights</em> for nodes in question, or <em>administer nodes</em>.'),
742
    ),
743 744 745
    'delete all revisions' => array(
      'title' => t('Delete all revisions'),
      'description' => t('Role requires permission to <em>view revisions</em> and <em>delete rights</em> for nodes in question, or <em>administer nodes</em>.'),
746
    ),
747
  );
748

749
  // Generate standard node permissions for all applicable node types.
750
  foreach (node_permissions_get_configured_types() as $type) {
751
    $perms += node_list_permissions($type);
752 753 754
  }

  return $perms;
Dries's avatar
Dries committed
755 756
}

757
/**
758
 * Implements hook_ranking().
759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780
 */
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',
    ),
  );

  // Add relevance based on creation or changed date.
781
  if ($node_cron_last = \Drupal::state()->get('node.cron_last')) {
782 783 784
    $ranking['recent'] = array(
      'title' => t('Recently posted'),
      // Exponential decay with half-life of 6 months, starting at last indexed node
785 786
      'score' => 'POW(2.0, (GREATEST(n.created, n.changed) - :node_cron_last) * 6.43e-8)',
      'arguments' => array(':node_cron_last' => $node_cron_last),
787 788 789 790 791
    );
  }
  return $ranking;
}

792
/**
793
 * Implements hook_user_cancel().
794
 */
795
function node_user_cancel($edit, $account, $method) {
796 797 798
  switch ($method) {
    case 'user_cancel_block_unpublish':
      // Unpublish nodes (current revisions).
799
      $nids = \Drupal::entityQuery('node')
800
        ->condition('uid', $account->id())
801 802 803
        ->execute();
      module_load_include('inc', 'node', 'node.admin');
      node_mass_update($nids, array('status' => 0), NULL, TRUE);
804 805 806
      break;

    case 'user_cancel_reassign':
807
      // Anonymize all of the nodes for this old account.