/[drupal]/drupal/modules/node/node.module
ViewVC logotype

Contents of /drupal/modules/node/node.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1162 - (show annotations) (download) (as text)
Wed Nov 4 05:39:14 2009 UTC (3 weeks, 4 days ago) by webchick
Branch: MAIN
Changes since 1.1161: +1 -2 lines
File MIME type: text/x-php
#571086 follow-up by sun: Allow specifying a 'wrapper callback' before executing a form builder function.
1 <?php
2 // $Id: node.module,v 1.1161 2009/11/03 06:47:22 webchick Exp $
3
4 /**
5 * @file
6 * The core that allows content to be submitted to the site. Modules and
7 * scripts may programmatically submit nodes using the usual form API pattern.
8 */
9
10 /**
11 * Node is not published.
12 */
13 define('NODE_NOT_PUBLISHED', 0);
14
15 /**
16 * Node is published.
17 */
18 define('NODE_PUBLISHED', 1);
19
20 /**
21 * Node is not promoted to front page.
22 */
23 define('NODE_NOT_PROMOTED', 0);
24
25 /**
26 * Node is promoted to front page.
27 */
28 define('NODE_PROMOTED', 1);
29
30 /**
31 * Node is not sticky at top of the page.
32 */
33 define('NODE_NOT_STICKY', 0);
34
35 /**
36 * Node is sticky at top of the page.
37 */
38 define('NODE_STICKY', 1);
39
40 /**
41 * Nodes changed before this time are always marked as read.
42 *
43 * Nodes changed after this time may be marked new, updated, or read, depending
44 * on their state for the current user. Defaults to 30 days ago.
45 */
46 define('NODE_NEW_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60);
47
48 /**
49 * Modules should return this value from hook_node_access() to allow access to a node.
50 */
51 define('NODE_ACCESS_ALLOW', 'allow');
52
53 /**
54 * Modules should return this value from hook_node_access() to deny access to a node.
55 */
56 define('NODE_ACCESS_DENY', 'deny');
57
58 /**
59 * Modules should return this value from hook_node_access() to not affect node access.
60 */
61 define('NODE_ACCESS_IGNORE', NULL);
62
63 /**
64 * Implement hook_help().
65 */
66 function node_help($path, $arg) {
67 // Remind site administrators about the {node_access} table being flagged
68 // for rebuild. We don't need to issue the message on the confirm form, or
69 // while the rebuild is being processed.
70 if ($path != 'admin/reports/status/rebuild' && $path != 'batch' && strpos($path, '#') === FALSE
71 && user_access('access administration pages') && node_access_needs_rebuild()) {
72 if ($path == 'admin/reports/status') {
73 $message = t('The content access permissions need to be rebuilt.');
74 }
75 else {
76 $message = t('The content access permissions need to be rebuilt. Please visit <a href="@node_access_rebuild">this page</a>.', array('@node_access_rebuild' => url('admin/reports/status/rebuild')));
77 }
78 drupal_set_message($message, 'error');
79 }
80
81 switch ($path) {
82 case 'admin/help#node':
83 $output = '<p>' . t('The node module manages content on your site, and stores all posts (regardless of type) as a "node" . In addition to basic publishing settings, including whether the post has been published, promoted to the site front page, or should remain present (or sticky) at the top of lists, the node module also records basic information about the author of a post. Optional revision control over edits is available. For additional functionality, the node module is often extended by other modules.') . '</p>';
84 $output .= '<p>' . t('Though each post on your site is a node, each post is also of a particular <a href="@content-type">content type</a>. <a href="@content-type">Content types</a> are used to define the characteristics of a post, including the title and description of the fields displayed on its add and edit pages. Each content type may have different default settings for <em>Publishing options</em> and other workflow controls. By default, the two content types in a standard Drupal installation are <em>Page</em> and <em>Story</em>. Use the <a href="@content-type">content types page</a> to add new or edit existing content types. Additional content types also become available as you enable additional core, contributed and custom modules.', array('@content-type' => url('admin/structure/types'))) . '</p>';
85 $output .= '<p>' . t('The administrative <a href="@content">content page</a> allows you to review and manage your site content. The node module makes a number of permissions available for each content type, which may be set by role on the <a href="@permissions">permissions page</a>.', array('@content' => url('admin/content'), '@permissions' => url('admin/config/people/permissions'))) . '</p>';
86 $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@node">Node module</a>.', array('@node' => 'http://drupal.org/handbook/modules/node/')) . '</p>';
87 return $output;
88
89 case 'admin/content':
90 return ' '; // Return a non-null value so that the 'more help' link is shown.
91
92 case 'admin/structure/types/add':
93 return '<p>' . t('Individual content types can have different fields, behaviors, and permissions assigned to them.') . '</p>';
94
95 case 'admin/structure/types/manage/' . $arg[3] . '/fields':
96 return '<p>' . t('This form lets you add, edit, and arrange fields within the %type content type.', array('%type' => node_type_get_name($arg[3]))) . '</p>';
97
98 case 'admin/structure/types/manage/' . $arg[3] . '/display':
99 return '<p>' . t('This form lets you configure how fields and labels are displayed when %type content is viewed in teaser and full-page mode.', array('%type' => node_type_get_name($arg[3]))) . '</p>';
100
101 case 'admin/structure/types/manage/' . $arg[3] . '/display/' . $arg[5]:
102 return '<p>' . t('This form lets you configure how fields should be displayed when rendered %type content in the following contexts.', array('%type' => node_type_get_name($arg[3]))) . '</p>';
103
104 case 'node/%/revisions':
105 return '<p>' . t('The revisions let you track differences between multiple versions of a post.') . '</p>';
106
107 case 'node/%/edit':
108 $node = node_load($arg[1]);
109 $type = node_type_get_type($node);
110 return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) . '</p>' : '');
111 }
112
113 if ($arg[0] == 'node' && $arg[1] == 'add' && $arg[2]) {
114 $type = node_type_get_type(str_replace('-', '_', $arg[2]));
115 return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) . '</p>' : '');
116 }
117 }
118
119 /**
120 * Implement hook_theme().
121 */
122 function node_theme() {
123 return array(
124 'node' => array(
125 'render element' => 'elements',
126 'template' => 'node',
127 ),
128 'node_list' => array(
129 'variables' => array('items' => NULL, 'title' => NULL),
130 ),
131 'node_search_admin' => array(
132 'render element' => 'form',
133 ),
134 'node_filter_form' => array(
135 'render element' => 'form',
136 'file' => 'node.admin.inc',
137 ),
138 'node_filters' => array(
139 'render element' => 'form',
140 'file' => 'node.admin.inc',
141 ),
142 'node_add_list' => array(
143 'variables' => array('content' => NULL),
144 'file' => 'node.pages.inc',
145 ),
146 'node_form' => array(
147 'render element' => 'form',
148 'file' => 'node.pages.inc',
149 ),
150 'node_preview' => array(
151 'variables' => array('node' => NULL),
152 'file' => 'node.pages.inc',
153 ),
154 'node_log_message' => array(
155 'variables' => array('log' => NULL),
156 ),
157 'node_admin_overview' => array(
158 'variables' => array('name' => NULL, 'type' => NULL),
159 ),
160 );
161 }
162
163 /**
164 * Implement hook_cron().
165 */
166 function node_cron() {
167 db_delete('history')
168 ->condition('timestamp', NODE_NEW_LIMIT, '<')
169 ->execute();
170 }
171
172 /**
173 * Implement hook_entity_info().
174 */
175 function node_entity_info() {
176 $return = array(
177 'node' => array(
178 'label' => t('Node'),
179 'controller class' => 'NodeController',
180 'base table' => 'node',
181 'revision table' => 'node_revision',
182 'fieldable' => TRUE,
183 'object keys' => array(
184 'id' => 'nid',
185 'revision' => 'vid',
186 'bundle' => 'type',
187 ),
188 'bundle keys' => array(
189 'bundle' => 'type',
190 ),
191 // Node.module handles its own caching.
192 // 'cacheable' => FALSE,
193 'bundles' => array(),
194 ),
195 );
196 // Bundles must provide a human readable name so we can create help and error
197 // messages, and the path to attach Field admin pages to.
198 foreach (node_type_get_names() as $type => $name) {
199 $return['node']['bundles'][$type] = array(
200 'label' => $name,
201 'admin' => array(
202 'path' => 'admin/structure/types/manage/%node_type',
203 'real path' => 'admin/structure/types/manage/' . str_replace('_', '-', $type),
204 'bundle argument' => 4,
205 'access arguments' => array('administer content types'),
206 ),
207 );
208 }
209 return $return;
210 }
211
212
213 /**
214 * Implement hook_field_build_modes().
215 */
216 function node_field_build_modes($obj_type) {
217 $modes = array();
218 if ($obj_type == 'node') {
219 $modes = array(
220 'teaser' => t('Teaser'),
221 'full' => t('Full node'),
222 'rss' => t('RSS'),
223 );
224 // Search integration is provided by node.module, so search-related
225 // build-modes for nodes are defined here and not in search.module.
226 if (module_exists('search')) {
227 $modes += array(
228 'search_index' => t('Search Index'),
229 'search_result' => t('Search Result'),
230 );
231 }
232 }
233 return $modes;
234 }
235
236 /**
237 * Gather a listing of links to nodes.
238 *
239 * @param $result
240 * A DB result object from a query to fetch node entities. If your query
241 * joins the <code>node_comment_statistics</code> table so that the
242 * <code>comment_count</code> field is available, a title attribute will
243 * be added to show the number of comments.
244 * @param $title
245 * A heading for the resulting list.
246 *
247 * @return
248 * An HTML list suitable as content for a block, or FALSE if no result can
249 * fetch from DB result object.
250 */
251 function node_title_list($result, $title = NULL) {
252 $items = array();
253 $num_rows = FALSE;
254 foreach ($result as $node) {
255 $items[] = l($node->title, 'node/' . $node->nid, !empty($node->comment_count) ? array('attributes' => array('title' => format_plural($node->comment_count, '1 comment', '@count comments'))) : array());
256 $num_rows = TRUE;
257 }
258
259 return $num_rows ? theme('node_list', array('items' => $items, 'title' => $title)) : FALSE;
260 }
261
262 /**
263 * Format a listing of links to nodes.
264 *
265 * @ingroup themeable
266 */
267 function theme_node_list($variables) {
268 return theme('item_list', $variables);
269 }
270
271 /**
272 * Update the 'last viewed' timestamp of the specified node for current user.
273 */
274 function node_tag_new($nid) {
275 global $user;
276
277 if ($user->uid) {
278 db_merge('history')
279 ->key(array(
280 'uid' => $user->uid,
281 'nid' => $nid,
282 ))
283 ->fields(array('timestamp' => REQUEST_TIME))
284 ->execute();
285 }
286 }
287
288 /**
289 * Retrieves the timestamp at which the current user last viewed the
290 * specified node.
291 */
292 function node_last_viewed($nid) {
293 global $user;
294 static $history;
295
296 if (!isset($history[$nid])) {
297 $history[$nid] = db_query("SELECT timestamp FROM {history} WHERE uid = :uid AND nid = :nid", array(':uid' => $user->uid, ':nid' => $nid))->fetchObject();
298 }
299
300 return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp : 0);
301 }
302
303 /**
304 * Decide on the type of marker to be displayed for a given node.
305 *
306 * @param $nid
307 * Node ID whose history supplies the "last viewed" timestamp.
308 * @param $timestamp
309 * Time which is compared against node's "last viewed" timestamp.
310 * @return
311 * One of the MARK constants.
312 */
313 function node_mark($nid, $timestamp) {
314 global $user;
315 static $cache;
316
317 if (!$user->uid) {
318 return MARK_READ;
319 }
320 if (!isset($cache[$nid])) {
321 $cache[$nid] = node_last_viewed($nid);
322 }
323 if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) {
324 return MARK_NEW;
325 }
326 elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) {
327 return MARK_UPDATED;
328 }
329 return MARK_READ;
330 }
331
332 /**
333 * Extract the type name.
334 *
335 * @param $node
336 * Either a string or object, containing the node type information.
337 *
338 * @return
339 * Node type of the passed in data.
340 */
341 function _node_extract_type($node) {
342 return is_object($node) ? $node->type : $node;
343 }
344
345 /**
346 * Returns a list of all the available node types.
347 *
348 * @return
349 * An array of node types, keyed by the type.
350 * @see node_type_get_type()
351 */
352 function node_type_get_types() {
353 return _node_types_build()->types;
354 }
355
356 /**
357 * Returns the node type of the passed node or node type string.
358 *
359 *@param $node
360 * A node object or string that indicates the node type to return.
361 * @return
362 * A single node type, as an object or FALSE if the node type is not found.
363 * The node type is an array with following content:
364 *
365 * @code
366 * array(
367 * 'type' => 'Machine readable type name',
368 * 'name' => 'Name of the node type',
369 * 'base' => 'Indicates to which module this node type belongs',
370 * 'description' => 'Description of the node type',
371 * // ...
372 * )
373 * @endcode
374 */
375 function node_type_get_type($node) {
376 $type = _node_extract_type($node);
377 $types = _node_types_build()->types;
378 return isset($types[$type]) ? $types[$type] : FALSE;
379 }
380
381 /**
382 * Returns the node type base of the passed node or node type string.
383 *
384 * The base indicates which module implement this node type and is used to
385 * execute node type specific hooks.
386 *
387 * @see node_invoke()
388 *
389 * @param $node
390 * A node object or string that indicates the node type to return.
391 * @return
392 * The node type base or FALSE if the node type is not found.
393 */
394 function node_type_get_base($node) {
395 $type = _node_extract_type($node);
396 $types = _node_types_build()->types;
397 return isset($types[$type]) && isset($types[$type]->base) ? $types[$type]->base : FALSE;
398 }
399
400 /**
401 * Returns a list of available node names.
402 *
403 * @return
404 * An array of node type names, keyed by the type.
405 */
406 function node_type_get_names() {
407 return _node_types_build()->names;
408 }
409
410 /**
411 * Returns the node type name of the passed node or node type string.
412 *
413 * @param $node
414 * A node object or string that indicates the node type to return.
415 *
416 * @return
417 * The node type name or FALSE if the node type is not found.
418 */
419 function node_type_get_name($node) {
420 $type = _node_extract_type($node);
421 $types = _node_types_build()->names;
422 return isset($types[$type]) ? $types[$type] : FALSE;
423 }
424
425 /**
426 * Resets the database cache of node types.
427 *
428 * All new or non-modified module-defined node types are saved to the database.
429 */
430 function node_types_rebuild() {
431 // Reset and load updated node types.
432 drupal_static_reset('_node_types_build');
433 foreach (node_type_get_types() as $type => $info) {
434 if (!empty($info->is_new)) {
435 node_type_save($info);
436 }
437 if (!empty($info->disabled)) {
438 node_type_delete($info->type);
439 }
440 }
441 }
442
443 /**
444 * Menu argument loader; Load a node type by string.
445 *
446 * @param $name
447 * The machine-readable name of a node type to load; having '_' replaced with
448 * '-'.
449 *
450 * @return
451 * A node type object or FALSE if $name does not exist.
452 */
453 function node_type_load($name) {
454 return node_type_get_type(strtr($name, array('-' => '_')));
455 }
456
457 /**
458 * Saves a node type to the database.
459 *
460 * @param $info
461 * The node type to save, as an object.
462 *
463 * @return
464 * Status flag indicating outcome of the operation.
465 */
466 function node_type_save($info) {
467 $is_existing = FALSE;
468 $existing_type = !empty($info->old_type) ? $info->old_type : $info->type;
469 $is_existing = (bool) db_query_range('SELECT 1 FROM {node_type} WHERE type = :type', 0, 1, array(':type' => $existing_type))->fetchField();
470 $type = node_type_set_defaults($info);
471
472 $fields = array(
473 'type' => (string) $type->type,
474 'name' => (string) $type->name,
475 'base' => (string) $type->base,
476 'has_title' => (int) $type->has_title,
477 'title_label' => (string) $type->title_label,
478 'has_body' => (int) $type->has_body,
479 'body_label' => (string) $type->body_label,
480 'description' => (string) $type->description,
481 'help' => (string) $type->help,
482 'custom' => (int) $type->custom,
483 'modified' => (int) $type->modified,
484 'locked' => (int) $type->locked,
485 );
486
487 if ($is_existing) {
488 db_update('node_type')
489 ->fields($fields)
490 ->condition('type', $existing_type)
491 ->execute();
492
493 if (!empty($type->old_type) && $type->old_type != $type->type) {
494 field_attach_rename_bundle('node', $type->old_type, $type->type);
495 }
496 node_configure_fields($type);
497 module_invoke_all('node_type_update', $type);
498 $status = SAVED_UPDATED;
499 }
500 else {
501 $fields['orig_type'] = (string) $type->orig_type;
502 db_insert('node_type')
503 ->fields($fields)
504 ->execute();
505
506 field_attach_create_bundle('node', $type->type);
507 node_configure_fields($type);
508 module_invoke_all('node_type_insert', $type);
509 $status = SAVED_NEW;
510 }
511
512 // Clear the node type cache.
513 drupal_static_reset('_node_types_build');
514
515 return $status;
516 }
517
518 /**
519 * Manage the field(s) for a node type.
520 *
521 * Currently, the node module manages a single Field API field,
522 * 'body'. If $type->has_body is true, this function ensures the
523 * 'body' field exists and creates an instance of it for the bundle
524 * $type->type (e.g. 'page', 'story', ...). If $type->has_body is
525 * false, this function removes the instance (if it exists) for the
526 * 'body' field on $type->type.
527 */
528 function node_configure_fields($type) {
529 // Add or remove the body field, as needed.
530 $field = field_info_field('body');
531 $instance = field_info_instance('node', 'body', $type->type);
532 if ($type->has_body) {
533 if (empty($field)) {
534 $field = array(
535 'field_name' => 'body',
536 'type' => 'text_with_summary',
537 'translatable' => TRUE,
538 );
539 $field = field_create_field($field);
540 }
541 if (empty($instance)) {
542 $instance = array(
543 'field_name' => 'body',
544 'object_type' => 'node',
545 'bundle' => $type->type,
546 'label' => $type->body_label,
547 'widget_type' => 'text_textarea_with_summary',
548 'settings' => array('display_summary' => TRUE),
549
550 // Define default formatters for the teaser and full view.
551 'display' => array(
552 'full' => array(
553 'label' => 'hidden',
554 'type' => 'text_default',
555 ),
556 'teaser' => array(
557 'label' => 'hidden',
558 'type' => 'text_summary_or_trimmed',
559 ),
560 ),
561 );
562 field_create_instance($instance);
563 }
564 else {
565 $instance['label'] = $type->body_label;
566 $instance['settings']['display_summary'] = TRUE;
567 field_update_instance($instance);
568 }
569 }
570 elseif (!empty($instance)) {
571 field_delete_instance($instance);
572 }
573
574 if ($type->has_title) {
575 // Add the title field if not present.
576 $field = field_info_field('title');
577 $instance = field_info_instance('node', 'title', $type->type);
578
579 if (empty($field)) {
580 $field = array(
581 'field_name' => 'title',
582 'type' => 'text',
583 );
584 $field = field_create_field($field);
585 }
586 if (empty($instance)) {
587 $weight = -5;
588 $instance = array(
589 'field_name' => 'title',
590 'object_type' => 'node',
591 'bundle' => $type->type,
592 'label' => $type->title_label,
593 'widget_type' => 'text',
594 'widget' => array(
595 'weight' => $weight,
596 ),
597 'required' => TRUE,
598 'locked' => TRUE,
599 'display' => array(
600 'full' => array(
601 'label' => 'hidden',
602 'type' => 'text_default',
603 'weight' => $weight,
604 ),
605 'teaser' => array(
606 'label' => 'hidden',
607 'type' => 'text_default',
608 'weight' => $weight,
609 ),
610 ),
611 );
612 field_create_instance($instance);
613 }
614 else {
615 $instance['label'] = $type->title_label;
616 field_update_instance($instance);
617 }
618 }
619 }
620
621 /**
622 * Deletes a node type from the database.
623 *
624 * @param $type
625 * The machine-readable name of the node type to be deleted.
626 */
627 function node_type_delete($type) {
628 $info = node_type_get_type($type);
629 db_delete('node_type')
630 ->condition('type', $type)
631 ->execute();
632 module_invoke_all('node_type_delete', $info);
633
634 // Clear the node type cache.
635 drupal_static_reset('_node_types_build');
636 }
637
638 /**
639 * Updates all nodes of one type to be of another type.
640 *
641 * @param $old_type
642 * The current node type of the nodes.
643 * @param $type
644 * The new node type of the nodes.
645 *
646 * @return
647 * The number of nodes whose node type field was modified.
648 */
649 function node_type_update_nodes($old_type, $type) {
650 return db_update('node')
651 ->fields(array('type' => $type))
652 ->condition('type', $old_type)
653 ->execute();
654 }
655
656 /**
657 * Builds and returns the list of available node types.
658 *
659 * The list of types is built by querying hook_node_info() in all modules, and
660 * by comparing this information with the node types in the {node_type} table.
661 *
662 */
663 function _node_types_build() {
664 $_node_types = &drupal_static(__FUNCTION__);
665 if (is_object($_node_types)) {
666 return $_node_types;
667 }
668 $_node_types = (object)array('types' => array(), 'names' => array());
669
670 $info_array = module_invoke_all('node_info');
671 foreach ($info_array as $type => $info) {
672 $info['type'] = $type;
673 $_node_types->types[$type] = node_type_set_defaults($info);
674 $_node_types->names[$type] = $info['name'];
675 }
676 $type_result = db_select('node_type', 'nt')
677 ->addTag('translatable')
678 ->addTag('node_type_access')
679 ->fields('nt')
680 ->orderBy('nt.type', 'ASC')
681 ->execute();
682 foreach ($type_result as $type_object) {
683 // Check for node types from disabled modules and mark their types for removal.
684 // Types defined by the node module in the database (rather than by a separate
685 // module using hook_node_info) have a base value of 'node_content'. The isset()
686 // check prevents errors on old (pre-Drupal 7) databases.
687 if (isset($type_object->base) && $type_object->base != 'node_content' && empty($info_array[$type_object->type])) {
688 $type_object->disabled = TRUE;
689 }
690 if (!isset($_node_types->types[$type_object->type]) || $type_object->modified) {
691 $_node_types->types[$type_object->type] = $type_object;
692 $_node_types->names[$type_object->type] = $type_object->name;
693
694 if ($type_object->type != $type_object->orig_type) {
695 unset($_node_types->types[$type_object->orig_type]);
696 unset($_node_types->names[$type_object->orig_type]);
697 }
698 }
699 }
700
701 asort($_node_types->names);
702
703 return $_node_types;
704 }
705
706 /**
707 * Set the default values for a node type.
708 *
709 * The defaults are for a type defined through hook_node_info().
710 * When populating a custom node type $info should have the 'custom'
711 * key set to 1.
712 *
713 * @param $info
714 * An object or array containing values to override the defaults.
715 *
716 * @return
717 * A node type object.
718 */
719 function node_type_set_defaults($info = array()) {
720 static $type;
721
722 if (!isset($type)) {
723 $type = new stdClass();
724 $type->type = '';
725 $type->name = '';
726 $type->base = '';
727 $type->description = '';
728 $type->help = '';
729 $type->has_title = 1;
730 $type->has_body = 1;
731 $type->title_label = t('Title');
732 $type->body_label = t('Body');
733 $type->custom = 0;
734 $type->modified = 0;
735 $type->locked = 1;
736 $type->is_new = 1;
737 }
738
739 $new_type = clone $type;
740 $info = (array) $info;
741 foreach ($info as $key => $data) {
742 $new_type->$key = $data;
743 }
744 // If the type has no title or body, set an empty label.
745 if (!$new_type->has_title) {
746 $new_type->title_label = '';
747 }
748 if (!$new_type->has_body) {
749 $new_type->body_label = '';
750 }
751 $new_type->orig_type = isset($info['type']) ? $info['type'] : '';
752
753 return $new_type;
754 }
755
756 /**
757 * Implements hook_rdf_mapping().
758 */
759 function node_rdf_mapping() {
760 return array(
761 array(
762 'type' => 'node',
763 'bundle' => RDF_DEFAULT_BUNDLE,
764 'mapping' => array(
765 'rdftype' => array('sioc:Item', 'foaf:Document'),
766 'title' => array(
767 'predicates' => array('dc:title'),
768 ),
769 'created' => array(
770 'predicates' => array('dc:date', 'dc:created'),
771 'datatype' => 'xsd:dateTime',
772 'callback' => 'date_iso8601',
773 ),
774 'changed' => array(
775 'predicates' => array('dc:modified'),
776 ),
777 'body' => array(
778 'predicates' => array('content:encoded'),
779 ),
780 'uid' => array(
781 'predicates' => array('sioc:has_creator'),
782 ),
783 'name' => array(
784 'predicates' => array('foaf:name'),
785 ),
786 ),
787 ),
788 );
789 }
790
791 /**
792 * Determine whether a node hook exists.
793 *
794 * @param $node
795 * A node object or a string containing the node type.
796 * @param $hook
797 * A string containing the name of the hook.
798 * @return
799 * TRUE if the $hook exists in the node type of $node.
800 */
801 function node_hook($node, $hook) {
802 $base = node_type_get_base($node);
803 return module_hook($base, $hook);
804 }
805
806 /**
807 * Invoke a node hook.
808 *
809 * @param $node
810 * A node object or a string containing the node type.
811 * @param $hook
812 * A string containing the name of the hook.
813 * @param $a2, $a3, $a4
814 * Arguments to pass on to the hook, after the $node argument.
815 * @return
816 * The returned value of the invoked hook.
817 */
818 function node_invoke($node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
819 if (node_hook($node, $hook)) {
820 $base = node_type_get_base($node);
821 $function = $base . '_' . $hook;
822 return ($function($node, $a2, $a3, $a4));
823 }
824 }
825
826 /**
827 * Load node entities from the database.
828 *
829 * This function should be used whenever you need to load more than one node
830 * from the database. Nodes are loaded into memory and will not require
831 * database access if loaded again during the same page request.
832 *
833 * @see entity_load()
834 *
835 * @param $nids
836 * An array of node IDs.
837 * @param $conditions
838 * An array of conditions on the {node} table in the form 'field' => $value.
839 * @param $reset
840 * Whether to reset the internal node_load cache.
841 *
842 * @return
843 * An array of node objects indexed by nid.
844 */
845 function node_load_multiple($nids = array(), $conditions = array(), $reset = FALSE) {
846 return entity_load('node', $nids, $conditions, $reset);
847 }
848
849 /**
850 * Load a node object from the database.
851 *
852 * @param $nid
853 * The node ID.
854 * @param $vid
855 * The revision ID.
856 * @param $reset
857 * Whether to reset the node_load_multiple cache.
858 *
859 * @return
860 * A fully-populated node object.
861 */
862 function node_load($nid = NULL, $vid = NULL, $reset = FALSE) {
863 $nids = (isset($nid) ? array($nid) : array());
864 $conditions = (isset($vid) ? array('vid' => $vid) : array());
865 $node = node_load_multiple($nids, $conditions, $reset);
866 return $node ? reset($node) : FALSE;
867 }
868
869 /**
870 * Perform validation checks on the given node.
871 */
872 function node_validate(stdClass $node, $form = array()) {
873 $type = node_type_get_type($node);
874
875 if (isset($node->nid) && (node_last_changed($node->nid) > $node->changed)) {
876 form_set_error('changed', t('The content on this page has either been modified by another user, or you have already submitted modifications using this form. As a result, your changes cannot be saved.'));
877 }
878
879 if (user_access('administer nodes')) {
880 // Validate the "authored by" field.
881 if (!empty($node->name) && !($account = user_load_by_name($node->name))) {
882 // The use of empty() is mandatory in the context of usernames
883 // as the empty string denotes the anonymous user. In case we
884 // are dealing with an anonymous user we set the user ID to 0.
885 form_set_error('name', t('The username %name does not exist.', array('%name' => $node->name)));
886 }
887
888 // Validate the "authored on" field.
889 if (!empty($node->date) && strtotime($node->date) === FALSE) {
890 form_set_error('date', t('You have to specify a valid date.'));
891 }
892 }
893
894 // Do node-type-specific validation checks.
895 node_invoke($node, 'validate', $form);
896 module_invoke_all('node_validate', $node, $form);
897 }
898
899 /**
900 * Prepare node for save and allow modules to make changes.
901 */
902 function node_submit($node) {
903 global $user;
904
905 if (user_access('administer nodes')) {
906 // Populate the "authored by" field.
907 if ($account = user_load_by_name($node->name)) {
908 $node->uid = $account->uid;
909 }
910 else {
911 $node->uid = 0;
912 }
913 }
914 $node->created = !empty($node->date) ? strtotime($node->date) : REQUEST_TIME;
915 $node->validated = TRUE;
916
917 return $node;
918 }
919
920 /**
921 * Save changes to a node or add a new node.
922 *
923 * @param $node
924 * The $node object to be saved. If $node->nid is
925 * omitted (or $node->is_new is TRUE), a new node will be added.
926 */
927 function node_save(stdClass $node) {
928 field_attach_presave('node', $node);
929 // Let modules modify the node before it is saved to the database.
930 module_invoke_all('node_presave', $node);
931 global $user;
932
933 if (!isset($node->is_new)) {
934 $node->is_new = empty($node->nid);
935 }
936
937 // Apply filters to some default node fields:
938 if ($node->is_new) {
939 // Insert a new node.
940 $node->is_new = TRUE;
941
942 // When inserting a node, $node->log must be set because
943 // {node_revision}.log does not (and cannot) have a default
944 // value. If the user does not have permission to create
945 // revisions, however, the form will not contain an element for
946 // log so $node->log will be unset at this point.
947 if (!isset($node->log)) {
948 $node->log = '';
949 }
950 }
951 elseif (!empty($node->revision)) {
952 $node->old_vid = $node->vid;
953 unset($node->vid);
954 }
955 else {
956 // When updating a node, avoid clobbering an existing log entry with an empty one.
957 if (empty($node->log)) {
958 unset($node->log);
959 }
960 }
961
962 // Set some required fields:
963 if (empty($node->created)) {
964 $node->created = REQUEST_TIME;
965 }
966 // The changed timestamp is always updated for bookkeeping purposes (revisions, searching, ...)
967 $node->changed = REQUEST_TIME;
968
969 $node->timestamp = REQUEST_TIME;
970 $update_node = TRUE;
971
972 // When converting the title property to fields we preserved the {node}.title
973 // db column for performance, setting the default language value into this
974 // column. After this we restore the field data structure to the previous node
975 // title field.
976 $title_field = $node->title;
977 $langcode = FIELD_LANGUAGE_NONE;
978 $node->title = $title_field[$langcode][0]['value'];
979
980 // Generate the node table query and the node_revisions table query.
981 if ($node->is_new) {
982 drupal_write_record('node', $node);
983 _node_save_revision($node, $user->uid);
984 $op = 'insert';
985 }
986 else {
987 drupal_write_record('node', $node, 'nid');
988 if (!empty($node->revision)) {
989 _node_save_revision($node, $user->uid);
990 }
991 else {
992 _node_save_revision($node, $user->uid, 'vid');
993 $update_node = FALSE;
994 }
995 $op = 'update';
996 }
997 if ($update_node) {
998 db_update('node')
999 ->fields(array('vid' => $node->vid))
1000 ->condition('nid', $node->nid)
1001 ->execute();
1002 }
1003
1004 // Restore the title field data structure after db storage.
1005 $node->title = $title_field;
1006
1007 // Call the node specific callback (if any). This can be
1008 // node_invoke($node, 'insert') or
1009 // node_invoke($node, 'update').
1010 node_invoke($node, $op);
1011
1012 // Save fields.
1013 $function = "field_attach_$op";
1014 $function('node', $node);
1015
1016 module_invoke_all('node_' . $op, $node);
1017
1018 // Update the node access table for this node.
1019 node_access_acquire_grants($node);
1020
1021 // Clear internal properties.
1022 unset($node->is_new);
1023
1024 // Clear the page and block caches.
1025 cache_clear_all();
1026
1027 // Ignore slave server temporarily to give time for the
1028 // saved node to be propagated to the slave.
1029 db_ignore_slave();
1030 }
1031
1032 /**
1033 * Helper function to save a revision with the uid of the current user.
1034 *
1035 * Node is taken by reference, because drupal_write_record() updates the
1036 * $node with the revision id, and we need to pass that back to the caller.
1037 */
1038 function _node_save_revision(stdClass $node, $uid, $update = NULL) {
1039 $temp_uid = $node->uid;
1040 $node->uid = $uid;
1041 if (isset($update)) {
1042 drupal_write_record('node_revision', $node, $update);
1043 }
1044 else {
1045 drupal_write_record('node_revision', $node);
1046 }
1047 $node->uid = $temp_uid;
1048 }
1049
1050 /**
1051 * Delete a node.
1052 *
1053 * @param $nid
1054 * A node ID.
1055 */
1056 function node_delete($nid) {
1057 node_delete_multiple(array($nid));
1058 }
1059
1060 /**
1061 * Delete multiple nodes.
1062 *
1063 * @param $nids
1064 * An array of node IDs.
1065 */
1066 function node_delete_multiple($nids) {
1067 if (!empty($nids)) {
1068 $nodes = node_load_multiple($nids, array());
1069
1070 db_delete('node')
1071 ->condition('nid', $nids, 'IN')
1072 ->execute();
1073 db_delete('node_revision')
1074 ->condition('nid', $nids, 'IN')
1075 ->execute();
1076 db_delete('history')
1077 ->condition('nid', $nids, 'IN')
1078 ->execute();
1079
1080 foreach ($nodes as $nid => $node) {
1081 // Call the node-specific callback (if any):
1082 node_invoke($node, 'delete');
1083 module_invoke_all('node_delete', $node);
1084 field_attach_delete('node', $node);
1085
1086 // Remove this node from the search index if needed.
1087 // This code is implemented in node module rather than in search module,
1088 // because node module is implementing search module's API, not the other
1089 // way around.
1090 if (module_exists('search')) {
1091 search_reindex($nid, 'node');
1092 }
1093 }
1094
1095 // Clear the page and block and node_load_multiple caches.
1096 cache_clear_all();
1097 entity_get_controller('node')->resetCache();
1098 }
1099 }
1100
1101 /**
1102 * Delete a node revision.
1103 *
1104 * @param $revision_id
1105 * The revision ID to delete.
1106 */
1107 function node_revision_delete($revision_id) {
1108 if ($revision = node_load(NULL, $revision_id)) {
1109 // Prevent deleting the current revision.
1110 $node = node_load($revision->nid);
1111 if ($revision_id == $node->vid) {
1112 return FALSE;
1113 }
1114
1115 db_delete('node_revision')
1116 ->condition('nid', $revision->nid)
1117 ->condition('vid', $revision->vid)
1118 ->execute();
1119 module_invoke_all('node_revision_delete', $revision);
1120 field_attach_delete_revision('node', $revision);
1121 return TRUE;
1122 }
1123 return FALSE;
1124 }
1125
1126 /**
1127 * Generate an array for rendering the given node.
1128 *
1129 * @param $node
1130 * A node object.
1131 * @param $build_mode
1132 * Build mode, e.g. 'full', 'teaser'...
1133 *
1134 * @return
1135 * An array as expected by drupal_render().
1136 */
1137 function node_build($node, $build_mode = 'full') {
1138 // Populate $node->content with a render() array.
1139 node_build_content($node, $build_mode);
1140
1141 $build = $node->content;
1142 // We don't need duplicate rendering info in node->content.
1143 unset($node->content);
1144
1145 $build += array(
1146 '#theme' => 'node',
1147 '#node' => $node,
1148 '#build_mode' => $build_mode,
1149 );
1150 // Add contextual links for this node.
1151 $build['#contextual_links']['node'] = menu_contextual_links('node', array($node->nid));
1152
1153 return $build;
1154 }
1155
1156 /**
1157 * Builds a structured array representing the node's content.
1158 *
1159 * The content built for the node (field values, comments, file attachments or
1160 * other node components) will vary depending on the $build_mode parameter.
1161 *
1162 * Drupal core defines the following build modes for nodes, with the following
1163 * default use cases:
1164 * - full (default): node is being displayed on its own page (node/123)
1165 * - teaser: node is being displayed on the default home page listing, on
1166 * taxonomy listing pages, or on blog listing pages.
1167 * - rss: node displayed in an RSS feed.
1168 * If search.module is enabled:
1169 * - search_index: node is being indexed for search.
1170 * - search_result: node is being displayed as a search result.
1171 * If book.module is enabled:
1172 * - print: node is being displayed in print-friendly mode.
1173 * Contributed modules might define additional build modes, or use existing
1174 * build modes in additional contexts.
1175 *
1176 * @param $node
1177 * A node object.
1178 * @param $build_mode
1179 * Build mode, e.g. 'full', 'teaser'...
1180 *
1181 */
1182 function node_build_content(stdClass $node, $build_mode = 'full') {
1183 // Remove previously built content, if exists.
1184 $node->content = array();
1185
1186 // The 'view' hook can be implemented to overwrite the default function
1187 // to display nodes.
1188 if (node_hook($node, 'view')) {
1189 $node = node_invoke($node, 'view', $build_mode);
1190 }
1191
1192 // Build fields content.
1193 $node->content += field_attach_view('node', $node, $build_mode);
1194
1195 // Always display a read more link on teasers because we have no way
1196 // to know when a teaser view is different than a full view.
1197 $links = array();
1198 if ($build_mode == 'teaser') {
1199 $links['node_readmore'] = array(
1200 'title' => t('Read more'),
1201 'href' => 'node/' . $node->nid,
1202 'attributes' => array('rel' => 'tag', 'title' => strip_tags($node->title[FIELD_LANGUAGE_NONE][0]['value']))
1203 );
1204 }
1205 $node->content['links']['node'] = array(
1206 '#theme' => 'links',
1207 '#links' => $links,
1208 '#attributes' => array('class' => array('links', 'inline')),
1209 );
1210
1211 // Allow modules to make their own additions to the node.
1212 module_invoke_all('node_view', $node, $build_mode);
1213
1214 // Allow modules to modify the structured node.
1215 drupal_alter('node_build', $node, $build_mode);
1216 }
1217
1218 /**
1219 * Implement hook_language_negotiation_info().
1220 */
1221 function node_language_negotiation_info() {
1222 $providers = array();
1223
1224 $providers['node-language'] = array(
1225 'types' => array(LANGUAGE_TYPE_CONTENT),
1226 'callbacks' => array('language' => 'node_language_provider'),
1227 'file' => drupal_get_path('module', 'node') . '/node.module',
1228 'name' => t('Node'),
1229 'description' => t('The current node language is used.'),
1230 );
1231
1232 return $providers;
1233 }
1234
1235 /**
1236 * Return the language of the current node.
1237 *
1238 * @param $languages
1239 * An array of valid language objects.
1240 *
1241 * @return
1242 * A valid language code on succes, FALSE otherwise.
1243 */
1244 function node_language_provider($languages) {
1245 require_once DRUPAL_ROOT . '/includes/path.inc';
1246
1247 $path = isset($_GET['q']) ? $_GET['q'] : '';
1248 list($language, $path) = language_url_split_prefix($path, $languages);
1249 $language = $language ? $language : language_default();
1250 $path = drupal_get_normal_path($path, $language->language);
1251
1252 // We cannot use args now.
1253 $path = explode('/', $path);
1254 // Act only if we are in a node page.
1255 if (isset($path[0]) && isset($path[1]) && $path[0] == 'node' && $nid = intval($path[1])) {
1256 // We cannot perform a node load here.
1257 $result = db_query('SELECT n.language FROM {node} n WHERE n.nid = :nid', array(':nid' => $nid))->fetchAssoc();
1258 return $result['language'];
1259 }
1260
1261 return FALSE;
1262 }
1263
1264 /**
1265 * Generate an array which displays a node detail page.
1266 *
1267 * @param $node
1268 * A node object.
1269 * @param $message
1270 * A flag which sets a page title relevant to the revision being viewed.
1271 * @return
1272 * A $page element suitable for use by drupal_page_render().
1273 */
1274 function node_show(stdClass $node, $message = FALSE) {
1275 if ($message) {
1276 drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title[FIELD_LANGUAGE_NONE][0]['value'], '%date' => format_date($node->revision_timestamp))), PASS_THROUGH);
1277 }
1278
1279 // Update the history tab