node.module 122 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 12
use Symfony\Component\HttpFoundation\Response;

13
use Drupal\Core\Cache\CacheBackendInterface;
14 15 16
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Database\Query\SelectExtender;
use Drupal\Core\Database\Query\SelectInterface;
17
use Drupal\Core\Datetime\DrupalDateTime;
18
use Drupal\Core\Template\Attribute;
19 20
use Drupal\node\Plugin\Core\Entity\Node;
use Drupal\file\Plugin\Core\Entity\File;
21
use Drupal\Core\Entity\EntityInterface;
22
use Drupal\entity\Plugin\Core\Entity\EntityDisplay;
23

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

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

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

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

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

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

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

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

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

78 79 80 81 82 83 84
/**
 * Implements hook_rebuild().
 */
function node_rebuild() {
  node_types_rebuild();
}

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

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

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

126 127
    case 'admin/structure/types/manage/%/display':
      return '<p>' . t('Content items can be displayed using different view modes: Teaser, Full content, Print, RSS, etc. <em>Teaser</em> is a short format that is typically used in lists of multiple content items. <em>Full content</em> is typically used when the content is displayed on its own page.') . '</p>' .
128
        '<p>' . t('Here, you can define which fields are shown and hidden when %type content is displayed in each view mode, and define how the fields are displayed in each view mode.', array('%type' => node_type_get_label($arg[4]))) . '</p>';
129

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

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

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

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

Dries's avatar
 
Dries committed
177
/**
178
 * Implements hook_entity_info().
Dries's avatar
 
Dries committed
179
 */
180
function node_entity_info(&$info) {
181 182
  // Add a translation handler for fields if the language module is enabled.
  if (module_exists('language')) {
183
    $info['node']['translation']['node'] = TRUE;
184 185
  }

186 187 188
  // Search integration is provided by node.module, so search-related
  // view modes for nodes are defined here and not in search.module.
  if (module_exists('search')) {
189 190 191 192 193 194 195
    $info['node']['view_modes']['search_index'] = array(
      'label' => t('Search index'),
      'custom_settings' => FALSE,
    );
    $info['node']['view_modes']['search_result'] = array(
      'label' => t('Search result'),
      'custom_settings' => FALSE,
196 197 198
    );
  }

199 200
  // Bundles must provide a human readable name so we can create help and error
  // messages, and the path to attach Field admin pages to.
201
  node_type_cache_reset();
202
  foreach (node_type_get_names() as $type => $name) {
203
    $info['node']['bundles'][$type] = array(
204 205
      'label' => $name,
      'admin' => array(
206
        'path' => 'admin/structure/types/manage/%node_type',
207
        'real path' => 'admin/structure/types/manage/' . $type,
208
        'bundle argument' => 4,
209 210 211
      ),
    );
  }
Dries's avatar
 
Dries committed
212 213
}

214
/**
215
 * Implements hook_entity_display_alter().
216
 */
217
function node_entity_display_alter(EntityDisplay $display, $context) {
218
  // Hide field labels in search index.
219 220 221 222 223 224 225
  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);
      }
    }
226 227 228
  }
}

229
/**
230
 * Entity URI callback.
231 232 233
 *
 * @param Drupal\node\Node $node
 *   A node entity.
234 235
 *
 * @return array
236
 *   An array with 'path' as the key and the path to the node as its value.
237
 */
238
function node_uri(Node $node) {
239 240 241
  return array(
    'path' => 'node/' . $node->nid,
  );
242 243
}

244
/**
245
 * Implements hook_admin_paths().
246 247
 */
function node_admin_paths() {
248 249 250 251 252 253 254
  if (variable_get('node_admin_theme')) {
    $paths = array(
      'node/*/edit' => TRUE,
      'node/*/delete' => TRUE,
      'node/*/revisions' => TRUE,
      'node/*/revisions/*/revert' => TRUE,
      'node/*/revisions/*/delete' => TRUE,
255 256
      'node/*/translations' => TRUE,
      'node/*/translations/*' => TRUE,
257 258 259 260 261
      'node/add' => TRUE,
      'node/add/*' => TRUE,
    );
    return $paths;
  }
262 263
}

Dries's avatar
 
Dries committed
264
/**
265
 * Gathers a listing of links to nodes.
Dries's avatar
 
Dries committed
266 267
 *
 * @param $result
268 269 270 271
 *   A database result object from a query to fetch node entities. If your
 *   query joins the {node_comment_statistics} table so that the comment_count
 *   field is available, a title attribute will be added to show the number of
 *   comments.
Dries's avatar
 
Dries committed
272
 * @param $title
273
 *   (optional) A heading for the resulting list.
Dries's avatar
 
Dries committed
274 275
 *
 * @return
276 277
 *   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
278
 */
Dries's avatar
 
Dries committed
279
function node_title_list($result, $title = NULL) {
280
  $items = array();
281
  $num_rows = FALSE;
282
  foreach ($result as $node) {
283
    // Do not use $node->label() here, because $node comes from the database.
284
    $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());
285
    $num_rows = TRUE;
Dries's avatar
 
Dries committed
286 287
  }

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

Dries's avatar
 
Dries committed
291
/**
292
 * Decides on the type of marker to be displayed for a given node.
Dries's avatar
 
Dries committed
293
 *
Dries's avatar
 
Dries committed
294 295 296 297
 * @param $nid
 *   Node ID whose history supplies the "last viewed" timestamp.
 * @param $timestamp
 *   Time which is compared against node's "last viewed" timestamp.
298
 *
299 300
 * @return
 *   One of the MARK constants.
Dries's avatar
 
Dries committed
301
 */
302
function node_mark($nid, $timestamp) {
Dries's avatar
 
Dries committed
303
  global $user;
304
  $cache = &drupal_static(__FUNCTION__, array());
Dries's avatar
 
Dries committed
305

306
  if (!$user->uid || !module_exists('history')) {
307 308
    return MARK_READ;
  }
Dries's avatar
Dries committed
309
  if (!isset($cache[$nid])) {
310
    $cache[$nid] = history_read($nid);
Dries's avatar
 
Dries committed
311
  }
312
  if ($cache[$nid] == 0 && $timestamp > HISTORY_READ_LIMIT) {
313 314
    return MARK_NEW;
  }
315
  elseif ($timestamp > $cache[$nid] && $timestamp > HISTORY_READ_LIMIT) {
316 317 318
    return MARK_UPDATED;
  }
  return MARK_READ;
Dries's avatar
 
Dries committed
319 320
}

321 322 323
/**
 * Returns a list of all the available node types.
 *
324 325 326
 * This list can include types that are queued for addition or deletion.
 * See _node_types_build() for details.
 *
327
 * @return
328
 *   An array of node types, as objects, keyed by the type.
329
 *
330
 * @see _node_types_build()
331
 * @see node_type_load()
332 333 334 335 336 337 338 339
 */
function node_type_get_types() {
  return _node_types_build()->types;
}

/**
 * Returns the node type base of the passed node or node type string.
 *
340 341 342
 * The base indicates which module implements this node type and is used to
 * execute node-type-specific hooks. For types defined in the user interface
 * and managed by node.module, the base is 'node_content'.
343
 *
344 345
 * @param string $type
 *   A string that indicates the node type to return.
346
 *
347
 * @return string|false
348
 *   The node type base or FALSE if the node type is not found.
349 350
 *
 * @see node_invoke()
351
 */
352
function node_type_get_base($type) {
353 354 355 356 357
  $types = _node_types_build()->types;
  return isset($types[$type]) && isset($types[$type]->base) ? $types[$type]->base : FALSE;
}

/**
358 359 360 361
 * Returns a list of available node type names.
 *
 * This list can include types that are queued for addition or deletion.
 * See _node_types_build() for details.
362 363
 *
 * @return
364
 *   An array of node type labels, keyed by the node type name.
365 366
 *
 * @see _node_types_build()
367 368 369 370 371 372
 */
function node_type_get_names() {
  return _node_types_build()->names;
}

/**
373
 * Returns the node type label for the passed node type name.
374
 *
375 376
 * @param string $name
 *   The machine name of a node type.
377
 *
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
 * @return string|false
 *   The node type label or FALSE if the node type is not found.
 */
function node_type_get_label($name) {
  $types = _node_types_build()->names;
  return isset($types[$name]) ? $types[$name] : FALSE;
}

/**
 * Returns the node type label for the passed node.
 *
 * @param Drupal\node\Node $node
 *   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.
394
 */
395
function node_get_type_label($node) {
396
  $types = _node_types_build()->names;
397
  return isset($types[$node->type]) ? $types[$node->type] : FALSE;
398 399
}

400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
/**
 * Title callback: Returns the sanitized node type name.
 *
 * @param $node_type
 *   The node type object.
 *
 * @return
 *   The node type name that is safe for printing.
 */
function node_type_get_clean_name($node_type) {
  return check_plain($node_type->name);
}

/**
 * Description callback: Returns the node type description.
 *
 * @param $node_type
 *   The node type object.
 *
 * @return
 *   The node type description.
 */
function node_type_get_description($node_type) {
  return $node_type->description;
}

426
/**
427
 * Updates the database cache of node types.
428
 *
429 430
 * All new module-defined node types are saved to the database via a call to
 * node_type_save(), and obsolete ones are deleted via a call to
431
 * node_type_delete(). See _node_types_build() for an explanation of the new
432
 * and obsolete types.
433 434
 *
 * @see _node_types_build()
435 436
 */
function node_types_rebuild() {
437
  _node_types_build(TRUE);
438 439
}

440
/**
441
 * Menu argument loader: Loads a node type by string.
442 443
 *
 * @param $name
444
 *   The machine name of a node type to load.
445 446 447 448 449
 *
 * @return
 *   A node type object or FALSE if $name does not exist.
 */
function node_type_load($name) {
450 451
  $types = _node_types_build()->types;
  return isset($types[$name]) ? $types[$name] : FALSE;
452 453
}

454
/**
455 456
 * Saves a node type to the database.
 *
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484
 * @param object $info
 *   The node type to save; an object with the following properties:
 *   - type: A string giving the machine name of the node type.
 *   - name: A string giving the human-readable name of the node type.
 *   - base: A string that indicates the base string for hook functions. For
 *     example, 'node_content' is the value used by the UI when creating a new
 *     node type.
 *   - description: A string that describes the node type.
 *   - help: A string giving the help information shown to the user when
 *     creating a node of this type.
 *   - custom: TRUE or FALSE indicating whether this type is defined by a module
 *     (FALSE) or by a user (TRUE) via Add Content Type.
 *   - modified: TRUE or FALSE indicating whether this type has been modified by
 *     an administrator. Currently not used in any way.
 *   - locked: TRUE or FALSE indicating whether the administrator can change the
 *     machine name of this type.
 *   - disabled: TRUE or FALSE indicating whether this type has been disabled.
 *   - has_title: TRUE or FALSE indicating whether this type uses the node title
 *     field.
 *   - title_label: A string containing the label for the title.
 *   - module: A string giving the module defining this type of node.
 *   - orig_type: A string giving the original machine-readable name of this
 *     node type. This may be different from the current type name if the locked
 *     field is 0.
 *
 * @return int
 *   A status flag indicating the outcome of the operation, either SAVED_NEW or
 *   SAVED_UPDATED.
Dries's avatar
 
Dries committed
485
 */
486 487
function node_type_save($info) {
  $existing_type = !empty($info->old_type) ? $info->old_type : $info->type;
488
  $is_existing = (bool) db_query_range('SELECT 1 FROM {node_type} WHERE type = :type', 0, 1, array(':type' => $existing_type))->fetchField();
489 490 491 492 493 494 495 496 497 498 499 500 501
  $type = node_type_set_defaults($info);

  $fields = array(
    'type' => (string) $type->type,
    'name' => (string) $type->name,
    'base' => (string) $type->base,
    'has_title' => (int) $type->has_title,
    'title_label' => (string) $type->title_label,
    'description' => (string) $type->description,
    'help' => (string) $type->help,
    'custom' => (int) $type->custom,
    'modified' => (int) $type->modified,
    'locked' => (int) $type->locked,
502 503
    'disabled' => (int) $type->disabled,
    'module' => $type->module,
504
  );
505 506

  if ($is_existing) {
507 508 509 510
    db_update('node_type')
      ->fields($fields)
      ->condition('type', $existing_type)
      ->execute();
511

Dries's avatar
 
Dries committed
512
    if (!empty($type->old_type) && $type->old_type != $type->type) {
513
      field_attach_rename_bundle('node', $type->old_type, $type->type);
Dries's avatar
 
Dries committed
514
    }
515
    module_invoke_all('node_type_update', $type);
516
    $status = SAVED_UPDATED;
517 518
  }
  else {
519
    $fields['orig_type'] = (string) $type->orig_type;
520 521 522
    db_insert('node_type')
      ->fields($fields)
      ->execute();
523

524
    field_attach_create_bundle('node', $type->type);
525

526
    module_invoke_all('node_type_insert', $type);
527
    $status = SAVED_NEW;
528
  }
529 530

  // Clear the node type cache.
531
  node_type_cache_reset();
532 533

  return $status;
534
}
535

536
/**
537
 * Adds the default body field to a node type.
538
 *
539
 * @param $type
540
 *   A node type object.
541
 * @param $label
542
 *   (optional) The label for the body instance.
543 544 545
 *
 * @return
 *   Body field instance.
546
 */
547
function node_add_body_field($type, $label = 'Body') {
548 549
   // Add or remove the body field, as needed.
  $field = field_info_field('body');
550
  $instance = field_info_instance('node', 'body', $type->type);
551 552 553 554 555 556 557
  if (empty($field)) {
    $field = array(
      'field_name' => 'body',
      'type' => 'text_with_summary',
      'entity_types' => array('node'),
    );
    $field = field_create_field($field);
558
  }
559 560 561 562 563 564
  if (empty($instance)) {
    $instance = array(
      'field_name' => 'body',
      'entity_type' => 'node',
      'bundle' => $type->type,
      'label' => $label,
565
      'widget' => array('type' => 'text_textarea_with_summary'),
566 567
      'settings' => array('display_summary' => TRUE),
    );
568
    $instance = field_create_instance($instance);
569 570 571 572 573 574 575 576 577 578 579 580 581 582

    // Assign display settings for the 'default' and 'teaser' view modes.
    entity_get_display('node', $type->type, 'default')
      ->setComponent($field['field_name'], array(
        'label' => 'hidden',
        'type' => 'text_default',
      ))
      ->save();
    entity_get_display('node', $type->type, 'teaser')
      ->setComponent($field['field_name'], array(
        'label' => 'hidden',
        'type' => 'text_summary_or_trimmed',
      ))
      ->save();
583
  }
584

585
  return $instance;
586
}
587

588 589 590 591 592
/**
 * Implements hook_field_extra_fields().
 */
function node_field_extra_fields() {
  $extra = array();
593 594 595 596 597 598 599 600 601 602 603
  $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,
      );
    }
604

605 606
    // Add also the 'language' select if Language module is enabled and the
    // bundle has multilingual support.
607
    // Visibility of the ordering of the language selector is the same as on the
608 609 610
    // node/add form.
    if ($module_language_enabled) {
      $configuration = language_get_default_configuration('node', $bundle->type);
611
      if ($configuration['language_show']) {
612 613 614 615 616 617
        $extra['node'][$bundle->type]['form']['language'] = array(
          'label' => t('Language'),
          'description' => $description,
          'weight' => 0,
        );
      }
618
    }
619 620 621 622 623 624
    $extra['node'][$bundle->type]['display']['language'] = array(
      'label' => t('Language'),
      'description' => $description,
      'weight' => 0,
      'visible' => FALSE,
    );
625
  }
626 627

  return $extra;
628 629
}

630 631 632
/**
 * Deletes a node type from the database.
 *
633 634
 * @param $name
 *   The machine name of the node type to delete.
635
 */
636 637
function node_type_delete($name) {
  $type = node_type_load($name);
638
  db_delete('node_type')
639
    ->condition('type', $name)
640
    ->execute();
641 642
  field_attach_delete_bundle('node', $name);
  module_invoke_all('node_type_delete', $type);
643 644

  // Clear the node type cache.
645
  node_type_cache_reset();
646 647
}

648
/**
649 650
 * Updates all nodes of one type to be of another type.
 *
651
 * @param $old_type
652 653 654
 *   The current node type of the nodes.
 * @param $type
 *   The new node type of the nodes.
655 656
 *
 * @return
657
 *   The number of nodes whose node type field was modified.
658
 */
659
function node_type_update_nodes($old_type, $type) {
660
  return db_update('node')
661 662 663
    ->fields(array('type' => $type))
    ->condition('type', $old_type)
    ->execute();
Dries's avatar
 
Dries committed
664
}
Dries's avatar
 
Dries committed
665

666
/**
667 668
 * Builds and returns the list of available node types.
 *
669 670 671 672
 * The list of types is built by invoking hook_node_info() on all modules and
 * comparing this information with the node types in the {node_type} table.
 * These two information sources are not synchronized during module installation
 * until node_types_rebuild() is called.
Dries's avatar
 
Dries committed
673
 *
674
 * @param $rebuild
675 676
 *  (optional) TRUE to rebuild node types. Equivalent to calling
 *  node_types_rebuild(). Defaults to FALSE.
677
 *
678
 * @return
679
 *   An object with two properties:
680 681 682 683 684 685 686 687 688 689
 *   - names: Associative array of the names of node types, keyed by the type.
 *   - types: Associative array of node type objects, keyed by the type.
 *   Both of these arrays will include new types that have been defined by
 *   hook_node_info() implementations but not yet saved in the {node_type}
 *   table. These are indicated in the type object by $type->is_new being set
 *   to the value 1. These arrays will also include obsolete types: types that
 *   were previously defined by modules that have now been disabled, or for
 *   whatever reason are no longer being defined in hook_node_info()
 *   implementations, but are still in the database. These are indicated in the
 *   type object by $type->disabled being set to TRUE.
Dries's avatar
 
Dries committed
690
 */
691
function _node_types_build($rebuild = FALSE) {
692
  $cid = 'node_types:' . language(LANGUAGE_TYPE_INTERFACE)->langcode;
693

694 695
  if (!$rebuild) {
    $_node_types = &drupal_static(__FUNCTION__);
696 697 698
    if (isset($_node_types)) {
      return $_node_types;
    }
699
    if ($cache = cache()->get($cid)) {
700
      $_node_types = $cache->data;
701 702
      return $_node_types;
    }
703
  }
704

705
  $_node_types = (object) array('types' => array(), 'names' => array());
706

707 708 709 710 711 712 713 714
  foreach (module_implements('node_info') as $module) {
    $info_array = module_invoke($module, 'node_info');
    foreach ($info_array as $type => $info) {
      $info['type'] = $type;
      $_node_types->types[$type] = node_type_set_defaults($info);
      $_node_types->types[$type]->module = $module;
      $_node_types->names[$type] = $info['name'];
    }
715
  }
716
  $query = db_select('node_type', 'nt')
717 718
    ->addTag('translatable')
    ->addTag('node_type_access')
719
    ->fields('nt')
720 721 722 723 724 725 726 727
    ->orderBy('nt.type', 'ASC');
  if (!$rebuild) {
    $query->condition('disabled', 0);
  }
  foreach ($query->execute() as $type_object) {
    $type_db = $type_object->type;
    // Original disabled value.
    $disabled = $type_object->disabled;
728 729
    // Check for node types from disabled modules and mark their types for removal.
    // Types defined by the node module in the database (rather than by a separate
730 731
    // module using hook_node_info) have a base value of 'node_content'. The isset()
    // check prevents errors on old (pre-Drupal 7) databases.
732
    if (isset($type_object->base) && $type_object->base != 'node_content' && empty($_node_types->types[$type_db])) {
733
      $type_object->disabled = TRUE;
734
    }
735
    if (isset($_node_types->types[$type_db])) {
736 737 738 739 740
      $type_object->disabled = FALSE;
    }
    if (!isset($_node_types->types[$type_db]) || $type_object->modified) {
      $_node_types->types[$type_db] = $type_object;
      $_node_types->names[$type_db] = $type_object->name;
741

742
      if ($type_db != $type_object->orig_type) {
743 744
        unset($_node_types->types[$type_object->orig_type]);
        unset($_node_types->names[$type_object->orig_type]);
745 746
      }
    }
747 748 749 750 751 752 753 754 755 756
    $_node_types->types[$type_db]->disabled = $type_object->disabled;
    $_node_types->types[$type_db]->disabled_changed = $disabled != $type_object->disabled;
  }

  if ($rebuild) {
    foreach ($_node_types->types as $type => $type_object) {
      if (!empty($type_object->is_new) || !empty($type_object->disabled_changed)) {
        node_type_save($type_object);
      }
    }
757 758
  }

759
  asort($_node_types->names);
760

761
  cache()->set($cid, $_node_types, CacheBackendInterface::CACHE_PERMANENT, array('node_types' => TRUE));
762

763
  return $_node_types;
764 765
}

766 767 768 769
/**
 * Clears the node type cache.
 */
function node_type_cache_reset() {
770
  cache()->deleteTags(array('node_types' => TRUE));
771 772 773
  drupal_static_reset('_node_types_build');
}

774
/**
775
 * Sets the default values for a node type.
776
 *
777 778 779 780 781
 * The defaults are appropriate for a type defined through hook_node_info(),
 * since 'custom' is TRUE for types defined in the user interface, and FALSE
 * for types defined by modules. (The 'custom' flag prevents types from being
 * deleted through the user interface.) Also, the default for 'locked' is TRUE,
 * which prevents users from changing the machine name of the type.
782 783
 *
 * @param $info
784
 *   (optional) An object or array containing values to override the defaults.
785 786
 *   See hook_node_info() for details on what the array elements mean. Defaults
 *   to an empty array.
787 788
 *
 * @return
789
 *   A node type object, with missing values in $info set to their defaults.
790 791
 *
 * @see hook_node_ifo()
792
 */
793 794
function node_type_set_defaults($info = array()) {
  $info = (array) $info;
795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810
  $new_type = $info + array(
    'type' => '',
    'name' => '',
    'base' => '',
    'description' => '',
    'help' => '',
    'custom' => 0,
    'modified' => 0,
    'locked' => 1,
    'disabled' => 0,
    'is_new' => 1,
    'has_title' => 1,
    'title_label' => 'Title',
  );
  $new_type = (object) $new_type;

811
  // If the type has no title, set an empty label.
812 813 814
  if (!$new_type->has_title) {
    $new_type->title_label = '';
  }
815 816 817
  if (empty($new_type->module)) {
    $new_type->module = $new_type->base == 'node_content' ? 'node' : '';
  }
818 819 820
  $new_type->orig_type = isset($info['type']) ? $info['type'] : '';

  return $new_type;
Dries's avatar
 
Dries committed
821
}
Dries's avatar
 
Dries committed
822

823
/**
824
 * Implements hook_rdf_mapping().
825 826 827 828 829 830 831 832
 */
function node_rdf_mapping() {
  return array(
    array(
      'type' => 'node',
      'bundle' => RDF_DEFAULT_BUNDLE,
      'mapping' => array(
        'rdftype' => array('sioc:Item', 'foaf:Document'),
833
        'title' => array(
834 835 836 837 838 839 840 841 842
          'predicates' => array('dc:title'),
        ),
        'created' => array(
          'predicates' => array('dc:date', 'dc:created'),
          'datatype' => 'xsd:dateTime',
          'callback' => 'date_iso8601',
        ),
        'changed' => array(
          'predicates' => array('dc:modified'),
843 844
          'datatype' => 'xsd:dateTime',
          'callback' => 'date_iso8601',
845
        ),
846
        'body' => array(
847 848
          'predicates' => array('content:encoded'),
        ),
849
        'uid' => array(
850
          'predicates' => array('sioc:has_creator'),
851
          'type' => 'rel',
852
        ),
853
        'name' => array(
854 855
          'predicates' => array('foaf:name'),
        ),
856 857
        'comment_count' => array(
          'predicates' => array('sioc:num_replies'),
858 859 860 861 862 863
          'datatype' => 'xsd:integer',
        ),
        'last_activity' => array(
          'predicates' => array('sioc:last_activity_date'),
          'datatype' => 'xsd:dateTime',
          'callback' => 'date_iso8601',
864
        ),
865 866 867 868 869
      ),
    ),
  );
}

870
/**
871
 * Determines whether a node hook exists.
Dries's avatar
 
Dries committed
872
 *
873