node.module 130 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
13
14
15
use Symfony\Component\HttpFoundation\Response;

use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Database\Query\SelectExtender;
use Drupal\Core\Database\Query\SelectInterface;
16
use Drupal\Core\Template\Attribute;
17
use Drupal\node\Node;
18
use Drupal\file\File;
19
use Drupal\entity\EntityInterface;
20

21
/**
22
 * Denotes that the node is not published.
23
 */
24
const NODE_NOT_PUBLISHED = 0;
25
26

/**
27
 * Denotes that the node is published.
28
 */
29
const NODE_PUBLISHED = 1;
30
31

/**
32
 * Denotes that the node is not promoted to the front page.
33
 */
34
const NODE_NOT_PROMOTED = 0;
35
36

/**
37
 * Denotes that the node is promoted to the front page.
38
 */
39
const NODE_PROMOTED = 1;
40
41

/**
42
 * Denotes that the node is not sticky at the top of the page.
43
 */
44
const NODE_NOT_STICKY = 0;
45
46

/**
47
 * Denotes that the node is sticky at the top of the page.
48
 */
49
const NODE_STICKY = 1;
50

51
/**
52
 * Denotes the time cutoff for nodes marked as read.
53
 *
54
55
56
 * Nodes changed before this time are always marked as read. Nodes changed after
 * this time may be marked new, updated, or read, depending on their state for
 * the current user. Defaults to 30 days ago.
57
 */
58
define('NODE_NEW_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60);
59

60
/**
61
62
63
64
 * Denotes that access is allowed for a node.
 *
 * Modules should return this value from hook_node_access() to allow access to a
 * node.
65
 */
66
const NODE_ACCESS_ALLOW = 'allow';
67
68

/**
69
70
71
72
 * Denotes that access is denied for a node.
 *
 * Modules should return this value from hook_node_access() to deny access to a
 * node.
73
 */
74
const NODE_ACCESS_DENY = 'deny';
75
76

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

84
85
86
87
88
89
90
/**
 * Implements hook_rebuild().
 */
function node_rebuild() {
  node_types_rebuild();
}

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

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

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

132
133
134
    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>' .
        '<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_name($arg[4]))) . '</p>';
135

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

139
140
    case 'node/%/edit':
      $node = node_load($arg[1]);
141
      $type = node_type_load($node->type);
142
      return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) . '</p>' : '');
Dries's avatar
   
Dries committed
143
  }
Dries's avatar
   
Dries committed
144

145
  if ($arg[0] == 'node' && $arg[1] == 'add' && $arg[2]) {
146
    $type = node_type_load($arg[2]);
147
    return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) . '</p>' : '');
148
  }
Dries's avatar
   
Dries committed
149
150
}

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

Dries's avatar
   
Dries committed
183
/**
184
 * Implements hook_cron().
Dries's avatar
   
Dries committed
185
 */
186
function node_cron() {
187
188
189
  db_delete('history')
    ->condition('timestamp', NODE_NEW_LIMIT, '<')
    ->execute();
190
191
}

Dries's avatar
   
Dries committed
192
/**
193
 * Implements hook_entity_info().
Dries's avatar
   
Dries committed
194
 */
195
function node_entity_info() {
Dries's avatar
   
Dries committed
196
197
  $return = array(
    'node' => array(
198
      'label' => t('Node'),
199
      'entity class' => 'Drupal\node\Node',
200
201
202
203
      'controller class' => 'Drupal\node\NodeStorageController',
      'form controller class' => array(
        'default' => 'Drupal\node\NodeFormController',
      ),
204
205
      'base table' => 'node',
      'revision table' => 'node_revision',
206
      'uri callback' => 'node_uri',
207
      'fieldable' => TRUE,
208
      'entity keys' => array(
209
210
211
        'id' => 'nid',
        'revision' => 'vid',
        'bundle' => 'type',
212
        'label' => 'title',
213
        'uuid' => 'uuid',
214
      ),
215
216
217
      'bundle keys' => array(
        'bundle' => 'type',
      ),
218
      'bundles' => array(),
219
220
      'view modes' => array(
        'full' => array(
221
          'label' => t('Full content'),
222
          'custom settings' => FALSE,
223
224
225
        ),
        'teaser' => array(
          'label' => t('Teaser'),
226
          'custom settings' => TRUE,
227
228
229
        ),
        'rss' => array(
          'label' => t('RSS'),
230
          'custom settings' => FALSE,
231
232
        ),
      ),
Dries's avatar
   
Dries committed
233
234
    ),
  );
235

236
237
238
239
240
  // Add a translation handler for fields if the language module is enabled.
  if (module_exists('language')) {
    $return['node']['translation']['node'] = TRUE;
  }

241
242
243
244
245
  // 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')) {
    $return['node']['view modes'] += array(
      'search_index' => array(
246
        'label' => t('Search index'),
247
        'custom settings' => FALSE,
248
249
      ),
      'search_result' => array(
250
        'label' => t('Search result'),
251
        'custom settings' => FALSE,
252
253
254
255
      ),
    );
  }

256
257
  // Bundles must provide a human readable name so we can create help and error
  // messages, and the path to attach Field admin pages to.
258
  node_type_cache_reset();
259
260
261
262
  foreach (node_type_get_names() as $type => $name) {
    $return['node']['bundles'][$type] = array(
      'label' => $name,
      'admin' => array(
263
        'path' => 'admin/structure/types/manage/%node_type',
264
        'real path' => 'admin/structure/types/manage/' . $type,
265
        'bundle argument' => 4,
266
267
268
269
        'access arguments' => array('administer content types'),
      ),
    );
  }
Dries's avatar
   
Dries committed
270

271
  return $return;
Dries's avatar
   
Dries committed
272
273
}

274
275
276
277
278
279
280
281
282
283
/**
 * Implements hook_field_display_ENTITY_TYPE_alter().
 */
function node_field_display_node_alter(&$display, $context) {
  // Hide field labels in search index.
  if ($context['view_mode'] == 'search_index') {
    $display['label'] = 'hidden';
  }
}

284
/**
285
 * Entity URI callback.
286
287
288
 *
 * @param Drupal\node\Node $node
 *   A node entity.
289
 */
290
function node_uri(Node $node) {
291
292
293
  return array(
    'path' => 'node/' . $node->nid,
  );
294
295
}

296
/**
297
 * Implements hook_admin_paths().
298
299
 */
function node_admin_paths() {
300
301
302
303
304
305
306
307
308
309
310
311
  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,
      'node/add' => TRUE,
      'node/add/*' => TRUE,
    );
    return $paths;
  }
312
313
}

Dries's avatar
   
Dries committed
314
/**
315
 * Gathers a listing of links to nodes.
Dries's avatar
   
Dries committed
316
317
 *
 * @param $result
318
319
320
321
 *   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
322
 * @param $title
323
 *   (optional) A heading for the resulting list.
Dries's avatar
   
Dries committed
324
325
 *
 * @return
326
327
 *   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
328
 */
Dries's avatar
   
Dries committed
329
function node_title_list($result, $title = NULL) {
330
  $items = array();
331
  $num_rows = FALSE;
332
  foreach ($result as $node) {
333
    // Do not use $node->label() here, because $node comes from the database.
334
    $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());
335
    $num_rows = TRUE;
Dries's avatar
   
Dries committed
336
337
  }

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

Dries's avatar
   
Dries committed
341
/**
342
 * Updates the 'last viewed' timestamp of the specified node for current user.
343
 *
344
345
 * @param Drupal\node\Node $node
 *   A node entity.
Dries's avatar
   
Dries committed
346
 */
347
function node_tag_new(Node $node) {
Dries's avatar
   
Dries committed
348
349
  global $user;
  if ($user->uid) {
350
351
352
353
354
355
356
    db_merge('history')
      ->key(array(
        'uid' => $user->uid,
        'nid' => $node->nid,
      ))
      ->fields(array('timestamp' => REQUEST_TIME))
      ->execute();
357
   }
Dries's avatar
   
Dries committed
358
359
}

Dries's avatar
   
Dries committed
360
/**
361
 * Retrieves the timestamp for the current user's last view of a specified node.
Dries's avatar
   
Dries committed
362
 */
Dries's avatar
   
Dries committed
363
364
function node_last_viewed($nid) {
  global $user;
365
  $history = &drupal_static(__FUNCTION__, array());
Dries's avatar
   
Dries committed
366

Dries's avatar
   
Dries committed
367
  if (!isset($history[$nid])) {
368
    $history[$nid] = db_query("SELECT timestamp FROM {history} WHERE uid = :uid AND nid = :nid", array(':uid' => $user->uid, ':nid' => $nid))->fetchObject();
Dries's avatar
   
Dries committed
369
370
  }

371
  return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp : 0);
Dries's avatar
   
Dries committed
372
373
374
}

/**
375
 * Decides on the type of marker to be displayed for a given node.
Dries's avatar
   
Dries committed
376
 *
Dries's avatar
   
Dries committed
377
378
379
380
 * @param $nid
 *   Node ID whose history supplies the "last viewed" timestamp.
 * @param $timestamp
 *   Time which is compared against node's "last viewed" timestamp.
381
 *
382
383
 * @return
 *   One of the MARK constants.
Dries's avatar
   
Dries committed
384
 */
385
function node_mark($nid, $timestamp) {
Dries's avatar
   
Dries committed
386
  global $user;
387
  $cache = &drupal_static(__FUNCTION__, array());
Dries's avatar
   
Dries committed
388

389
390
391
  if (!$user->uid) {
    return MARK_READ;
  }
Dries's avatar
Dries committed
392
  if (!isset($cache[$nid])) {
393
    $cache[$nid] = node_last_viewed($nid);
Dries's avatar
   
Dries committed
394
  }
395
396
397
398
399
400
401
  if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) {
    return MARK_NEW;
  }
  elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) {
    return MARK_UPDATED;
  }
  return MARK_READ;
Dries's avatar
   
Dries committed
402
403
}

404
/**
405
 * Extracts the type name.
406
 *
407
 * @param Drupal\node\Node|string $node
408
 *   Either a string or object, containing the node type information.
409
410
 *
 * @return
411
 *   Node type of the passed-in data.
412
 */
413
414
415
function _node_extract_type($node) {
  return is_object($node) ? $node->type : $node;
}
416

417
418
419
/**
 * Returns a list of all the available node types.
 *
420
421
422
 * This list can include types that are queued for addition or deletion.
 * See _node_types_build() for details.
 *
423
 * @return
424
 *   An array of node types, as objects, keyed by the type.
425
 *
426
 * @see node_type_load()
427
428
429
430
431
432
433
434
 */
function node_type_get_types() {
  return _node_types_build()->types;
}

/**
 * Returns the node type base of the passed node or node type string.
 *
435
436
437
 * 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'.
438
 *
439
440
 * @param Drupal\node\Node|string $node
 *   A node entity or string that indicates the node type to return.
441
 *
442
443
 * @return
 *   The node type base or FALSE if the node type is not found.
444
445
 *
 * @see node_invoke()
446
447
448
449
450
451
452
453
 */
function node_type_get_base($node) {
  $type = _node_extract_type($node);
  $types = _node_types_build()->types;
  return isset($types[$type]) && isset($types[$type]->base) ? $types[$type]->base : FALSE;
}

/**
454
455
456
457
 * 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.
458
459
460
461
462
463
464
465
466
467
468
 *
 * @return
 *   An array of node type names, keyed by the type.
 */
function node_type_get_names() {
  return _node_types_build()->names;
}

/**
 * Returns the node type name of the passed node or node type string.
 *
469
470
 * @param Drupal\node\Node|string $node
 *   A node entity or string that indicates the node type to return.
471
472
473
474
475
476
477
478
 *
 * @return
 *   The node type name or FALSE if the node type is not found.
 */
function node_type_get_name($node) {
  $type = _node_extract_type($node);
  $types = _node_types_build()->names;
  return isset($types[$type]) ? $types[$type] : FALSE;
479
480
}

481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
/**
 * 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;
}

507
/**
508
 * Updates the database cache of node types.
509
 *
510
511
 * 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
512
 * node_type_delete(). See _node_types_build() for an explanation of the new
513
 * and obsolete types.
514
515
 */
function node_types_rebuild() {
516
  _node_types_build(TRUE);
517
518
}

519
/**
520
 * Menu argument loader: Loads a node type by string.
521
522
 *
 * @param $name
523
 *   The machine name of a node type to load.
524
525
526
527
528
 *
 * @return
 *   A node type object or FALSE if $name does not exist.
 */
function node_type_load($name) {
529
530
  $types = _node_types_build()->types;
  return isset($types[$name]) ? $types[$name] : FALSE;
531
532
}

533
/**
534
535
536
537
 * Saves a node type to the database.
 *
 * @param $info
 *   The node type to save, as an object.
Dries's avatar
   
Dries committed
538
539
 *
 * @return
540
 *   Status flag indicating outcome of the operation.
Dries's avatar
   
Dries committed
541
 */
542
543
function node_type_save($info) {
  $existing_type = !empty($info->old_type) ? $info->old_type : $info->type;
544
  $is_existing = (bool) db_query_range('SELECT 1 FROM {node_type} WHERE type = :type', 0, 1, array(':type' => $existing_type))->fetchField();
545
546
547
548
549
550
551
552
553
554
555
556
557
  $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,
558
559
    'disabled' => (int) $type->disabled,
    'module' => $type->module,
560
  );
561
562

  if ($is_existing) {
563
564
565
566
    db_update('node_type')
      ->fields($fields)
      ->condition('type', $existing_type)
      ->execute();
567

Dries's avatar
   
Dries committed
568
    if (!empty($type->old_type) && $type->old_type != $type->type) {
569
      field_attach_rename_bundle('node', $type->old_type, $type->type);
Dries's avatar
   
Dries committed
570
    }
571
    module_invoke_all('node_type_update', $type);
572
    $status = SAVED_UPDATED;
573
574
  }
  else {
575
    $fields['orig_type'] = (string) $type->orig_type;
576
577
578
    db_insert('node_type')
      ->fields($fields)
      ->execute();
579

580
    field_attach_create_bundle('node', $type->type);
581

582
    module_invoke_all('node_type_insert', $type);
583
    $status = SAVED_NEW;
584
  }
585
586

  // Clear the node type cache.
587
  node_type_cache_reset();
588
589

  return $status;
590
}
591

592
/**
593
 * Adds the default body field to a node type.
594
 *
595
 * @param $type
596
 *   A node type object.
597
 * @param $label
598
 *   (optional) The label for the body instance.
599
600
601
 *
 * @return
 *   Body field instance.
602
 */
603
function node_add_body_field($type, $label = 'Body') {
604
605
   // Add or remove the body field, as needed.
  $field = field_info_field('body');
606
  $instance = field_info_instance('node', 'body', $type->type);
607
608
609
610
611
612
613
  if (empty($field)) {
    $field = array(
      'field_name' => 'body',
      'type' => 'text_with_summary',
      'entity_types' => array('node'),
    );
    $field = field_create_field($field);
614
  }
615
616
617
618
619
620
  if (empty($instance)) {
    $instance = array(
      'field_name' => 'body',
      'entity_type' => 'node',
      'bundle' => $type->type,
      'label' => $label,
621
      'widget' => array('type' => 'text_textarea_with_summary'),
622
623
      'settings' => array('display_summary' => TRUE),
      'display' => array(
624
        'default' => array(
625
626
627
628
629
630
631
632
633
          'label' => 'hidden',
          'type' => 'text_default',
        ),
        'teaser' => array(
          'label' => 'hidden',
          'type' => 'text_summary_or_trimmed',
        ),
      ),
    );
634
    $instance = field_create_instance($instance);
635
  }
636
  return $instance;
637
}
638

639
640
641
642
643
/**
 * Implements hook_field_extra_fields().
 */
function node_field_extra_fields() {
  $extra = array();
644
645
646
647
648
649
650
651
652
653
654
  $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,
      );
    }
655

656
657
    // Add also the 'language' select if Language module is enabled and the
    // bundle has multilingual support.
658
659
660
    // Visibility of the ordering of the language selector is the same as on the node/add form,
    // i.e. node_type_language_hidden_TYPE variable
    if ($module_language_enabled && !variable_get('node_type_language_hidden_' . $bundle->type, TRUE)) {
661
662
663
664
      $extra['node'][$bundle->type]['form']['language'] = array(
        'label' => t('Language'),
        'description' => $description,
        'weight' => 0,
665
666
667
      );
    }
  }
668
669

  return $extra;
670
671
}

672
673
674
675
676
677
678
679
680
681
682
683
/**
 * Get the default language for a node type.
 *
 * @param string $node_type
 *   The type of node.
 *
 * @return string
 *   The language code of the node type's default langcode.
 */
function node_type_get_default_langcode($node_type) {
  $default_value = variable_get('node_type_language_default_' . $node_type, 'site_default');

684
  $language_interface = language(LANGUAGE_TYPE_INTERFACE);
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712

  if ($default_value == LANGUAGE_NOT_SPECIFIED) {
    return LANGUAGE_NOT_SPECIFIED;
  }

  switch ($default_value) {
    case 'site_default':
      $default_value = language_default()->langcode;
      break;

    case 'current_interface':
      $default_value = $language_interface->langcode;
      break;

    case 'authors_default':
      global $user;
      if (!empty($user->preferred_langcode)) {
        $default_value = $user->preferred_langcode;
      }
      else {
        $default_value = $language_interface->langcode;
      }
      break;
  }

  return $default_value;
}

713
714
715
/**
 * Deletes a node type from the database.
 *
716
717
 * @param $name
 *   The machine name of the node type to delete.
718
 */
719
720
function node_type_delete($name) {
  $type = node_type_load($name);
721
  db_delete('node_type')
722
    ->condition('type', $name)
723
    ->execute();
724
725
  field_attach_delete_bundle('node', $name);
  module_invoke_all('node_type_delete', $type);
726
727

  // Clear the node type cache.
728
  node_type_cache_reset();
729
730
}

731
/**
732
733
 * Updates all nodes of one type to be of another type.
 *
734
 * @param $old_type
735
736
737
 *   The current node type of the nodes.
 * @param $type
 *   The new node type of the nodes.
738
739
 *
 * @return
740
 *   The number of nodes whose node type field was modified.
741
 */
742
function node_type_update_nodes($old_type, $type) {
743
  return db_update('node')
744
745
746
    ->fields(array('type' => $type))
    ->condition('type', $old_type)
    ->execute();
Dries's avatar
   
Dries committed
747
}
Dries's avatar
   
Dries committed
748

749
/**
750
751
 * Builds and returns the list of available node types.
 *
752
753
754
755
 * 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
756
 *
757
758
 * @param $rebuild
 *  TRUE to rebuild node types. Equivalent to calling node_types_rebuild().
759
 *
760
 * @return
761
 *   An object with two properties:
762
763
764
765
766
767
768
769
770
771
 *   - 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
772
 */
773
function _node_types_build($rebuild = FALSE) {
774
  $cid = 'node_types:' . language(LANGUAGE_TYPE_INTERFACE)->langcode;
775

776
777
  if (!$rebuild) {
    $_node_types = &drupal_static(__FUNCTION__);
778
779
780
    if (isset($_node_types)) {
      return $_node_types;
    }
781
    if ($cache = cache()->get($cid)) {
782
      $_node_types = $cache->data;
783
784
      return $_node_types;
    }
785
  }
786

787
  $_node_types = (object) array('types' => array(), 'names' => array());
788

789
790
791
792
793
794
795
796
  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'];
    }
797
  }
798
  $query = db_select('node_type', 'nt')
799
800
    ->addTag('translatable')
    ->addTag('node_type_access')
801
    ->fields('nt')
802
803
804
805
806
807
808
809
    ->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;
810
811
    // 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
812
813
    // module using hook_node_info) have a base value of 'node_content'. The isset()
    // check prevents errors on old (pre-Drupal 7) databases.
814
    if (isset($type_object->base) && $type_object->base != 'node_content' && empty($_node_types->types[$type_db])) {
815
      $type_object->disabled = TRUE;
816
    }
817
    if (isset($_node_types->types[$type_db])) {
818
819
820
821
822
      $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;
823

824
      if ($type_db != $type_object->orig_type) {
825
826
        unset($_node_types->types[$type_object->orig_type]);
        unset($_node_types->names[$type_object->orig_type]);
827
828
      }
    }
829
830
831
832
833
834
835
836
837
838
    $_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);
      }
    }
839
840
  }

841
  asort($_node_types->names);
842

843
  cache()->set($cid, $_node_types);
844

845
  return $_node_types;
846
847
}

848
849
850
851
/**
 * Clears the node type cache.
 */
function node_type_cache_reset() {
852
  cache()->deletePrefix('node_types:');
853
854
855
  drupal_static_reset('_node_types_build');
}

856
/**
857
 * Sets the default values for a node type.
858
 *
859
860
861
862
863
 * 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.
864
865
 *
 * @param $info
866
867
 *   (optional) An object or array containing values to override the defaults.
 *   See hook_node_info() for details on what the array elements mean.
868
869
 *
 * @return
870
 *   A node type object, with missing values in $info set to their defaults.
871
 */
872
873
function node_type_set_defaults($info = array()) {
  $info = (array) $info;
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
  $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;

890
  // If the type has no title, set an empty label.
891
892
893
  if (!$new_type->has_title) {
    $new_type->title_label = '';
  }
894
895
896
  if (empty($new_type->module)) {
    $new_type->module = $new_type->base == 'node_content' ? 'node' : '';
  }
897
898
899
  $new_type->orig_type = isset($info['type']) ? $info['type'] : '';

  return $new_type;
Dries's avatar
   
Dries committed
900
}
Dries's avatar
   
Dries committed
901

902
/**
903
 * Implements hook_rdf_mapping().
904
905
906
907
908
909
910
911
 */
function node_rdf_mapping() {
  return array(
    array(
      'type' => 'node',
      'bundle' => RDF_DEFAULT_BUNDLE,
      'mapping' => array(
        'rdftype' => array('sioc:Item', 'foaf:Document'),
912
        'title' => array(
913
914
915
916
917
918
919
920
921
          'predicates' => array('dc:title'),
        ),
        'created' => array(
          'predicates' => array('dc:date', 'dc:created'),
          'datatype' => 'xsd:dateTime',
          'callback' => 'date_iso8601',
        ),
        'changed' => array(
          'predicates' => array('dc:modified'),
922
923
          'datatype' => 'xsd:dateTime',
          'callback' => 'date_iso8601',
924
        ),
925
        'body' => array(
926
927
          'predicates' => array('content:encoded'),
        ),