node.module 58.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\Xss;
12
use Drupal\Core\Language\LanguageInterface;
13
use Drupal\Core\Render\Element;
14
use Drupal\Core\Routing\RouteMatchInterface;
15
use Drupal\Core\Url;
16
17
18
use Symfony\Component\HttpFoundation\Response;
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Database\Query\SelectInterface;
19
20
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldInstanceConfig;
21
22
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
23
use Drupal\node\NodeTypeInterface;
24
use Drupal\node\NodeInterface;
25
use Drupal\Core\Entity\EntityInterface;
26
27
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
28
use Drupal\Core\Template\Attribute;
29
use Drupal\file\Entity\File;
30
use Drupal\language\Entity\Language as LanguageEntity;
31
32
use Drupal\block\Entity\Block;
use Drupal\Core\Session\AccountInterface;
33

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

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

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

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

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

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

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

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

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

Dries's avatar
   
Dries committed
88
/**
89
 * Implements hook_help().
Dries's avatar
   
Dries committed
90
 */
91
function node_help($route_name, RouteMatchInterface $route_match) {
92
93
94
  // 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.
95
96
97
  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') {
98
99
100
      $message = t('The content access permissions need to be rebuilt.');
    }
    else {
101
      $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')));
102
103
104
105
    }
    drupal_set_message($message, 'error');
  }

106
107
  switch ($route_name) {
    case 'help.page.node':
108
109
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
110
      $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>';
111
112
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
113
      $output .= '<dt>' . t('Creating content') . '</dt>';
114
      $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>';
115
      $output .= '<dt>' . t('Creating custom content types') . '</dt>';
116
      $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>';
117
      $output .= '<dt>' . t('Administering content') . '</dt>';
118
      $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('system.admin_content'))) . '</dd>';
119
120
      $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>';
121
      $output .= '<dt>' . t('User permissions') . '</dt>';
122
      $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>';
123
      $output .= '</dl>';
124
      return $output;
125

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

129
130
    case 'field_ui.form_display_overview_node':
    case 'field_ui.form_display_overview_form_mode_node':
131
      $type = $route_match->getParameter('node_type');
132
      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>' ;
133

134
135
    case 'field_ui.display_overview_node':
    case 'field_ui.display_overview_view_mode_node':
136
      $type = $route_match->getParameter('node_type');
137
      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>' .
138
        '<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>';
139

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

143
    case 'node.page_edit':
144
      $node = $route_match->getParameter('node');
145
      $type = $node->getType();
146
      return (!empty($type->help) ? Xss::filterAdmin($type->help) : '');
Dries's avatar
   
Dries committed
147

148
    case 'node.add':
149
      $type = $route_match->getParameter('node_type');
150
      return (!empty($type->help) ? Xss::filterAdmin($type->help) : '');
151
  }
Dries's avatar
   
Dries committed
152
153
}

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

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

/**
 * Implements hook_entity_form_display_alter().
 */
207
function node_entity_form_display_alter(EntityFormDisplayInterface $form_display, $context) {
208
209
  if ($context['entity_type'] == 'node') {
    $node_type = node_type_load($context['bundle']);
210
211
212
  }
}

213
/**
214
 * Entity URI callback.
215
 *
216
 * @param \Drupal\node\NodeInterface $node
217
 *   A node entity.
218
 *
219
220
 * @return \Drupal\core\Url
 *   A Url object that provides a URL to the view page for $node.
221
 */
222
function node_uri(NodeInterface $node) {
223
224
225
  return new Url('node.view', array(
    'node' => $node->id(),
  ));
226
227
}

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

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

Dries's avatar
   
Dries committed
255
/**
256
 * Determines the type of marker to be displayed for a given node.
Dries's avatar
   
Dries committed
257
 *
258
 * @param int $nid
Dries's avatar
   
Dries committed
259
 *   Node ID whose history supplies the "last viewed" timestamp.
260
 * @param int $timestamp
Dries's avatar
   
Dries committed
261
 *   Time which is compared against node's "last viewed" timestamp.
262
 *
263
 * @return int
264
 *   One of the MARK constants.
Dries's avatar
   
Dries committed
265
 */
266
function node_mark($nid, $timestamp) {
267

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

270
  if (\Drupal::currentUser()->isAnonymous() || !\Drupal::moduleHandler()->moduleExists('history')) {
271
272
    return MARK_READ;
  }
Dries's avatar
Dries committed
273
  if (!isset($cache[$nid])) {
274
    $cache[$nid] = history_read($nid);
Dries's avatar
   
Dries committed
275
  }
276
  if ($cache[$nid] == 0 && $timestamp > HISTORY_READ_LIMIT) {
277
278
    return MARK_NEW;
  }
279
  elseif ($timestamp > $cache[$nid] && $timestamp > HISTORY_READ_LIMIT) {
280
281
282
    return MARK_UPDATED;
  }
  return MARK_READ;
Dries's avatar
   
Dries committed
283
284
}

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

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

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

/**
 * Description callback: Returns the node type description.
 *
336
 * @param \Drupal\node\NodeTypeInterface $node_type
337
338
 *   The node type object.
 *
339
 * @return string
340
341
 *   The node type description.
 */
342
function node_type_get_description(NodeTypeInterface $node_type) {
343
344
345
  return $node_type->description;
}

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

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

394
395
    // Assign widget settings for the 'default' form mode.
    entity_get_form_display('node', $type->type, 'default')
396
      ->setComponent('body', array(
397
398
399
400
        'type' => 'text_textarea_with_summary',
      ))
      ->save();

401
402
    // Assign display settings for the 'default' and 'teaser' view modes.
    entity_get_display('node', $type->type, 'default')
403
      ->setComponent('body', array(
404
405
406
407
        'label' => 'hidden',
        'type' => 'text_default',
      ))
      ->save();
408
409
410

    // The teaser view mode is created by the Standard profile and therefore
    // might not exist.
411
    $view_modes = \Drupal::entityManager()->getViewModes('node');
412
413
414
415
416
417
418
419
    if (isset($view_modes['teaser'])) {
      entity_get_display('node', $type->type, 'teaser')
        ->setComponent('body', array(
          'label' => 'hidden',
          'type' => 'text_summary_or_trimmed',
        ))
        ->save();
    }
420
  }
421

422
  return $instance;
423
}
424

425
/**
426
 * Implements hook_entity_extra_field_info().
427
 */
428
function node_entity_extra_field_info() {
429
  $extra = array();
430
  $module_language_enabled = \Drupal::moduleHandler()->moduleExists('language');
431
432
433
  $description = t('Node module element');

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

  return $extra;
457
458
}

459
/**
460
461
 * Updates all nodes of one type to be of another type.
 *
462
 * @param string $old_id
463
 *   The current node type of the nodes.
464
 * @param string $new_id
465
 *   The new node type of the nodes.
466
467
 *
 * @return
468
 *   The number of nodes whose node type field was modified.
469
 */
470
function node_type_update_nodes($old_id, $new_id) {
471
  return \Drupal::entityManager()->getStorage('node')->updateType($old_id, $new_id);
Dries's avatar
   
Dries committed
472
}
Dries's avatar
   
Dries committed
473

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

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

/**
 * Loads a node revision from the database.
 *
528
 * @param int $vid
529
530
 *   The node revision id.
 *
531
532
 * @return \Drupal\node\NodeInterface|null
 *   A fully-populated node entity, or NULL if the node is not found.
533
534
535
 */
function node_revision_load($vid = NULL) {
  return entity_revision_load('node', $vid);
Dries's avatar
   
Dries committed
536
537
}

538
/**
539
 * Deletes a node revision.
540
541
542
 *
 * @param $revision_id
 *   The revision ID to delete.
543
544
 *
 * @return
545
 *   TRUE if the revision deletion was successful; otherwise, FALSE.
546
547
 */
function node_revision_delete($revision_id) {
548
  entity_revision_delete('node', $revision_id);
549
550
}

551
/**
552
 * Checks whether the current page is the full page view of the passed-in node.
553
 *
554
 * @param \Drupal\node\NodeInterface $node
555
 *   A node entity.
556
557
558
 *
 * @return
 *   The ID of the node if this is a full page view, otherwise FALSE.
559
 */
560
function node_is_page(NodeInterface $node) {
561
562
563
  $route_match = \Drupal::routeMatch();
  if ($route_match->getRouteName() == 'node.view') {
    $page_node = $route_match->getParameter('node');
564
  }
565
  return (!empty($page_node) ? $page_node->id() == $node->id() : FALSE);
566
567
}

568
569
570
571
572
/**
 * 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.
573
  if (($node = \Drupal::routeMatch()->getParameter('node')) && $node instanceof NodeInterface) {
574
    $variables['attributes']['class'][] = drupal_html_class('node--type-' . $node->getType());
575
576
577
  }
}

578
/**
579
 * Implements hook_preprocess_HOOK() for block templates.
580
581
 */
function node_preprocess_block(&$variables) {
582
  if ($variables['configuration']['provider'] == 'node') {
583
    switch ($variables['elements']['#plugin_id']) {
584
      case 'node_syndicate_block':
585
        $variables['attributes']['role'] = 'complementary';
586
587
588
589
590
        break;
    }
  }
}

591
592
593
594
595
596
/**
 * Implements hook_theme_suggestions_HOOK().
 */
function node_theme_suggestions_node(array $variables) {
  $suggestions = array();
  $node = $variables['elements']['#node'];
597
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
598

599
  $suggestions[] = 'node__' . $sanitized_view_mode;
600
  $suggestions[] = 'node__' . $node->bundle();
601
  $suggestions[] = 'node__' . $node->bundle() . '__' . $sanitized_view_mode;
602
  $suggestions[] = 'node__' . $node->id();
603
  $suggestions[] = 'node__' . $node->id() . '__' . $sanitized_view_mode;
604
605
606
607

  return $suggestions;
}

608
/**
609
 * Prepares variables for node templates.
610
 *
611
 * Default template: node.html.twig.
612
 *
613
614
615
616
617
 * 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
618
619
620
621
 *   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'...
622
623
 */
function template_preprocess_node(&$variables) {
624
  $variables['view_mode'] = $variables['elements']['#view_mode'];
625
  // Provide a distinct $teaser boolean.
626
  $variables['teaser'] = $variables['view_mode'] == 'teaser';
627
  $variables['node'] = $variables['elements']['#node'];
628
  /** @var \Drupal\node\NodeInterface $node */
629
630
  $node = $variables['node'];

631
  $variables['date'] = format_date($node->getCreatedTime());
632
633
  $username = array(
    '#theme' => 'username',
634
    '#account' => $node->getOwner(),
635
    '#link_options' => array('attributes' => array('rel' => 'author')),
636
  );
637
  $variables['author_name'] = drupal_render($username);
638

639
  $variables['url'] = $node->url('canonical', array(
640
641
    'language' => $node->language(),
  ));
642
643
  $variables['label'] = $variables['elements']['title'];
  unset($variables['elements']['title']);
644
  $variables['page'] = $variables['view_mode'] == 'full' && node_is_page($node);
645

646
  // Helpful $content variable for templates.
647
  $variables += array('content' => array());
648
  foreach (Element::children($variables['elements']) as $key) {
649
650
    $variables['content'][$key] = $variables['elements'][$key];
  }
651
652

  // Display post information only on certain node types.
653
654
  // Avoid loading the entire node type config entity here that may not exist.
  $node_type_config = \Drupal::config('node.type.' . $node->bundle());
655
656
  // Used by RDF to add attributes around the author and date submitted.
  $variables['author_attributes'] = new Attribute();
657
658
659
  // Display submitted by default.
  $variables['display_submitted'] = $node_type_config->isNew() || $node_type_config->get('settings.node.submitted');
  if ($variables['display_submitted']) {
660
    if (theme_get_setting('features.node_user_picture')) {
661
      // To change user picture settings (e.g. image style), edit the 'compact'
662
663
      // 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.
664
      $variables['author_picture'] = user_view($node->getOwner(), 'compact');
665
    }
666
  }
667

668
  // Add article ARIA role.
669
  $variables['attributes']['role'] = 'article';
670

671
  // Gather node classes.
672
  $variables['attributes']['class'][] = 'node';
673
  $variables['attributes']['class'][] = drupal_html_class('node--type-' . $node->bundle());
674
  if ($node->isPromoted()) {
675
    $variables['attributes']['class'][] = 'node--promoted';
676
  }
677
  if ($node->isSticky()) {
678
    $variables['attributes']['class'][] = 'node--sticky';
679
  }
680
  if (!$node->isPublished()) {
681
    $variables['attributes']['class'][] = 'node--unpublished';
682
  }
683
  if ($variables['view_mode']) {
684
    $variables['attributes']['class'][] = drupal_html_class('node--view-mode-' . $variables['view_mode']);
685
686
  }
  if (isset($variables['preview'])) {
687
    $variables['attributes']['class'][] = 'node--preview';
688
  }
689
690
}

Dries's avatar
   
Dries committed
691
/**
692
 * Implements hook_permission().
Dries's avatar
   
Dries committed
693
 */
694
function node_permission() {
695
  $perms = array(
696
697
698
699
700
    '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,
    ),
701
702
    'administer content types' => array(
      'title' => t('Administer content types'),
703
      'description' => t('Promote, change ownership, edit revisions, and perform other tasks across all content types.'),
704
      'restrict access' => TRUE,
705
706
    ),
    'administer nodes' => array(
707
      'title' => t('Administer content'),
708
      'restrict access' => TRUE,
709
    ),
710
    'access content overview' => array(
711
      'title' => t('Access the Content overview page'),
712
      'description' => t('Get an overview of <a href="!url">all content</a>.', array('!url' => \Drupal::url('system.admin_content'))),
713
    ),
714
715
716
717
718
    'access content' => array(
      'title' => t('View published content'),
    ),
    'view own unpublished content' => array(
      'title' => t('View own unpublished content'),
719
    ),
720
721
    'view all revisions' => array(
      'title' => t('View all revisions'),
722
    ),
723
724
725
    '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>.'),
726
    ),
727
728
729
    '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>.'),
730
    ),
731
  );
732

733
  // Generate standard node permissions for all applicable node types.
734
  foreach (node_permissions_get_configured_types() as $type) {
735
    $perms += node_list_permissions($type);
736
737
738
  }

  return $perms;
Dries's avatar
   
Dries committed
739
740
}

741
/**
742
 * Implements hook_ranking().
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
 */
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.
765
  if ($node_cron_last = \Drupal::state()->get('node.cron_last')) {
766
767
768
    $ranking['recent'] = array(
      'title' => t('Recently posted'),
      // Exponential decay with half-life of 6 months, starting at last indexed node
769
770
      'score' => 'POW(2.0, (GREATEST(n.created, n.changed) - :node_cron_last) * 6.43e-8)',
      'arguments' => array(':node_cron_last' => $node_cron_last),
771
772
773
774
775
    );
  }
  return $ranking;
}

776
/**
777
 * Implements hook_user_cancel().
778
 */
779
function node_user_cancel($edit, $account, $method) {
780
781
782
  switch ($method) {
    case 'user_cancel_block_unpublish':
      // Unpublish nodes (current revisions).
783
      $nids = \Drupal::entityQuery('node')