Simpletest Coverage - modules/node/node.module

1 <?php
2 // $Id: node.module,v 1.1099 2009/08/14 13:53:01 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 * Nodes changed before this time are always marked as read.
12 *
13 * Nodes changed after this time may be marked new, updated, or read, depending
14 * on their state for the current user. Defaults to 30 days ago.
15 */
16 define('NODE_NEW_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60);
17
18 /**
19 * Implement hook_help().
20 */
21 function node_help($path, $arg) {
22 // Remind site administrators about the {node_access} table being flagged
23 // for rebuild. We don't need to issue the message on the confirm form, or
24 // while the rebuild is being processed.
25 if ($path != 'admin/reports/status/rebuild' && $path != 'batch' && strpos($path, '#') === FALSE
26 && user_access('access administration pages') && node_access_needs_rebuild()) {
27 if ($path == 'admin/reports/status') {
28 $message = t('The content access permissions need to be rebuilt.');
29 }
30 else {
31 $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')));
32 }
33 drupal_set_message($message, 'error');
34 }
35
36 switch ($path) {
37 case 'admin/help#node':
38 $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>';
39 $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>';
40 $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/settings/permissions'))) . '</p>';
41 $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>';
42 return $output;
43 case 'admin/content':
44 return ' '; // Return a non-null value so that the 'more help' link is shown.
45 case 'admin/structure/types/add':
46 return '<p>' . t('Each piece of content is of a specific content type. Each <em>content type</em> can have different fields, behaviors, and permissions assigned to it.') . '</p>';
47 case 'node/%/revisions':
48 return '<p>' . t('The revisions let you track differences between multiple versions of a post.') . '</p>';
49 case 'node/%/edit':
50 $node = node_load($arg[1]);
51 $type = node_type_get_type($node);
52 return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) . '</p>' : '');
53 }
54
55 if ($arg[0] == 'node' && $arg[1] == 'add' && $arg[2]) {
56 $type = node_type_get_type(str_replace('-', '_', $arg[2]));
57 return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) . '</p>' : '');
58 }
59 }
60
61 /**
62 * Implement hook_theme().
63 */
64 function node_theme() {
65 return array(
66 'node' => array(
67 'arguments' => array('elements' => NULL),
68 'template' => 'node',
69 ),
70 'node_list' => array(
71 'arguments' => array('items' => NULL, 'title' => NULL),
72 ),
73 'node_search_admin' => array(
74 'arguments' => array('form' => NULL),
75 ),
76 'node_filter_form' => array(
77 'arguments' => array('form' => NULL),
78 'file' => 'node.admin.inc',
79 ),
80 'node_filters' => array(
81 'arguments' => array('form' => NULL),
82 'file' => 'node.admin.inc',
83 ),
84 'node_admin_nodes' => array(
85 'arguments' => array('form' => NULL),
86 'file' => 'node.admin.inc',
87 ),
88 'node_add_list' => array(
89 'arguments' => array('content' => NULL),
90 'file' => 'node.pages.inc',
91 ),
92 'node_form' => array(
93 'arguments' => array('form' => NULL),
94 'file' => 'node.pages.inc',
95 ),
96 'node_preview' => array(
97 'arguments' => array('node' => NULL),
98 'file' => 'node.pages.inc',
99 ),
100 'node_log_message' => array(
101 'arguments' => array('log' => NULL),
102 ),
103 'node_submitted' => array(
104 'arguments' => array('node' => NULL),
105 ),
106 'node_admin_overview' => array(
107 'arguments' => array('name' => NULL, 'type' => NULL),
108 ),
109 );
110 }
111
112 /**
113 * Implement hook_cron().
114 */
115 function node_cron() {
116 db_delete('history')
117 ->condition('timestamp', NODE_NEW_LIMIT, '<')
118 ->execute();
119 }
120
121 /**
122 * Implement hook_fieldable_info().
123 */
124 function node_fieldable_info() {
125 $return = array(
126 'node' => array(
127 'label' => t('Node'),
128 'object keys' => array(
129 'id' => 'nid',
130 'revision' => 'vid',
131 'bundle' => 'type',
132 ),
133 // Node.module handles its own caching.
134 // 'cacheable' => FALSE,
135 'bundles' => array(),
136 ),
137 );
138 // Bundles must provide a human readable name so we can create help and error
139 // messages, and the path to attach Field admin pages to.
140 foreach (node_type_get_names() as $type => $name) {
141 $return['node']['bundles'][$type] = array(
142 'label' => $name,
143 'admin' => array(
144 'path' => 'admin/structure/node-type/' . str_replace('_', '-', $type),
145 'access arguments' => array('administer content types'),
146 ),
147 );
148 }
149 return $return;
150 }
151
152
153 /**
154 * Implement hook_field_build_modes().
155 */
156 function node_field_build_modes($obj_type) {
157 $modes = array();
158 if ($obj_type == 'node') {
159 $modes = array(
160 'teaser' => t('Teaser'),
161 'full' => t('Full node'),
162 'rss' => t('RSS'),
163 );
164 // Search integration is provided by node.module, so search-related
165 // build-modes for nodes are defined here and not in search.module.
166 if (module_exists('search')) {
167 $modes += array(
168 'search_index' => t('Search Index'),
169 'search_result' => t('Search Result'),
170 );
171 }
172 }
173 return $modes;
174 }
175
176 /**
177 * Gather a listing of links to nodes.
178 *
179 * @param $result
180 * A DB result object from a query to fetch node objects. If your query
181 * joins the <code>node_comment_statistics</code> table so that the
182 * <code>comment_count</code> field is available, a title attribute will
183 * be added to show the number of comments.
184 * @param $title
185 * A heading for the resulting list.
186 *
187 * @return
188 * An HTML list suitable as content for a block, or FALSE if no result can
189 * fetch from DB result object.
190 */
191 function node_title_list($result, $title = NULL) {
192 $items = array();
193 $num_rows = FALSE;
194 foreach ($result as $node) {
195 $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());
196 $num_rows = TRUE;
197 }
198
199 return $num_rows ? theme('node_list', $items, $title) : FALSE;
200 }
201
202 /**
203 * Format a listing of links to nodes.
204 *
205 * @ingroup themeable
206 */
207 function theme_node_list($items, $title = NULL) {
208 return theme('item_list', $items, $title);
209 }
210
211 /**
212 * Update the 'last viewed' timestamp of the specified node for current user.
213 */
214 function node_tag_new($nid) {
215 global $user;
216
217 if ($user->uid) {
218 db_merge('history')
219 ->key(array(
220 'uid' => $user->uid,
221 'nid' => $nid,
222 ))
223 ->fields(array('timestamp' => REQUEST_TIME))
224 ->execute();
225 }
226 }
227
228 /**
229 * Retrieves the timestamp at which the current user last viewed the
230 * specified node.
231 */
232 function node_last_viewed($nid) {
233 global $user;
234 static $history;
235
236 if (!isset($history[$nid])) {
237 $history[$nid] = db_query("SELECT timestamp FROM {history} WHERE uid = :uid AND nid = :nid", array(':uid' => $user->uid, ':nid' => $nid))->fetchObject();
238 }
239
240 return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp : 0);
241 }
242
243 /**
244 * Decide on the type of marker to be displayed for a given node.
245 *
246 * @param $nid
247 * Node ID whose history supplies the "last viewed" timestamp.
248 * @param $timestamp
249 * Time which is compared against node's "last viewed" timestamp.
250 * @return
251 * One of the MARK constants.
252 */
253 function node_mark($nid, $timestamp) {
254 global $user;
255 static $cache;
256
257 if (!$user->uid) {
258 return MARK_READ;
259 }
260 if (!isset($cache[$nid])) {
261 $cache[$nid] = node_last_viewed($nid);
262 }
263 if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) {
264 return MARK_NEW;
265 }
266 elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) {
267 return MARK_UPDATED;
268 }
269 return MARK_READ;
270 }
271
272 /**
273 * Extract the type name.
274 *
275 * @param $node
276 * Either a string or object, containing the node type information.
277 *
278 * @return
279 * Node type of the passed in data.
280 */
281 function _node_extract_type($node) {
282 return is_object($node) ? $node->type : $node;
283 }
284
285 /**
286 * Clear the statically cached node type information.
287 */
288 function node_type_clear() {
289 drupal_static_reset('_node_types_build');
290 }
291
292 /**
293 * Returns a list of all the available node types.
294 *
295 * @return
296 * An array of node types, keyed by the type.
297 * @see node_type_get_type()
298 */
299 function node_type_get_types() {
300 return _node_types_build()->types;
301 }
302
303 /**
304 * Returns the node type of the passed node or node type string.
305 *
306 *@param $node
307 * A node object or string that indicates the node type to return.
308 * @return
309 * A single node type, as an object or FALSE if the node type is not found.
310 * The node type is an array with following content:
311 *
312 * @code
313 * array(
314 * 'type' => 'Machine readable type name',
315 * 'name' => 'Name of the node type',
316 * 'base' => 'Indicates to which module this node type belongs',
317 * 'description' => 'Description of the node type',
318 * // ...
319 * )
320 * @endcode
321 */
322 function node_type_get_type($node) {
323 $type = _node_extract_type($node);
324 $types = _node_types_build()->types;
325 return isset($types[$type]) ? $types[$type] : FALSE;
326 }
327
328 /**
329 * Returns the node type base of the passed node or node type string.
330 *
331 * The base indicates which module implement this node type and is used to
332 * execute node type specific hooks.
333 *
334 * @see node_invoke()
335 *
336 * @param $node
337 * A node object or string that indicates the node type to return.
338 * @return
339 * The node type base or FALSE if the node type is not found.
340 */
341 function node_type_get_base($node) {
342 $type = _node_extract_type($node);
343 $types = _node_types_build()->types;
344 return isset($types[$type]) && isset($types[$type]->base) ? $types[$type]->base : FALSE;
345 }
346
347 /**
348 * Returns a list of available node names.
349 *
350 * @return
351 * An array of node type names, keyed by the type.
352 */
353 function node_type_get_names() {
354 return _node_types_build()->names;
355 }
356
357 /**
358 * Returns the node type name of the passed node or node type string.
359 *
360 * @param $node
361 * A node object or string that indicates the node type to return.
362 *
363 * @return
364 * The node type name or FALSE if the node type is not found.
365 */
366 function node_type_get_name($node) {
367 $type = _node_extract_type($node);
368 $types = _node_types_build()->names;
369 return isset($types[$type]) ? $types[$type] : FALSE;
370 }
371
372 /**
373 * Resets the database cache of node types, and saves all new or non-modified
374 * module-defined node types to the database.
375 */
376 function node_types_rebuild() {
377 // Reset and load updated node types.
378 node_type_clear();
379 foreach (node_type_get_types() as $type => $info) {
380 if (!empty($info->is_new)) {
381 node_type_save($info);
382 }
383 if (!empty($info->disabled)) {
384 node_type_delete($info->type);
385 }
386 }
387 // Reset cached node type information so that the next access
388 // will use the updated data.
389 node_type_clear();
390 // This is required for proper menu items at node/add/type.
391 menu_rebuild();
392 }
393
394 /**
395 * Saves a node type to the database.
396 *
397 * @param $info
398 * The node type to save, as an object.
399 *
400 * @return
401 * Status flag indicating outcome of the operation.
402 */
403 function node_type_save($info) {
404 $is_existing = FALSE;
405 $existing_type = !empty($info->old_type) ? $info->old_type : $info->type;
406 $is_existing = (bool) db_query_range('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => $existing_type), 0, 1)->fetchField();
407 $type = node_type_set_defaults($info);
408
409 $fields = array(
410 'type' => (string) $type->type,
411 'name' => (string) $type->name,
412 'base' => (string) $type->base,
413 'has_title' => (int) $type->has_title,
414 'title_label' => (string) $type->title_label,
415 'has_body' => (int) $type->has_body,
416 'body_label' => (string) $type->body_label,
417 'description' => (string) $type->description,
418 'help' => (string) $type->help,
419 'custom' => (int) $type->custom,
420 'modified' => (int) $type->modified,
421 'locked' => (int) $type->locked,
422 );
423
424 if ($is_existing) {
425 db_update('node_type')
426 ->fields($fields)
427 ->condition('type', $existing_type)
428 ->execute();
429
430 if (!empty($type->old_type) && $type->old_type != $type->type) {
431 field_attach_rename_bundle($type->old_type, $type->type);
432 }
433 node_configure_fields($type);
434 module_invoke_all('node_type_update', $type);
435 return SAVED_UPDATED;
436 }
437 else {
438 $fields['orig_type'] = (string) $type->orig_type;
439 db_insert('node_type')
440 ->fields($fields)
441 ->execute();
442
443 field_attach_create_bundle($type->type);
444 node_configure_fields($type);
445 module_invoke_all('node_type_insert', $type);
446 return SAVED_NEW;
447 }
448 }
449
450 /**
451 * Manage the field(s) for a node type.
452 *
453 * Currently, the node module manages a single Field API field,
454 * 'body'. If $type->has_body is true, this function ensures the
455 * 'body' field exists and creates an instance of it for the bundle
456 * $type->type (e.g. 'page', 'story', ...). If $type->has_body is
457 * false, this function removes the instance (if it exists) for the
458 * 'body' field on $type->type.
459 */
460 function node_configure_fields($type) {
461 // Add or remove the body field, as needed.
462 $field = field_info_field('body');
463 $instance = field_info_instance('body', $type->type);
464 if ($type->has_body) {
465 if (empty($field)) {
466 $field = array(
467 'field_name' => 'body',
468 'type' => 'text_with_summary',
469 );
470 $field = field_create_field($field);
471 }
472 if (empty($instance)) {
473 $instance = array(
474 'field_name' => 'body',
475 'bundle' => $type->type,
476 'label' => $type->body_label,
477 'widget_type' => 'text_textarea_with_summary',
478 'settings' => array('display_summary' => TRUE),
479
480 // With no UI in core, we have to define default
481 // formatters for the teaser and full view.
482 // This may change if the method of handling displays
483 // is changed or if a UI gets into core.
484 'display' => array(
485 'full' => array(
486 'label' => 'hidden',
487 'type' => 'text_default',
488 ),
489 'teaser' => array(
490 'label' => 'hidden',
491 'type' => 'text_summary_or_trimmed',
492 ),
493 ),
494 );
495 field_create_instance($instance);
496 }
497 else {
498 $instance['label'] = $type->body_label;
499 $instance['settings']['display_summary'] = TRUE;
500 field_update_instance($instance);
501 }
502 }
503 elseif (!empty($instance)) {
504 field_delete_instance($instance);
505 }
506
507 }
508
509 /**
510 * Deletes a node type from the database.
511 *
512 * @param $type
513 * The machine-readable name of the node type to be deleted.
514 */
515 function node_type_delete($type) {
516 $info = node_type_get_type($type);
517 db_delete('node_type')
518 ->condition('type', $type)
519 ->execute();
520 module_invoke_all('node_type_delete', $info);
521 }
522
523 /**
524 * Updates all nodes of one type to be of another type.
525 *
526 * @param $old_type
527 * The current node type of the nodes.
528 * @param $type
529 * The new node type of the nodes.
530 *
531 * @return
532 * The number of nodes whose node type field was modified.
533 */
534 function node_type_update_nodes($old_type, $type) {
535 return db_update('node')
536 ->fields(array('type' => $type))
537 ->condition('type', $old_type)
538 ->execute();
539 }
540
541 /**
542 * Builds and returns the list of available node types.
543 *
544 * The list of types is built by querying hook_node_info() in all modules, and
545 * by comparing this information with the node types in the {node_type} table.
546 *
547 */
548 function _node_types_build() {
549 $_node_types = &drupal_static(__FUNCTION__);
550 if (is_object($_node_types)) {
551 return $_node_types;
552 }
553 $_node_types = (object) array('types' => array(), 'names' => array());
554
555 $info_array = module_invoke_all('node_info');
556 foreach ($info_array as $type => $info) {
557 $info['type'] = $type;
558 $_node_types->types[$type] = node_type_set_defaults($info);
559 $_node_types->names[$type] = $info['name'];
560 }
561 $type_result = db_select('node_type', 'nt')
562 ->fields('nt')
563 ->orderBy('nt.type', 'ASC')
564 ->addTag('node_type_access')
565 ->execute();
566 foreach ($type_result as $type_object) {
567 // Check for node types from disabled modules and mark their types for removal.
568 // Types defined by the node module in the database (rather than by a separate
569 // module using hook_node_info) have a base value of 'node_content'. The isset()
570 // check prevents errors on old (pre-Drupal 7) databases.
571 if (isset($type_object->base) && $type_object->base != 'node_content' && empty($info_array[$type_object->type])) {
572 $type_object->disabled = TRUE;
573 }
574 if (!isset($_node_types->types[$type_object->type]) || $type_object->modified) {
575 $_node_types->types[$type_object->type] = $type_object;
576 $_node_types->names[$type_object->type] = $type_object->name;
577
578 if ($type_object->type != $type_object->orig_type) {
579 unset($_node_types->types[$type_object->orig_type]);
580 unset($_node_types->names[$type_object->orig_type]);
581 }
582 }
583 }
584
585 asort($_node_types->names);
586
587 return $_node_types;
588 }
589
590 /**
591 * Set the default values for a node type.
592 *
593 * The defaults are for a type defined through hook_node_info().
594 * When populating a custom node type $info should have the 'custom'
595 * key set to 1.
596 *
597 * @param $info
598 * An object or array containing values to override the defaults.
599 *
600 * @return
601 * A node type object.
602 */
603 function node_type_set_defaults($info = array()) {
604 static $type;
605
606 if (!isset($type)) {
607 $type = new stdClass();
608 $type->type = '';
609 $type->name = '';
610 $type->base = '';
611 $type->description = '';
612 $type->help = '';
613 $type->has_title = 1;
614 $type->has_body = 1;
615 $type->title_label = t('Title');
616 $type->body_label = t('Body');
617 $type->custom = 0;
618 $type->modified = 0;
619 $type->locked = 1;
620 $type->is_new = 1;
621 }
622
623 $new_type = clone $type;
624 $info = (array) $info;
625 foreach ($info as $key => $data) {
626 $new_type->$key = $data;
627 }
628 // If the type has no title or body, set an empty label.
629 if (!$new_type->has_title) {
630 $new_type->title_label = '';
631 }
632 if (!$new_type->has_body) {
633 $new_type->body_label = '';
634 }
635 $new_type->orig_type = isset($info['type']) ? $info['type'] : '';
636
637 return $new_type;
638 }
639
640 /**
641 * Determine whether a node hook exists.
642 *
643 * @param $node
644 * Either a node object, node array, or a string containing the node type.
645 * @param $hook
646 * A string containing the name of the hook.
647 * @return
648 * TRUE if the $hook exists in the node type of $node.
649 */
650 function node_hook($node, $hook) {
651 $base = node_type_get_base($node);
652 return module_hook($base, $hook);
653 }
654
655 /**
656 * Invoke a node hook.
657 *
658 * @param $node
659 * Either a node object, node array, or a string containing the node type.
660 * @param $hook
661 * A string containing the name of the hook.
662 * @param $a2, $a3, $a4
663 * Arguments to pass on to the hook, after the $node argument.
664 * @return
665 * The returned value of the invoked hook.
666 */
667 function node_invoke($node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
668 if (node_hook($node, $hook)) {
669 $base = node_type_get_base($node);
670 $function = $base . '_' . $hook;
671 return ($function($node, $a2, $a3, $a4));
672 }
673 }
674
675 /**
676 * Load node objects from the database.
677 *
678 * This function should be used whenever you need to load more than one node
679 * from the database. Nodes are loaded into memory and will not require
680 * database access if loaded again during the same page request.
681 *
682 * @param $nids
683 * An array of node IDs.
684 * @param $conditions
685 * An array of conditions on the {node} table in the form 'field' => $value.
686 * @param $reset
687 * Whether to reset the internal node_load cache.
688 *
689 * @return
690 * An array of node objects indexed by nid.
691 */
692 function node_load_multiple($nids = array(), $conditions = array(), $reset = FALSE) {
693 $node_cache = &drupal_static(__FUNCTION__, array());
694
695 if ($reset) {
696 $node_cache = array();
697 }
698 $nodes = array();
699
700 // Create a new variable which is either a prepared version of the $nids
701 // array for later comparison with the node cache, or FALSE if no $nids were
702 // passed. The $nids array is reduced as items are loaded from cache, and we
703 // need to know if it's empty for this reason to avoid querying the database
704 // when all requested nodes are loaded from cache.
705 $passed_nids = !empty($nids) ? array_flip($nids) : FALSE;
706
707 // Revisions are not statically cached, and require a different query to
708 // other conditions, so separate vid into its own variable.
709 $vid = isset($conditions['vid']) ? $conditions['vid'] : FALSE;
710 unset($conditions['vid']);
711
712 // Load any available nodes from the internal cache.
713 if ($node_cache && !$vid) {
714 if ($nids) {
715 $nodes += array_intersect_key($node_cache, $passed_nids);
716 // If any nodes were loaded, remove them from the $nids still to load.
717 $nids = array_keys(array_diff_key($passed_nids, $nodes));
718 }
719 // If loading nodes only by conditions, fetch all available nodes from
720 // the cache. Nodes which don't match are removed later.
721 elseif ($conditions) {
722 $nodes = $node_cache;
723 }
724 }
725
726 // Exclude any nodes loaded from cache if they don't match $conditions.
727 // This ensures the same behavior whether loading from memory or database.
728 if ($conditions) {
729 foreach ($nodes as $node) {
730 $node_values = (array) $node;
731 if (array_diff_assoc($conditions, $node_values)) {
732 unset($nodes[$node->nid]);
733 }
734 }
735 }
736
737 // Load any remaining nodes from the database. This is the case if there are
738 // any $nids left to load, if loading a revision, or if $conditions was
739 // passed without $nids.
740 if ($nids || $vid || ($conditions && !$passed_nids)) {
741 $query = db_select('node', 'n');
742
743 if ($vid) {
744 $query->join('node_revision', 'r', 'r.nid = n.nid AND r.vid = :vid', array(':vid' => $vid));
745 }
746 else {
747 $query->join('node_revision', 'r', 'r.vid = n.vid');
748 }
749
750 // Add fields from the {node} table.
751 $node_fields = drupal_schema_fields_sql('node');
752
753 // vid and title are provided by node_revision, so remove them.
754 unset($node_fields['vid']);
755 unset($node_fields['title']);
756 $query->fields('n', $node_fields);
757
758 // Add all fields from the {node_revision} table.
759 $node_revision_fields = drupal_schema_fields_sql('node_revision');
760
761 // nid is provided by node, so remove it.
762 unset($node_revision_fields['nid']);
763
764 // Change timestamp to revision_timestamp before adding it to the query.
765 unset($node_revision_fields['timestamp']);
766 $query->addField('r', 'timestamp', 'revision_timestamp');
767 $query->fields('r', $node_revision_fields);
768
769 if ($nids) {
770 $query->condition('n.nid', $nids, 'IN');
771 }
772 if ($conditions) {
773 foreach ($conditions as $field => $value) {
774 $query->condition('n.' . $field, $value);
775 }
776 }
777 $queried_nodes = $query->execute()->fetchAllAssoc('nid');
778 }
779
780 // Pass all nodes loaded from the database through the node type specific
781 // callbacks and hook_node_load(), then add them to the internal cache.
782 if (!empty($queried_nodes)) {
783 // Create an array of nodes for each content type and pass this to the
784 // node type specific callback.
785 $typed_nodes = array();
786 foreach ($queried_nodes as $nid => $node) {
787 $typed_nodes[$node->type][$nid] = $node;
788 }
789
790 // Call node type specific callbacks on each typed array of nodes.
791 foreach ($typed_nodes as $type => $nodes_of_type) {
792 if (node_hook($type, 'load')) {
793 $function = node_type_get_base($type) . '_load';
794 $function($nodes_of_type);
795 }
796 }
797
798 // Attach fields.
799 if ($vid) {
800 field_attach_load_revision('node', $queried_nodes);
801 }
802 else {
803 field_attach_load('node', $queried_nodes);
804 }
805
806 // Call hook_node_load(), pass the node types so modules can return early
807 // if not acting on types in the array.
808 foreach (module_implements('node_load') as $module) {
809 $function = $module . '_node_load';
810 $function($queried_nodes, array_keys($typed_nodes));
811 }
812 $nodes += $queried_nodes;
813 // Add nodes to the cache if we're not loading a revision.
814 if (!$vid) {
815 $node_cache += $queried_nodes;
816 }
817 }
818
819 // Ensure that the returned array is ordered the same as the original $nids
820 // array if this was passed in and remove any invalid nids.
821 if ($passed_nids) {
822 // Remove any invalid nids from the array.
823 $passed_nids = array_intersect_key($passed_nids, $nodes);
824 foreach ($nodes as $node) {
825 $passed_nids[$node->nid] = $node;
826 }
827 $nodes = $passed_nids;
828 }
829
830 return $nodes;
831 }
832
833 /**
834 * Load a node object from the database.
835 *
836 * @param $nid
837 * The node ID.
838 * @param $vid
839 * The revision ID.
840 * @param $reset
841 * Whether to reset the node_load_multiple cache.
842 *
843 * @return
844 * A fully-populated node object.
845 */
846 function node_load($nid, $vid = array(), $reset = FALSE) {
847 $vid = isset($vid) ? array('vid' => $vid) : NULL;
848 $node = node_load_multiple(array($nid), $vid, $reset);
849
850 return $node ? $node[$nid] : FALSE;
851 }
852
853 /**
854 * Perform validation checks on the given node.
855 */
856 function node_validate($node, $form = array()) {
857 // Convert the node to an object, if necessary.
858 $node = (object)$node;
859 $type = node_type_get_type($node);
860
861 if (isset($node->nid) && (node_last_changed($node->nid) > $node->changed)) {
862 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.'));
863 }
864
865 if (user_access('administer nodes')) {
866 // Validate the "authored by" field.
867 if (!empty($node->name) && !($account = user_load_by_name($node->name))) {
868 // The use of empty() is mandatory in the context of usernames
869 // as the empty string denotes the anonymous user. In case we
870 // are dealing with an anonymous user we set the user ID to 0.
871 form_set_error('name', t('The username %name does not exist.', array('%name' => $node->name)));
872 }
873
874 // Validate the "authored on" field.
875 if (!empty($node->date) && strtotime($node->date) === FALSE) {
876 form_set_error('date', t('You have to specify a valid date.'));
877 }
878 }
879
880 // Do node-type-specific validation checks.
881 node_invoke($node, 'validate', $form);
882 module_invoke_all('node_validate', $node, $form);
883 }
884
885 /**
886 * Prepare node for save and allow modules to make changes.
887 */
888 function node_submit($node) {
889 global $user;
890
891 // Convert the node to an object, if necessary.
892 $node = (object)$node;
893
894 if (user_access('administer nodes')) {
895 // Populate the "authored by" field.
896 if ($account = user_load_by_name($node->name)) {
897 $node->uid = $account->uid;
898 }
899 else {
900 $node->uid = 0;
901 }
902 }
903 $node->created = !empty($node->date) ? strtotime($node->date) : REQUEST_TIME;
904 $node->validated = TRUE;
905
906 return $node;
907 }
908
909 /**
910 * Save changes to a node or add a new node.
911 *
912 * @param $node
913 * The $node object to be saved. If $node->nid is
914 * omitted (or $node->is_new is TRUE), a new node will be added.
915 */
916 function node_save($node) {
917 field_attach_presave('node', $node);
918 // Let modules modify the node before it is saved to the database.
919 module_invoke_all('node_presave', $node);
920 global $user;
921
922 if (!isset($node->is_new)) {
923 $node->is_new = empty($node->nid);
924 }
925
926 // Apply filters to some default node fields:
927 if ($node->is_new) {
928 // Insert a new node.
929 $node->is_new = TRUE;
930
931 // When inserting a node, $node->log must be set because
932 // {node_revision}.log does not (and cannot) have a default
933 // value. If the user does not have permission to create
934 // revisions, however, the form will not contain an element for
935 // log so $node->log will be unset at this point.
936 if (!isset($node->log)) {
937 $node->log = '';
938 }
939 }
940 elseif (!empty($node->revision)) {
941 $node->old_vid = $node->vid;
942 unset($node->vid);
943 }
944 else {
945 // When updating a node, avoid clobbering an existing log entry with an empty one.
946 if (empty($node->log)) {
947 unset($node->log);
948 }
949 }
950
951 // Set some required fields:
952 if (empty($node->created)) {
953 $node->created = REQUEST_TIME;
954 }
955 // The changed timestamp is always updated for bookkeeping purposes (revisions, searching, ...)
956 $node->changed = REQUEST_TIME;
957
958 $node->timestamp = REQUEST_TIME;
959 $update_node = TRUE;
960
961 // Generate the node table query and the node_revisions table query.
962 if ($node->is_new) {
963 drupal_write_record('node', $node);
964 _node_save_revision($node, $user->uid);
965 $op = 'insert';
966 }
967 else {
968 drupal_write_record('node', $node, 'nid');
969 if (!empty($node->revision)) {
970 _node_save_revision($node, $user->uid);
971 }
972 else {
973 _node_save_revision($node, $user->uid, 'vid');
974 $update_node = FALSE;
975 }
976 $op = 'update';
977 }
978 if ($update_node) {
979 db_update('node')
980 ->fields(array('vid' => $node->vid))
981 ->condition('nid', $node->nid)
982 ->execute();
983 }
984
985 // Call the node specific callback (if any). This can be
986 // node_invoke($node, 'insert') or
987 // node_invoke($node, 'update').
988 node_invoke($node, $op);
989
990 // Save fields.
991 $function = "field_attach_$op";
992 $function('node', $node);
993
994 module_invoke_all('node_' . $op, $node);
995
996 // Update the node access table for this node.
997 node_access_acquire_grants($node);
998
999 // Clear internal properties.
1000 unset($node->is_new);
1001
1002 // Clear the page and block caches.
1003 cache_clear_all();
1004
1005 // Ignore slave server temporarily to give time for the
1006 // saved node to be propagated to the slave.
1007 db_ignore_slave();
1008 }
1009
1010 /**
1011 * Helper function to save a revision with the uid of the current user.
1012 *
1013 * Node is taken by reference, because drupal_write_record() updates the
1014 * $node with the revision id, and we need to pass that back to the caller.
1015 */
1016 function _node_save_revision($node, $uid, $update = NULL) {
1017 $temp_uid = $node->uid;
1018 $node->uid = $uid;
1019 if (isset($update)) {
1020 drupal_write_record('node_revision', $node, $update);
1021 }
1022 else {
1023 drupal_write_record('node_revision', $node);
1024 }
1025 $node->uid = $temp_uid;
1026 }
1027
1028 /**
1029 * Delete a node.
1030 *
1031 * @param $nid
1032 * A node ID.
1033 */
1034 function node_delete($nid) {
1035 node_delete_multiple(array($nid));
1036 }
1037
1038 /**
1039 * Delete multiple nodes.
1040 *
1041 * @param $nids
1042 * An array of node IDs.
1043 */
1044 function node_delete_multiple($nids) {
1045 $nodes = node_load_multiple($nids, array());
1046
1047 db_delete('node')
1048 ->condition('nid', $nids, 'IN')
1049 ->execute();
1050 db_delete('node_revision')
1051 ->condition('nid', $nids, 'IN')
1052 ->execute();
1053 db_delete('history')
1054 ->condition('nid', $nids, 'IN')
1055 ->execute();
1056
1057 foreach ($nodes as $nid => $node) {
1058 // Call the node-specific callback (if any):
1059 node_invoke($node, 'delete');
1060 module_invoke_all('node_delete', $node);
1061 field_attach_delete('node', $node);
1062
1063 // Remove this node from the search index if needed.
1064 // This code is implemented in node module rather than in search module,
1065 // because node module is implementing search module's API, not the other
1066 // way around.
1067 if (module_exists('search')) {
1068 search_reindex($nid, 'node');
1069 }
1070 }
1071
1072 // Clear the page and block and node_load_multiple caches.
1073 cache_clear_all();
1074 drupal_static_reset('node_load_multiple');
1075 }
1076
1077 /**
1078 * Generate an array for rendering the given node.
1079 *
1080 * @param $node
1081 * A node array or node object.
1082 * @param $build_mode
1083 * Build mode, e.g. 'full', 'teaser'...
1084 *
1085 * @return
1086 * An array as expected by drupal_render().
1087 */
1088 function node_build($node, $build_mode = 'full') {
1089 $node = (object)$node;
1090
1091 $node = node_build_content($node, $build_mode);
1092
1093 $build = $node->content;
1094 $build += array(
1095 '#theme' => 'node',
1096 '#node' => $node,
1097 '#build_mode' => $build_mode,
1098 );
1099 return $build;
1100 }
1101
1102 /**
1103 * Builds a structured array representing the node's content.
1104 *
1105 * The content built for the node (field values, comments, file attachments or
1106 * other node components) will vary depending on the $build_mode parameter.
1107 *
1108 * Drupal core defines the following build modes for nodes, with the following
1109 * default use cases:
1110 * - full (default): node is being displayed on its own page (node/123)
1111 * - teaser: node is being displayed on the default home page listing, on
1112 * taxonomy listing pages, or on blog listing pages.
1113 * - rss: node displayed in an RSS feed.
1114 * If search.module is enabled:
1115 * - search_index: node is being indexed for search.
1116 * - search_result: node is being displayed as a search result.
1117 * If book.module is enabled:
1118 * - print: node is being displayed in print-friendly mode.
1119 * Contributed modules might define additional build modes, or use existing
1120 * build modes in additional contexts.
1121 *
1122 * @param $node
1123 * A node object.
1124 * @param $build_mode
1125 * Build mode, e.g. 'full', 'teaser'...
1126 *
1127 * @return
1128 * A structured array containing the individual elements
1129 * of the node's content.
1130 */
1131 function node_build_content($node, $build_mode = 'full') {
1132 // The 'view' hook can be implemented to overwrite the default function
1133 // to display nodes.
1134 if (node_hook($node, 'view')) {
1135 $node = node_invoke($node, 'view', $build_mode);
1136 }
1137
1138 // Build fields content.
1139 if (empty($node->content)) {
1140 $node->content = array();
1141 };
1142 $node->content += field_attach_view('node', $node, $build_mode);
1143
1144 // Always display a read more link on teasers because we have no way
1145 // to know when a teaser view is different than a full view.
1146 $links = array();
1147 if ($build_mode == 'teaser') {
1148 $links['node_readmore'] = array(
1149 'title' => t('Read more'),
1150 'href' => 'node/' . $node->nid,
1151 'attributes' => array('rel' => 'tag', 'title' => strip_tags($node->title))
1152 );
1153 }
1154 $node->content['links']['node'] = array(
1155 '#theme' => 'links',
1156 '#links' => $links,
1157 '#attributes' => array('class' => 'links inline'),
1158 );
1159
1160 // Allow modules to make their own additions to the node.
1161 module_invoke_all('node_view', $node, $build_mode);
1162
1163 // Allow modules to modify the structured node.
1164 drupal_alter('node_build', $node, $build_mode);
1165
1166 return $node;
1167 }
1168
1169 /**
1170 * Generate an array which displays a node detail page.
1171 *
1172 * @param $node
1173 * A node object.
1174 * @param $message
1175 * A flag which sets a page title relevant to the revision being viewed.
1176 * @return
1177 * A $page element suitable for use by drupal_page_render().
1178 */
1179 function node_show($node, $message = FALSE) {
1180 if ($message) {
1181 drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp))), PASS_THROUGH);
1182 }
1183
1184 // Update the history table, stating that this user viewed this node.
1185 node_tag_new($node->nid);
1186
1187 // For markup consistency with other pages, use node_build_multiple() rather than node_build().
1188 return node_build_multiple(array($node), 'full');
1189 }
1190
1191 /**
1192 * Process variables for node.tpl.php
1193 *
1194 * Most themes utilize their own copy of node.tpl.php. The default is located
1195 * inside "modules/node/node.tpl.php". Look in there for the full list of
1196 * variables.
1197 *
1198 * The $variables array contains the following arguments:
1199 * - $node
1200 * - $build_mode
1201 * - $page
1202 *
1203 * @see node.tpl.php
1204 */
1205 function template_preprocess_node(&$variables) {
1206 $variables['build_mode'] = $variables['elements']['#build_mode'];
1207 // Provide a distinct $teaser boolean.
1208 $variables['teaser'] = $variables['build_mode'] == 'teaser';
1209 $variables['node'] = $variables['elements']['#node'];
1210 $node = $variables['node'];
1211
1212 $variables['date'] = format_date($node->created);
1213 $variables['name'] = theme('username', $node);
1214 $variables['node_url'] = url('node/' . $node->nid);
1215 $variables['title'] = check_plain($node->title);
1216 $variables['page'] = (bool)menu_get_object();
1217
1218 if (!empty($node->in_preview)) {
1219 unset($node->content['links']);
1220 }
1221
1222 // Flatten the node object's member fields.
1223 $variables = array_merge((array)$node, $variables);
1224
1225 // Display post information only on certain node types.
1226 if (variable_get('node_submitted_' . $node->type, TRUE)) {
1227 $variables['submitted'] = theme('node_submitted', $node);
1228 $variables['user_picture'] = theme_get_setting('toggle_node_user_picture') ? theme('user_picture', $node) : '';
1229 }
1230 else {
1231 $variables['submitted'] = '';
1232 $variables['user_picture'] = '';
1233 }
1234
1235 // Gather node classes.
1236 $variables['classes_array'][] = 'node-' . $node->type;
1237 if ($variables['promote']) {
1238 $variables['classes_array'][] = 'node-promoted';
1239 }
1240 if ($variables['sticky']) {
1241 $variables['classes_array'][] = 'node-sticky';
1242 }
1243 if (!$variables['status']) {
1244 $variables['classes_array'][] = 'node-unpublished';
1245 }
1246 if ($variables['teaser']) {
1247 $variables['classes_array'][] = 'node-teaser';
1248 }
1249 if (isset($variables['preview'])) {
1250 $variables['classes_array'][] = 'node-preview';
1251 }
1252
1253 // Clean up name so there are no underscores.
1254 $variables['template_files'][] = 'node-' . str_replace('_', '-', $node->type);
1255 $variables['template_files'][] = 'node-' . $node->nid;
1256 }
1257
1258 /**
1259 * Theme a log message.
1260 *
1261 * @ingroup themeable
1262 */
1263 function theme_node_log_message($log) {
1264 return '<div class="log"><div class="title">' . t('Log') . ':</div>' . $log . '</div>';
1265 }
1266
1267 /**
1268 * Implement hook_permission().
1269 */
1270 function node_permission() {
1271 $perms = array(
1272 'administer content types' => array(
1273 'title' => t('Administer content types'),
1274 'description' => t('Manage content types and content type administration settings.'),
1275 ),
1276 'administer nodes' => array(
1277 'title' => t('Administer nodes'),
1278 'description' => t('Manage all information associated with site content, such as author, publication date and current revision. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))),
1279 ),
1280 'access content' => array(
1281 'title' => t('Access content'),
1282 'description' => t('View published content.'),
1283 ),
1284 'bypass node access' => array(
1285 'title' => t('Bypass node access'),
1286 'description' => t('View, edit and delete all site content. Users with this permission will bypass any content-related access control. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))),
1287 ),
1288 'view revisions' => array(
1289 'title' => t('View revisions'),
1290 'description' => t('View content revisions.'),
1291 ),
1292 'revert revisions' => array(
1293 'title' => t('Revert revisions'),
1294 'description' => t('Replace content with an older revision.'),
1295 ),
1296 'delete revisions' => array(
1297 'title' => t('Delete revisions'),
1298 'description' => t('Delete content revisions.'),
1299 ),
1300 'view own unpublished content' => array(
1301 'title' => t('View own unpublished content'),
1302 'description' => t('View unpublished content created by the user'),
1303 ),
1304 );
1305
1306 foreach (node_type_get_types() as $type) {
1307 if ($type->base == 'node_content') {
1308 $perms += node_list_permissions($type);
1309 }
1310 }
1311
1312 return $perms;
1313 }
1314
1315 /**
1316 * Gather the rankings from the the hook_ranking implementations.
1317 */
1318 function _node_rankings() {
1319 $rankings = array(
1320 'total' => 0, 'join' => array(), 'score' => array(), 'args' => array(),
1321 );
1322 if ($ranking = module_invoke_all('ranking')) {
1323 foreach ($ranking as $rank => $values) {
1324 if ($node_rank = variable_get('node_rank_' . $rank, 0)) {
1325 // If the table defined in the ranking isn't already joined, then add it.
1326 if (isset($values['join']) && !isset($rankings['join'][$values['join']])) {
1327 $rankings['join'][$values['join']] = $values['join'];
1328 }
1329
1330 // Add the rankings weighted score multiplier value, handling NULL gracefully.
1331 $rankings['score'][] = 'CAST(%f AS DECIMAL) * COALESCE((' . $values['score'] . '), 0)';
1332
1333 // Add the the administrator's weighted score multiplier value for this ranking.
1334 $rankings['total'] += $node_rank;
1335 $rankings['arguments'][] = $node_rank;
1336
1337 // Add any additional arguments used by this ranking.
1338 if (isset($values['arguments'])) {
1339 $rankings['arguments'] = array_merge($rankings['arguments'], $values['arguments']);
1340 }
1341 }
1342 }
1343 }
1344 return $rankings;
1345 }
1346
1347
1348 /**
1349 * Implement hook_search().
1350 */
1351 function node_search($op = 'search', $keys = NULL) {
1352 switch ($op) {
1353 case 'name':
1354 return t('Content');
1355
1356 case 'reset':
1357 db_update('search_dataset')
1358 ->fields(array('reindex' => REQUEST_TIME))
1359 ->condition('type', 'node')
1360 ->execute();
1361 return;
1362
1363 case 'status':
1364 $total = db_query('SELECT COUNT(*) FROM {node} WHERE status = 1')->fetchField();
1365 $remaining = db_query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE n.status = 1 AND d.sid IS NULL OR d.reindex <> 0")->fetchField();
1366 return array('remaining' => $remaining, 'total' => $total);
1367
1368 case 'admin':
1369 $form = array();
1370 // Output form for defining rank factor weights.
1371 $form['content_ranking'] = array(
1372 '#type' => 'fieldset',
1373 '#title' => t('Content ranking'),
1374 );
1375 $form['content_ranking']['#theme'] = 'node_search_admin';
1376 $form['content_ranking']['info'] = array(
1377 '#value' => '<em>' . t('The following numbers control which properties the content search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '</em>'
1378 );
1379
1380 // Note: reversed to reflect that higher number = higher ranking.
1381 $options = drupal_map_assoc(range(0, 10));
1382 foreach (module_invoke_all('ranking') as $var => $values) {
1383 $form['content_ranking']['factors']['node_rank_' . $var] = array(
1384 '#title' => $values['title'],
1385 '#type' => 'select',
1386 '#options' => $options,
1387 '#default_value' => variable_get('node_rank_' . $var, 0),
1388 );
1389 }
1390 return $form;
1391
1392 case 'search':
1393 // Build matching conditions
1394 list($join1, $where1) = _db_rewrite_sql();
1395 $arguments1 = array();
1396 $conditions1 = 'n.status = 1';
1397
1398 if ($type = search_query_extract($keys, 'type')) {
1399 $types = array();
1400 foreach (explode(',', $type) as $t) {
1401 $types[] = "n.type = '%s'";
1402 $arguments1[] = $t;
1403 }
1404 $conditions1 .= ' AND (' . implode(' OR ', $types) . ')';
1405 $keys = search_query_insert($keys, 'type');
1406 }
1407
1408 if ($term = search_query_extract($keys, 'term')) {
1409 $terms = array();
1410 foreach (explode(',', $term) as $c) {
1411 $terms[] = "tn.tid = %d";
1412 $arguments1[] = $c;
1413 }
1414 $conditions1 .= ' AND (' . implode(' OR ', $terms) . ')';
1415 $join1 .= ' INNER JOIN {taxonomy_term_node} tn ON n.vid = tn.vid';
1416 $keys = search_query_insert($keys, 'term');
1417 }
1418
1419 if ($languages = search_query_extract($keys, 'language')) {
1420 $terms = array();
1421 foreach (explode(',', $languages) as $l) {
1422 $terms[] = "n.language = '%s'";
1423 $arguments1[] = $l;
1424 }
1425 $conditions1 .= ' AND (' . implode(' OR ', $terms) . ')';
1426 $keys = search_query_insert($keys, 'language');
1427 }
1428
1429 // Get the ranking expressions.
1430 $rankings = _node_rankings();
1431
1432 // When all search factors are disabled (ie they have a weight of zero),
1433 // The default score is based only on keyword relevance.
1434 if ($rankings['total'] == 0) {
1435 $total = 1;
1436 $arguments2 = array();
1437 $join2 = '';
1438 $select2 = 'SUM(i.relevance) AS calculated_score';
1439 }
1440 else {
1441 $total = $rankings['total'];
1442 $arguments2 = $rankings['arguments'];
1443 $join2 = implode(' ', $rankings['join']);
1444 $select2 = 'SUM(' . implode(' + ', $rankings['score']) . ') AS calculated_score';
1445 }
1446
1447 // Do search.
1448 $find = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid = i.sid ' . $join1, $conditions1 . (empty($where1) ? '' : ' AND ' . $where1), $arguments1, $select2, $join2, $arguments2);
1449
1450 // Load results.
1451 $results = array();
1452 foreach ($find as $item) {
1453 // Render the node.
1454 $node = node_load($item->sid);
1455 $node = node_build_content($node, 'search_result');
1456 $node->rendered = drupal_render($node->content);
1457
1458 // Fetch comments for snippet.
1459 $node->rendered .= module_invoke('comment', 'node_update_index', $node);
1460 // Fetch terms for snippet.
1461 $node->rendered .= module_invoke('taxonomy', 'node_update_index', $node);
1462
1463 $extra = module_invoke_all('node_search_result', $node);
1464
1465 $results[] = array(
1466 'link' => url('node/' . $item->sid, array('absolute' => TRUE)),
1467 'type' => check_plain(node_type_get_name($node)),
1468 'title' => $node->title,
1469 'user' => theme('username', $node),
1470 'date' => $node->changed,
1471 'node' => $node,
1472 'extra' => $extra,
1473 'score' => $total ? ($item->calculated_score / $total) : 0,
1474 'snippet' => search_excerpt($keys, $node->rendered),
1475 );
1476 }
1477 return $results;
1478 }
1479 }
1480
1481 /**
1482 * Implement hook_ranking().
1483 */
1484 function node_ranking() {
1485 // Create the ranking array and add the basic ranking options.
1486 $ranking = array(
1487 'relevance' => array(
1488 'title' => t('Keyword relevance'),
1489 // Average relevance values hover around 0.15
1490 'score' => 'i.relevance',
1491 ),
1492 'sticky' => array(
1493 'title' => t('Content is sticky at top of lists'),
1494 // The sticky flag is either 0 or 1, which is automatically normalized.
1495 'score' => 'n.sticky',
1496 ),
1497 'promote' => array(
1498 'title' => t('Content is promoted to the front page'),
1499 // The promote flag is either 0 or 1, which is automatically normalized.
1500 'score' => 'n.promote',
1501 ),
1502 );
1503
1504 // Add relevance based on creation or changed date.
1505 if ($node_cron_last = variable_get('node_cron_last', 0)) {
1506 $ranking['recent'] = array(
1507 'title' => t('Recently posted'),
1508 // Exponential decay with half-life of 6 months, starting at last indexed node
1509 'score' => 'POW(2.0, (GREATEST(n.created, n.changed) - %d) * 6.43e-8)',
1510 'arguments' => array($node_cron_last),
1511 );
1512 }
1513 return $ranking;
1514 }
1515
1516 /**
1517 * Implement hook_user_cancel().
1518 */
1519 function node_user_cancel($edit, $account, $method) {
1520 switch ($method) {
1521 case 'user_cancel_block_unpublish':
1522 // Unpublish nodes (current revisions).
1523 module_load_include('inc', 'node', 'node.admin');
1524 $nodes = db_select('node', 'n')
1525 ->fields('n', array('nid'))
1526 ->condition('uid', $account->uid)
1527 ->execute()
1528 ->fetchCol();
1529 node_mass_update($nodes, array('status' => 0));
1530 break;
1531
1532 case 'user_cancel_reassign':
1533 // Anonymize nodes (current revisions).
1534 module_load_include('inc', 'node', 'node.admin');
1535 $nodes = db_select('node', 'n')
1536 ->fields('n', array('nid'))
1537 ->condition('uid', $account->uid)
1538 ->execute()
1539 ->fetchCol();
1540 node_mass_update($nodes, array('uid' => 0));
1541 // Anonymize old revisions.
1542 db_update('node_revision')
1543 ->fields(array('uid' => 0))
1544 ->condition('uid', $account->uid)
1545 ->execute();
1546 // Clean history.
1547 db_delete('history')
1548 ->condition('uid', $account->uid)
1549 ->execute();
1550 break;
1551
1552 case 'user_cancel_delete':
1553 // Delete nodes (current revisions).
1554 // @todo Introduce node_mass_delete() or make node_mass_update() more flexible.
1555 $nodes = db_select('node', 'n')
1556 ->fields('n', array('nid'))
1557 ->condition('uid', $account->uid)
1558 ->execute()
1559 ->fetchCol();
1560 foreach ($nodes as $nid) {
1561 node_delete($nid);
1562 }
1563 // Delete old revisions.
1564 db_delete('node_revision')
1565 ->condition('uid', $account->uid)
1566 ->execute();
1567 // Clean history.
1568 db_delete('history')
1569 ->condition('uid', $account->uid)
1570 ->execute();
1571 break;
1572 }
1573 }
1574
1575 /**
1576 * Theme the content ranking part of the search settings admin page.
1577 *
1578 * @ingroup themeable
1579 */
1580 function theme_node_search_admin($form) {
1581 $output = drupal_render($form['info']);
1582
1583 $header = array(t('Factor'), t('Weight'));
1584 foreach (element_children($form['factors']) as $key) {
1585 $row = array();
1586 $row[] = $form['factors'][$key]['#title'];
1587 unset($form['factors'][$key]['#title']);
1588 $row[] = drupal_render($form['factors'][$key]);
1589 $rows[] = $row;
1590 }
1591 $output .= theme('table', $header, $rows);
1592
1593 $output .= drupal_render_children($form);
1594 return $output;
1595 }
1596
1597 function _node_revision_access($node, $op = 'view') {
1598 $access = &drupal_static(__FUNCTION__, array());
1599 if (!isset($access[$node->vid])) {
1600 $node_current_revision = node_load($node->nid);
1601 $is_current_revision = $node_current_revision->vid == $node->vid;
1602 // There should be at least two revisions. If the vid of the given node
1603 // and the vid of the current revision differs, then we already have two
1604 // different revisions so there is no need for a separate database check.
1605 // Also, if you try to revert to or delete the current revision, that's
1606 // not good.
1607 if ($is_current_revision && (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField() == 1 || $op == 'update' || $op == 'delete')) {
1608 $access[$node->vid] = FALSE;
1609 }
1610 elseif (user_access('administer nodes')) {
1611 $access[$node->vid] = TRUE;
1612 }
1613 else {
1614 $map = array('view' => 'view revisions', 'update' => 'revert revisions', 'delete' => 'delete revisions');
1615 // First check the user permission, second check the access to the
1616 // current revision and finally, if the node passed in is not the current
1617 // revision then access to that, too.
1618 $access[$node->vid] = isset($map[$op]) && user_access($map[$op]) && node_access($op, $node_current_revision) && ($is_current_revision || node_access($op, $node));
1619 }
1620 }
1621 return $access[$node->vid];
1622 }
1623
1624 function _node_add_access() {
1625 $types = node_type_get_types();
1626 foreach ($types as $type) {
1627 if (node_hook($type->type, 'form') && node_access('create', $type->type)) {
1628 return TRUE;
1629 }
1630 }
1631 if (user_access('administer content types')) {
1632 // There are no content types defined that the user has permission to create,
1633 // but the user does have the permission to administer the content types, so
1634 // grant them access to the page anyway.
1635 return TRUE;
1636 }
1637 return FALSE;
1638 }
1639
1640 /**
1641 * Implement hook_menu().
1642 */
1643 function node_menu() {
1644 $items['admin/content'] = array(
1645 'title' => 'Content',
1646 'page callback' => 'drupal_get_form',
1647 'page arguments' => array('node_admin_content'),
1648 'access arguments' => array('administer nodes'),
1649 'weight' => -10,
1650 );
1651 $items['admin/content/node'] = array(
1652 'title' => 'Content',
1653 'type' => MENU_DEFAULT_LOCAL_TASK,
1654 'weight' => -10,
1655 );
1656
1657 $items['admin/reports/status/rebuild'] = array(
1658 'title' => 'Rebuild permissions',
1659 'page callback' => 'drupal_get_form',
1660 'page arguments' => array('node_configure_rebuild_confirm'),
1661 // Any user than can potentially trigger a node_access_needs_rebuild(TRUE)
1662 // has to be allowed access to the 'node access rebuild' confirm form.
1663 'access arguments' => array('access administration pages'),
1664 'type' => MENU_CALLBACK,
1665 );
1666
1667 $items['admin/structure/types'] = array(
1668 'title' => 'Content types',
1669 'description' => 'Manage posts by content type, including default status, front page promotion, comment settings, etc.',
1670 'page callback' => 'node_overview_types',
1671 'access arguments' => array('administer content types'),
1672 );
1673 $items['admin/structure/types/list'] = array(
1674 'title' => 'List',
1675 'type' => MENU_DEFAULT_LOCAL_TASK,
1676 'weight' => -10,
1677 );
1678 $items['admin/structure/types/add'] = array(
1679 'title' => 'Add content type',
1680 'page callback' => 'drupal_get_form',
1681 'page arguments' => array('node_type_form'),
1682 'access arguments' => array('administer content types'),
1683 'type' => MENU_LOCAL_TASK,
1684 );
1685 $items['node'] = array(
1686 'title' => 'Content',
1687 'page callback' => 'node_page_default',
1688 'access arguments' => array('access content'),
1689 'type' => MENU_CALLBACK,
1690 );
1691 $items['node/add'] = array(
1692 'title' => 'Add new content',
1693 'page callback' => 'node_add_page',
1694 'access callback' => '_node_add_access',
1695 'weight' => 1,
1696 'menu_name' => 'management',
1697 );
1698 $items['rss.xml'] = array(
1699 'title' => 'RSS feed',
1700 'page callback' => 'node_feed',
1701 'access arguments' => array('access content'),
1702 'type' => MENU_CALLBACK,
1703 );
1704 // Reset internal static cache of _node_types_build, forces to rebuild the node type information.
1705 node_type_clear();
1706 foreach (node_type_get_types() as $type) {
1707 $type_url_str = str_replace('_', '-', $type->type);
1708 $items['node/add/' . $type_url_str] = array(
1709 'title' => $type->name,
1710 'title callback' => 'check_plain',
1711 'page callback' => 'node_add',
1712 'page arguments' => array(2),
1713 'access callback' => 'node_access',
1714 'access arguments' => array('create', $type->type),
1715 'description' => $type->description,
1716 );
1717 $items['admin/structure/node-type/' . $type_url_str] = array(
1718 'title' => $type->name,
1719 'page callback' => 'drupal_get_form',
1720 'page arguments' => array('node_type_form', $type),
1721 'access arguments' => array('administer content types'),
1722 'type' => MENU_CALLBACK,
1723 );
1724 $items['admin/structure/node-type/' . $type_url_str . '/edit'] = array(
1725 'title' => 'Edit',
1726 'type' => MENU_DEFAULT_LOCAL_TASK,
1727 );
1728 $items['admin/structure/node-type/' . $type_url_str . '/delete'] = array(
1729 'title' => 'Delete',
1730 'page arguments' => array('node_type_delete_confirm', $type),
1731 'access arguments' => array('administer content types'),
1732 'type' => MENU_CALLBACK,
1733 );
1734 }
1735 $items['node/%node'] = array(
1736 'title callback' => 'node_page_title',
1737 'title arguments' => array(1),
1738 'page callback' => 'node_page_view',
1739 'page arguments' => array(1),
1740 'access callback' => 'node_access',
1741 'access arguments' => array('view', 1),
1742 'type' => MENU_CALLBACK);
1743 $items['node/%node/view'] = array(
1744 'title' => 'View',
1745 'type' => MENU_DEFAULT_LOCAL_TASK,
1746 'weight' => -10);
1747 $items['node/%node/edit'] = array(
1748 'title' => 'Edit',
1749 'page callback' => 'node_page_edit',
1750 'page arguments' => array(1),
1751 'access callback' => 'node_access',
1752 'access arguments' => array('update', 1),
1753 'weight' => 1,
1754 'type' => MENU_LOCAL_TASK,
1755 );
1756 $items['node/%node/delete'] = array(
1757 'title' => 'Delete',
1758 'page callback' => 'drupal_get_form',
1759 'page arguments' => array('node_delete_confirm', 1),
1760 'access callback' => 'node_access',
1761 'access arguments' => array('delete', 1),
1762 'weight' => 1,
1763 'type' => MENU_CALLBACK);
1764 $items['node/%node/revisions'] = array(
1765 'title' => 'Revisions',
1766 'page callback' => 'node_revision_overview',
1767 'page arguments' => array(1),
1768 'access callback' => '_node_revision_access',
1769 'access arguments' => array(1),
1770 'weight' => 2,
1771 'type' => MENU_LOCAL_TASK,
1772 );
1773 $items['node/%node/revisions/%/view'] = array(
1774 'title' => 'Revisions',
1775 'load arguments' => array(3),
1776 'page callback' => 'node_show',
1777 'page arguments' => array(1, TRUE),
1778 'access callback' => '_node_revision_access',
1779 'access arguments' => array(1),
1780 'type' => MENU_CALLBACK,
1781 );
1782 $items['node/%node/revisions/%/revert'] = array(
1783 'title' => 'Revert to earlier revision',
1784 'load arguments' => array(3),
1785 'page callback' => 'drupal_get_form',
1786 'page arguments' => array('node_revision_revert_confirm', 1),
1787 'access callback' => '_node_revision_access',
1788 'access arguments' => array(1, 'update'),
1789 'type' => MENU_CALLBACK,
1790 );
1791 $items['node/%node/revisions/%/delete'] = array(
1792 'title' => 'Delete earlier revision',
1793 'load arguments' => array(3),
1794 'page callback' => 'drupal_get_form',
1795 'page arguments' => array('node_revision_delete_confirm', 1),
1796 'access callback' => '_node_revision_access',
1797 'access arguments' => array(1, 'delete'),
1798 'type' => MENU_CALLBACK,
1799 );
1800 return $items;
1801 }
1802
1803 /**
1804 * Title callback.
1805 */
1806 function node_page_title($node) {
1807 return $node->title;
1808 }
1809
1810 /**
1811 * Implement hook_init().
1812 */
1813 function node_init() {
1814 drupal_add_css(drupal_get_path('module', 'node') . '/node.css');
1815 }
1816
1817 function node_last_changed($nid) {
1818 return db_query('SELECT changed FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetch()->changed;
1819 }
1820
1821 /**
1822 * Return a list of all the existing revision numbers.
1823 */
1824 function node_revision_list($node) {
1825 $revisions = array();
1826 $result = db_query('SELECT r.vid, r.title, r.log, r.uid, n.vid AS current_vid, r.timestamp, u.name FROM {node_revision} r LEFT JOIN {node} n ON n.vid = r.vid INNER JOIN {users} u ON u.uid = r.uid WHERE r.nid = :nid ORDER BY r.timestamp DESC', array(':nid' => $node->nid));
1827 foreach ($result as $revision) {
1828 $revisions[$revision->vid] = $revision;
1829 }
1830
1831 return $revisions;
1832 }
1833
1834 /**
1835 * Implement hook_block_list().
1836 */
1837 function node_block_list() {
1838 $blocks['syndicate']['info'] = t('Syndicate');
1839 // Not worth caching.
1840 $blocks['syndicate']['cache'] = BLOCK_NO_CACHE;
1841 return $blocks;
1842 }
1843
1844 /**
1845 * Implement hook_block_view().
1846 */
1847 function node_block_view($delta = '') {
1848 $block['subject'] = t('Syndicate');
1849 $block['content'] = theme('feed_icon', url('rss.xml'), t('Syndicate'));
1850
1851 return $block;
1852 }
1853
1854 /**
1855 * A generic function for generating RSS feeds from a set of nodes.
1856 *
1857 * @param $nids
1858 * An array of node IDs (nid). Defaults to FALSE so empty feeds can be
1859 * generated with passing an empty array, if no items are to be added
1860 * to the feed.
1861 * @param $channel
1862 * An associative array containing title, link, description and other keys.
1863 * The link should be an absolute URL.
1864 */
1865 function node_feed($nids = FALSE, $channel = array()) {
1866 global $base_url, $language;
1867
1868 if ($nids === FALSE) {
1869 $nids = db_select('node', 'n')
1870 ->fields('n', array('nid', 'created'))
1871 ->condition('n.promote', 1)
1872 ->condition('status', 1)
1873 ->orderBy('n.created', 'DESC')
1874 ->range(0, variable_get('feed_default_items', 10))
1875 ->addTag('node_access')
1876 ->execute()
1877 ->fetchCol();
1878 }
1879
1880 $item_length = variable_get('feed_item_length', 'teaser');
1881 $namespaces = array('xmlns:dc' => 'http://purl.org/dc/elements/1.1/');
1882 $teaser = ($item_length == 'teaser');
1883
1884 // Load all nodes to be rendered.
1885 $nodes = node_load_multiple($nids);
1886 $items = '';
1887 foreach ($nodes as $node) {
1888 $item_text = '';
1889
1890 $node->link = url("node/$node->nid", array('absolute' => TRUE));
1891 $node->rss_namespaces = array();
1892 $node->rss_elements = array(
1893 array('key' => 'pubDate', 'value' => gmdate('r', $node->created)),
1894 array('key' => 'dc:creator', 'value' => $node->name),
1895 array('key' => 'guid', 'value' => $node->nid . ' at ' . $base_url, 'attributes' => array('isPermaLink' => 'false'))
1896 );
1897
1898 // The node gets built and modules add to or modify $node->rss_elements
1899 // and $node->rss_namespaces.
1900 $node = node_build_content($node, 'rss');
1901
1902 if (!empty($node->rss_namespaces)) {
1903 $namespaces = array_merge($namespaces, $node->rss_namespaces);
1904 }
1905
1906 if ($item_length != 'title' && !empty($node->content)) {
1907 // We render node contents and force links to be last.
1908 $links = drupal_render($node->content['links']);
1909 $item_text .= drupal_render($node->content) . $links;
1910 }
1911
1912 $items .= format_rss_item($node->title, $node->link, $item_text, $node->rss_elements);
1913 }
1914
1915 $channel_defaults = array(
1916 'version' => '2.0',
1917 'title' => variable_get('site_name', 'Drupal'),
1918 'link' => $base_url,
1919 'description' => variable_get('feed_description', ''),
1920 'language' => $language->language
1921 );
1922 $channel = array_merge($channel_defaults, $channel);
1923
1924 $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1925 $output .= "<rss version=\"" . $channel["version"] . "\" xml:base=\"" . $base_url . "\" " . drupal_attributes($namespaces) . ">\n";
1926 $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language']);
1927 $output .= "</rss>\n";
1928
1929 drupal_set_header('Content-Type', 'application/rss+xml; charset=utf-8');
1930 print $output;
1931 }
1932
1933 /**
1934 * Construct a drupal_render() style array from an array of loaded nodes.
1935 *
1936 * @param $nodes
1937 * An array of nodes as returned by node_load_multiple().
1938 * @param $build_mode
1939 * Build mode, e.g. 'full', 'teaser'...
1940 * @param $weight
1941 * An integer representing the weight of the first node in the list.
1942 * @return
1943 * An array in the format expected by drupal_render().
1944 */
1945 function node_build_multiple($nodes, $build_mode = 'teaser', $weight = 0) {
1946 $build = array();
1947 foreach ($nodes as $node) {
1948 $build['nodes'][$node->nid] = node_build($node, $build_mode);
1949 $build['nodes'][$node->nid]['#weight'] = $weight;
1950 $weight++;
1951 }
1952 $build['nodes']['#sorted'] = TRUE;
1953 return $build;
1954 }
1955
1956 /**
1957 * Menu callback; Generate a listing of promoted nodes.
1958 */
1959 function node_page_default() {
1960 $select = db_select('node', 'n')
1961 ->fields('n', array('nid'))
1962 ->condition('promote', 1)
1963 ->condition('status', 1)
1964 ->orderBy('sticky', 'DESC')
1965 ->orderBy('created', 'DESC')
1966 ->extend('PagerDefault')
1967 ->limit(variable_get('default_nodes_main', 10))
1968 ->addTag('node_access');
1969
1970 $nids = $select->execute()->fetchCol();
1971
1972 if (!empty($nids)) {
1973 $nodes = node_load_multiple($nids);
1974 $build = node_build_multiple($nodes);
1975
1976 $feed_url = url('rss.xml', array('absolute' => TRUE));
1977 drupal_add_feed($feed_url, variable_get('site_name', 'Drupal') . ' ' . t('RSS'));
1978 $build['pager'] = array(
1979 '#theme' => 'pager',
1980 '#weight' => 5,
1981 );
1982 drupal_set_title('');
1983 }
1984 else {
1985 drupal_set_title(t('Welcome to @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), PASS_THROUGH);
1986
1987 $default_message = '<p>' . t('No front page content has been created yet.') . '</p>';
1988
1989 $default_links = array();
1990 if (_node_add_access()) {
1991 $default_links[] = l(t('Create content'), 'node/add');
1992 }
1993 if (user_access('administer site configuration')) {
1994 $default_links[] = l(t('Change the default front page'), 'admin/settings/site-information');
1995 }
1996 if (!empty($default_links)) {
1997 $default_message .= theme('item_list', $default_links);
1998 }
1999
2000 $build['default_message'] = array(
2001 '#markup' => $default_message,
2002 '#prefix' => '<div id="first-time">',
2003 '#suffix' => '</div>',
2004 );
2005 }
2006 return $build;
2007 }
2008
2009 /**
2010 * Menu callback; view a single node.
2011 */
2012 function node_page_view($node) {
2013 drupal_set_title($node->title);
2014 return node_show($node);
2015 }
2016
2017 /**
2018 * Implement hook_update_index().
2019 */
2020 function node_update_index() {
2021 $limit = (int)variable_get('search_cron_limit', 100);
2022
2023 $result = db_query_range("SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, $limit);
2024
2025 foreach ($result as $node) {
2026 _node_index_node($node);
2027 }
2028 }
2029
2030 /**
2031 * Index a single node.
2032 *
2033 * @param $node
2034 * The node to index.
2035 */
2036 function _node_index_node($node) {
2037 $node = node_load($node->nid);
2038
2039 // Save the changed time of the most recent indexed node, for the search
2040 // results half-life calculation.
2041 variable_set('node_cron_last', $node->changed);
2042
2043 // Render the node.
2044 $node = node_build_content($node, 'search_index');
2045 $node->rendered = drupal_render($node->content);
2046
2047 $text = '<h1>' . check_plain($node->title) . '</h1>' . $node->rendered;
2048
2049 // Fetch extra data normally not visible
2050 $extra = module_invoke_all('node_update_index', $node);
2051 foreach ($extra as $t) {
2052 $text .= $t;
2053 }
2054
2055 // Update index
2056 search_index($node->nid, 'node', $text);
2057 }
2058
2059 /**
2060 * Implement hook_form_FORM_ID_alter().
2061 */
2062 function node_form_search_form_alter(&$form, $form_state) {
2063 if ($form['module']['#value'] == 'node' && user_access('use advanced search')) {
2064 // Keyword boxes:
2065 $form['advanced'] = array(
2066 '#type' => 'fieldset',
2067 '#title' => t('Advanced search'),
2068 '#collapsible' => TRUE,
2069 '#collapsed' => TRUE,
2070 '#attributes' => array('class' => 'search-advanced'),
2071 );
2072 $form['advanced']['keywords'] = array(
2073 '#prefix' => '<div class="criterion">',
2074 '#suffix' => '</div>',
2075 );
2076 $form['advanced']['keywords']['or'] = array(
2077 '#type' => 'textfield',
2078 '#title' => t('Containing any of the words'),
2079 '#size' => 30,
2080 '#maxlength' => 255,
2081 );
2082 $form['advanced']['keywords']['phrase'] = array(
2083 '#type' => 'textfield',
2084 '#title' => t('Containing the phrase'),
2085 '#size' => 30,
2086 '#maxlength' => 255,
2087 );
2088 $form['advanced']['keywords']['negative'] = array(
2089 '#type' => 'textfield',
2090 '#title' => t('Containing none of the words'),
2091 '#size' => 30,
2092 '#maxlength' => 255,
2093 );
2094
2095 // Taxonomy box:
2096 if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
2097 $form['advanced']['term'] = array(
2098 '#type' => 'select',
2099 '#title' => t('Only in the term(s)'),
2100 '#prefix' => '<div class="criterion">',
2101 '#size' => 10,
2102 '#suffix' => '</div>',
2103 '#options' => $taxonomy,
2104 '#multiple' => TRUE,
2105 );
2106 }
2107
2108 // Node types:
2109 $types = array_map('check_plain', node_type_get_names());
2110 $form['advanced']['type'] = array(
2111 '#type' => 'checkboxes',
2112 '#title' => t('Only of the type(s)'),
2113 '#prefix' => '<div class="criterion">',
2114 '#suffix' => '</div>',
2115 '#options' => $types,
2116 );
2117 $form['advanced']['submit'] = array(
2118 '#type' => 'submit',
2119 '#value' => t('Advanced search'),
2120 '#prefix' => '<div class="action">',
2121 '#suffix' => '</div>',
2122 );
2123
2124 // Languages:
2125 $language_options = array();
2126 foreach (language_list('language') as $key => $object) {
2127 $language_options[$key] = $object->name;
2128 }
2129 if (count($language_options) > 1) {
2130 $form['advanced']['language'] = array(
2131 '#type' => 'checkboxes',
2132 '#title' => t('Languages'),
2133 '#prefix' => '<div class="criterion">',
2134 '#suffix' => '</div>',
2135 '#options' => $language_options,
2136 );
2137 }
2138
2139 $form['#validate'][] = 'node_search_validate';
2140 }
2141 }
2142
2143 /**
2144 * Form API callback for the search form. Registered in node_form_alter().
2145 */
2146 function node_search_validate($form, &$form_state) {
2147 // Initialize using any existing basic search keywords.
2148 $keys = $form_state['values']['processed_keys'];
2149
2150 // Insert extra restrictions into the search keywords string.
2151 if (isset($form_state['values']['type']) && is_array($form_state['values']['type'])) {
2152 // Retrieve selected types - Forms API sets the value of unselected
2153 // checkboxes to 0.
2154 $form_state['values']['type'] = array_filter($form_state['values']['type']);
2155 if (count($form_state['values']['type'])) {
2156 $keys = search_query_insert($keys, 'type', implode(',', array_keys($form_state['values']['type'])));
2157 }
2158 }
2159
2160 if (isset($form_state['values']['term']) && is_array($form_state['values']['term'])) {
2161 $keys = search_query_insert($keys, 'term', implode(',', $form_state['values']['term']));
2162 }
2163 if (isset($form_state['values']['language']) && is_array($form_state['values']['language'])) {
2164 $keys = search_query_insert($keys, 'language', implode(',', array_filter($form_state['values']['language'])));
2165 }
2166 if ($form_state['values']['or'] != '') {
2167 if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['or'], $matches)) {
2168 $keys .= ' ' . implode(' OR ', $matches[1]);
2169 }
2170 }
2171 if ($form_state['values']['negative'] != '') {
2172 if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['negative'], $matches)) {
2173 $keys .= ' -' . implode(' -', $matches[1]);
2174 }
2175 }
2176 if ($form_state['values']['phrase'] != '') {
2177 $keys .= ' "' . str_replace('"', ' ', $form_state['values']['phrase']) . '"';
2178 }
2179 if (!empty($keys)) {
2180 form_set_value($form['basic']['inline']['processed_keys'], trim($keys), $form_state);
2181 }
2182 }
2183
2184 /**
2185 * @defgroup node_access Node access rights
2186 * @{
2187 * The node access system determines who can do what to which nodes.
2188 *
2189 * In determining access rights for a node, node_access() first checks
2190 * whether the user has the "bypass node access" permission. Such users have
2191 * unrestricted access to all nodes. Then the node module's hook_access()
2192 * is called, and a TRUE or FALSE return value will grant or deny access.
2193 * This allows, for example, the blog module to always grant access to the
2194 * blog author, and for the book module to always deny editing access to
2195 * PHP pages.
2196 *
2197 * If node module does not intervene (returns NULL), then the
2198 * node_access table is used to determine access. All node access
2199 * modules are queried using hook_node_grants() to assemble a list of
2200 * "grant IDs" for the user. This list is compared against the table.
2201 * If any row contains the node ID in question (or 0, which stands for "all
2202 * nodes"), one of the grant IDs returned, and a value of TRUE for the
2203 * operation in question, then access is granted. Note that this table is a
2204 * list of grants; any matching row is sufficient to grant access to the
2205 * node.
2206 *
2207 * In node listings, the process above is followed except that
2208 * hook_access() is not called on each node for performance reasons and for
2209 * proper functioning of the pager system. When adding a node listing to your
2210 * module, be sure to use db_rewrite_sql() to add
2211 * the appropriate clauses to your query for access checks.
2212 *
2213 * To see how to write a node access module of your own, see
2214 * node_access_example.module.
2215 */
2216
2217 /**
2218 * Determine whether the current user may perform the given operation on the
2219 * specified node.
2220 *
2221 * @param $op
2222 * The operation to be performed on the node. Possible values are:
2223 * - "view"
2224 * - "update"
2225 * - "delete"
2226 * - "create"
2227 * @param $node
2228 * The node object (or node array) on which the operation is to be performed,
2229 * or node type (e.g. 'forum') for "create" operation.
2230 * @param $account
2231 * Optional, a user object representing the user for whom the operation is to
2232 * be performed. Determines access for a user other than the current user.
2233 * @return
2234 * TRUE if the operation may be performed.
2235 */
2236 function node_access($op, $node, $account = NULL) {
2237 global $user;
2238
2239 if (!$node || !in_array($op, array('view', 'update', 'delete', 'create'), TRUE)) {
2240 // If there was no node to check against, or the $op was not one of the
2241 // supported ones, we return access denied.
2242 return FALSE;
2243 }
2244 // Convert the node to an object if necessary:
2245 if ($op != 'create') {
2246 $node = (object)$node;
2247 }
2248 // If no user object is supplied, the access check is for the current user.
2249 if (empty($account)) {
2250 $account = $user;
2251 }
2252
2253 if (user_access('bypass node access', $account)) {
2254 return TRUE;
2255 }
2256
2257 if (!user_access('access content', $account)) {
2258 return FALSE;
2259 }
2260
2261 // Can't use node_invoke('access', $node), because the access hook takes the
2262 // $op parameter before the $node parameter.
2263 $base = node_type_get_base($node);
2264 $access = module_invoke($base, 'access', $op, $node, $account);
2265 if (!is_null($access)) {
2266 return $access;
2267 }
2268
2269 // Check if authors can view their own unpublished nodes.
2270 if ($op == 'view' && !$node->status && user_access('view own unpublished content', $account) && $account->uid == $node->uid && $account->uid != 0) {
2271 return TRUE;
2272 }
2273
2274 // If the module did not override the access rights, use those set in the
2275 // node_access table.
2276 if ($op != 'create' && $node->nid) {
2277 $query = db_select('node_access');
2278 $query->addExpression('COUNT(*)');
2279 $query->condition('grant_' . $op, 1, '>=');
2280 $nids = db_or()->condition('nid', $node->nid);
2281 if ($node->status) {
2282 $nids->condition('nid', 0);
2283 }
2284 $query->condition($nids);
2285
2286 $grants = db_or();
2287 foreach (node_access_grants($op, $account) as $realm => $gids) {
2288 foreach ($gids as $gid) {
2289 $grants->condition(db_and()
2290 ->condition('gid', $gid)
2291 ->condition('realm', $realm)
2292 );
2293 }
2294 }
2295 if (count($grants) > 0 ) {
2296 $query->condition($grants);
2297 }
2298 return $query
2299 ->execute()
2300 ->fetchField();
2301 }
2302
2303 return FALSE;
2304 }
2305
2306 /**
2307 * Generate an SQL join clause for use in fetching a node listing.
2308 *
2309 * @param $node_alias
2310 * If the node table has been given an SQL alias other than the default
2311 * "n", that must be passed here.
2312 * @param $node_access_alias
2313 * If the node_access table has been given an SQL alias other than the default
2314 * "na", that must be passed here.
2315 * @return
2316 * An SQL join clause.
2317 */
2318 function _node_access_join_sql($node_alias = 'n', $node_access_alias = 'na') {
2319 if (user_access('bypass node access')) {
2320 return '';
2321 }
2322
2323 return 'INNER JOIN {node_access} ' . $node_access_alias . ' ON ' . $node_access_alias . '.nid = ' . $node_alias . '.nid';
2324 }
2325
2326 /**
2327 * Generate an SQL where clause for use in fetching a node listing.
2328 *
2329 * @param $op
2330 * The operation that must be allowed to return a node.
2331 * @param $node_access_alias
2332 * If the node_access table has been given an SQL alias other than the default
2333 * "na", that must be passed here.
2334 * @param $account
2335 * The user object for the user performing the operation. If omitted, the
2336 * current user is used.
2337 * @return
2338 * An SQL where clause.
2339 */
2340 function _node_access_where_sql($op = 'view', $node_access_alias = 'na', $account = NULL) {
2341 if (user_access('bypass node access')) {
2342 return;
2343 }
2344
2345 $grants = array();
2346 foreach (node_access_grants($op, $account) as $realm => $gids) {
2347 foreach ($gids as $gid) {
2348 $grants[] = "($node_access_alias.gid = $gid AND $node_access_alias.realm = '$realm')";
2349 }
2350 }
2351
2352 $grants_sql = '';
2353 if (count($grants)) {
2354 $grants_sql = 'AND (' . implode(' OR ', $grants) . ')';
2355 }
2356
2357 $sql = "$node_access_alias.grant_$op >= 1 $grants_sql";
2358 return $sql;
2359 }
2360
2361 /**
2362 * Fetch an array of permission IDs granted to the given user ID.
2363 *
2364 * The implementation here provides only the universal "all" grant. A node
2365 * access module should implement hook_node_grants() to provide a grant
2366 * list for the user.
2367 *
2368 * After the default grants have been loaded, we allow modules to alter
2369 * the grants array by reference. This hook allows for complex business
2370 * logic to be applied when integrating multiple node access modules.
2371 *
2372 * @param $op
2373 * The operation that the user is trying to perform.
2374 * @param $account
2375 * The user object for the user performing the operation. If omitted, the
2376 * current user is used.
2377 * @return
2378 * An associative array in which the keys are realms, and the values are
2379 * arrays of grants for those realms.
2380 */
2381 function node_access_grants($op, $account = NULL) {
2382
2383 if (!isset($account)) {
2384 $account = $GLOBALS['user'];
2385 }
2386
2387 // Fetch node access grants from other modules.
2388 $grants = module_invoke_all('node_grants', $account, $op);
2389 // Allow modules to alter the assigned grants.
2390 drupal_alter('node_grants', $grants, $account, $op);
2391
2392 return array_merge(array('all' => array(0)), $grants);
2393 }
2394
2395 /**
2396 * Determine whether the user has a global viewing grant for all nodes.
2397 */
2398 function node_access_view_all_nodes() {
2399 static $access;
2400
2401 if (!isset($access)) {
2402 // If no modules implement the node access system, access is always true.
2403 if (!module_implements('node_grants')) {
2404 $access = TRUE;
2405 }
2406 else {
2407 $query = db_select('node_access');
2408 $query->addExpression('COUNT(*)');
2409 $query
2410 ->condition('nid', 0)
2411 ->condition('grant_view', 1, '>=');
2412
2413 $grants = db_or();
2414 foreach (node_access_grants('view') as $realm => $gids) {
2415 foreach ($gids as $gid) {
2416 $grants->condition(db_and()
2417 ->condition('gid', $gid)
2418 ->condition('realm', $realm)
2419 );
2420 }
2421 }
2422 if (count($grants) > 0 ) {
2423 $query->condition($grants);
2424 }
2425 $access = $query
2426 ->execute()
2427 ->fetchField();
2428 }
2429 }
2430
2431 return $access;
2432 }
2433
2434 /**
2435 * Implement hook_db_rewrite_sql().
2436 */
2437 function node_db_rewrite_sql($query, $primary_table, $primary_field) {
2438 if ($primary_field == 'nid' && !node_access_view_all_nodes()) {
2439 $return['join'] = _node_access_join_sql($primary_table);
2440 $return['where'] = _node_access_where_sql();
2441 $return['distinct'] = 1;
2442 return $return;
2443 }
2444 }
2445
2446 /**
2447 * Implement hook_query_TAG_alter().
2448 */
2449 function node_query_node_access_alter(QueryAlterableInterface $query) {
2450 // Skip the extra expensive alterations if site has no node access control
2451 // modules.
2452 if (!node_access_view_all_nodes()) {
2453 // Prevent duplicate records.
2454 $query->distinct();
2455 // The recognized operations are 'view', 'update', 'delete'.
2456 if (!$op = $query->getMetaData('op')) {
2457 $op = 'view';
2458 }
2459 // Skip the extra joins and conditions for node admins.
2460 if (!user_access('bypass node access')) {
2461 // The node_access table has the access grants for any given node.
2462 $access_alias = $query->join('node_access', 'na', 'na.nid = n.nid');
2463 $or = db_or();
2464 // If any grant exists for the specified user, then user has access to the
2465 // node for the specified operation.
2466 foreach (node_access_grants($op, $query->getMetaData('account')) as $realm => $gids) {
2467 foreach ($gids as $gid) {
2468 $or->condition(db_and()
2469 ->condition("{$access_alias}.gid", $gid)
2470 ->condition("{$access_alias}.realm", $realm)
2471 );
2472 }
2473 }
2474
2475 if (count($or->conditions())) {
2476 $query->condition($or);
2477 }
2478
2479 $query->condition("{$access_alias}.grant_$op", 1, '>=');
2480 }
2481 }
2482 }
2483
2484 /**
2485 * This function will call module invoke to get a list of grants and then
2486 * write them to the database. It is called at node save, and should be
2487 * called by modules whenever something other than a node_save causes
2488 * the permissions on a node to change.
2489 *
2490 * After the default grants have been loaded, we allow modules to alter
2491 * the grants array by reference. This hook allows for complex business
2492 * logic to be applied when integrating multiple node access modules.
2493 *
2494 * @see hook_node_access_records()
2495 *
2496 * This function is the only function that should write to the node_access
2497 * table.
2498 *
2499 * @param $node
2500 * The $node to acquire grants for.
2501 */
2502 function node_access_acquire_grants($node) {
2503 $grants = module_invoke_all('node_access_records', $node);
2504 // Let modules alter the grants.
2505 drupal_alter('node_access_records', $grants, $node);
2506 // If no grants are set, then use the default grant.
2507 if (empty($grants)) {
2508 $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0);
2509 }
2510 else {
2511 // Retain grants by highest priority.
2512 $grant_by_priority = array();
2513 foreach ($grants as $g) {
2514 $grant_by_priority[intval($g['priority'])][] = $g;
2515 }
2516 krsort($grant_by_priority);
2517 $grants = array_shift($grant_by_priority);
2518 }
2519
2520 node_access_write_grants($node, $grants);
2521 }
2522
2523 /**
2524 * This function will write a list of grants to the database, deleting
2525 * any pre-existing grants. If a realm is provided, it will only
2526 * delete grants from that realm, but it will always delete a grant
2527 * from the 'all' realm. Modules which utilize node_access can
2528 * use this function when doing mass updates due to widespread permission
2529 * changes.
2530 *
2531 * @param $node
2532 * The $node being written to. All that is necessary is that it contain a nid.
2533 * @param $grants
2534 * A list of grants to write. Each grant is an array that must contain the
2535 * following keys: realm, gid, grant_view, grant_update, grant_delete.
2536 * The realm is specified by a particular module; the gid is as well, and
2537 * is a module-defined id to define grant privileges. each grant_* field
2538 * is a boolean value.
2539 * @param $realm
2540 * If provided, only read/write grants for that realm.
2541 * @param $delete
2542 * If false, do not delete records. This is only for optimization purposes,
2543 * and assumes the caller has already performed a mass delete of some form.
2544 */
2545 function node_access_write_grants($node, $grants, $realm = NULL, $delete = TRUE) {
2546 if ($delete) {
2547 $query = db_delete('node_access')->condition('nid', $node->nid);
2548 if ($realm) {
2549 $query->condition('realm', array($realm, 'all'), 'IN');
2550 }
2551 $query->execute();
2552 }
2553
2554 // Only perform work when node_access modules are active.
2555 if (count(module_implements('node_grants'))) {
2556 $query = db_insert('node_access')->fields(array('nid', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete'));
2557 foreach ($grants as $grant) {
2558 if ($realm && $realm != $grant['realm']) {
2559 continue;
2560 }
2561 // Only write grants; denies are implicit.
2562 if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
2563 $grant['nid'] = $node->nid;
2564 $query->values($grant);
2565 }
2566 }
2567 $query->execute();
2568 }
2569 }
2570
2571 /**
2572 * Flag / unflag the node access grants for rebuilding, or read the current
2573 * value of the flag.
2574 *
2575 * When the flag is set, a message is displayed to users with 'access
2576 * administration pages' permission, pointing to the 'rebuild' confirm form.
2577 * This can be used as an alternative to direct node_access_rebuild calls,
2578 * allowing administrators to decide when they want to perform the actual
2579 * (possibly time consuming) rebuild.
2580 * When unsure the current user is an adminisrator, node_access_rebuild
2581 * should be used instead.
2582 *
2583 * @param $rebuild
2584 * (Optional) The boolean value to be written.
2585 * @return
2586 * (If no value was provided for $rebuild) The current value of the flag.
2587 */
2588 function node_access_needs_rebuild($rebuild = NULL) {
2589 if (!isset($rebuild)) {
2590 return variable_get('node_access_needs_rebuild', FALSE);
2591 }
2592 elseif ($rebuild) {
2593 variable_set('node_access_needs_rebuild', TRUE);
2594 }
2595 else {
2596 variable_del('node_access_needs_rebuild');
2597 }
2598 }
2599
2600 /**
2601 * Rebuild the node access database. This is occasionally needed by modules
2602 * that make system-wide changes to access levels.
2603 *
2604 * When the rebuild is required by an admin-triggered action (e.g module
2605 * settings form), calling node_access_needs_rebuild(TRUE) instead of
2606 * node_access_rebuild() lets the user perform his changes and actually
2607 * rebuild only once he is done.
2608 *
2609 * Note : As of Drupal 6, node access modules are not required to (and actually
2610 * should not) call node_access_rebuild() in hook_enable/disable anymore.
2611 *
2612 * @see node_access_needs_rebuild()
2613 *
2614 * @param $batch_mode
2615 * Set to TRUE to process in 'batch' mode, spawning processing over several
2616 * HTTP requests (thus avoiding the risk of PHP timeout if the site has a
2617 * large number of nodes).
2618 * hook_update_N and any form submit handler are safe contexts to use the
2619 * 'batch mode'. Less decidable cases (such as calls from hook_user,
2620 * hook_taxonomy, etc...) might consider using the non-batch mode.
2621 */
2622 function node_access_rebuild($batch_mode = FALSE) {
2623 db_delete('node_access')->execute();
2624 // Only recalculate if the site is using a node_access module.
2625 if (count(module_implements('node_grants'))) {
2626 if ($batch_mode) {
2627 $batch = array(
2628 'title' => t('Rebuilding content access permissions'),
2629 'operations' => array(
2630 array('_node_access_rebuild_batch_operation', array()),
2631 ),
2632 'finished' => '_node_access_rebuild_batch_finished'
2633 );
2634 batch_set($batch);
2635 }
2636 else {
2637 // Try to allocate enough time to rebuild node grants
2638 drupal_set_time_limit(240);
2639
2640 $nids = db_query("SELECT nid FROM {node}")->fetchCol();
2641 foreach ($nids as $nid) {
2642 $node = node_load($nid, NULL, TRUE);
2643 // To preserve database integrity, only acquire grants if the node
2644 // loads successfully.
2645 if (!empty($node)) {
2646 node_access_acquire_grants($node);
2647 }
2648 }
2649 }
2650 }
2651 else {
2652 // Not using any node_access modules. Add the default grant.
2653 db_insert('node_access')
2654 ->fields(array(
2655 'nid' => 0,
2656 'realm' => 'all',
2657 'gid' => 0,
2658 'grant_view' => 1,
2659 'grant_update' => 0,
2660 'grant_delete' => 0,
2661 ))
2662 ->execute();
2663 }
2664
2665 if (!isset($batch)) {
2666 drupal_set_message(t('Content permissions have been rebuilt.'));
2667 node_access_needs_rebuild(FALSE);
2668 cache_clear_all();
2669 }
2670 }
2671
2672 /**
2673 * Batch operation for node_access_rebuild_batch.
2674 *
2675 * This is a multistep operation : we go through all nodes by packs of 20.
2676 * The batch processing engine interrupts processing and sends progress
2677 * feedback after 1 second execution time.
2678 */
2679 function _node_access_rebuild_batch_operation(&$context) {
2680 if (empty($context['sandbox'])) {
2681 // Initiate multistep processing.
2682 $context['sandbox']['progress'] = 0;
2683 $context['sandbox']['current_node'] = 0;
2684 $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField();
2685 }
2686
2687 // Process the next 20 nodes.
2688 $limit = 20;
2689 $nids = db_query_range("SELECT nid FROM {node} WHERE nid > :nid ORDER BY nid ASC", array(':nid' => $context['sandbox']['current_node']), 0, $limit)->fetchCol();
2690 $nodes = node_load_multiple($nids, array(), TRUE);
2691 foreach ($nodes as $node) {
2692 // To preserve database integrity, only acquire grants if the node
2693 // loads successfully.
2694 if (!empty($node)) {
2695 node_access_acquire_grants($node);
2696 }
2697 $context['sandbox']['progress']++;
2698 $context['sandbox']['current_node'] = $node->nid;
2699 }
2700
2701 // Multistep processing : report progress.
2702 if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
2703 $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
2704 }
2705 }
2706
2707 /**
2708 * Post-processing for node_access_rebuild_batch.
2709 */
2710 function _node_access_rebuild_batch_finished($success, $results, $operations) {
2711 if ($success) {
2712 drupal_set_message(t('The content access permissions have been rebuilt.'));
2713 node_access_needs_rebuild(FALSE);
2714 }
2715 else {
2716 drupal_set_message(t('The content access permissions have not been properly rebuilt.'), 'error');
2717 }
2718 cache_clear_all();
2719 }
2720
2721 /**
2722 * @} End of "defgroup node_access".
2723 */
2724
2725
2726 /**
2727 * @defgroup node_content Hook implementations for user-created content types.
2728 * @{
2729 */
2730
2731 /**
2732 * Implement hook_access().
2733 *
2734 * Named so as not to conflict with node_access()
2735 */
2736 function node_content_access($op, $node, $account) {
2737 $type = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
2738
2739 if ($op == 'create') {
2740 return user_access('create ' . $type . ' content', $account);
2741 }
2742
2743 if ($op == 'update') {
2744 if (user_access('edit any ' . $type . ' content', $account) || (user_access('edit own ' . $type . ' content', $account) && ($account->uid == $node->uid))) {
2745 return TRUE;
2746 }
2747 }
2748
2749 if ($op == 'delete') {
2750 if (user_access('delete any ' . $type . ' content', $account) || (user_access('delete own ' . $type . ' content', $account) && ($account->uid == $node->uid))) {
2751 return TRUE;
2752 }
2753 }
2754 }
2755
2756 /**
2757 * Implement hook_form().
2758 */
2759 function node_content_form($node, $form_state) {
2760
2761 $type = node_type_get_type($node);
2762
2763 $form = array();
2764
2765 if ($type->has_title) {
2766 $form['title'] = array(
2767 '#type' => 'textfield',
2768 '#title' => check_plain($type->title_label),
2769 '#required' => TRUE,
2770 '#default_value' => $node->title,
2771 '#maxlength' => 255,
2772 '#weight' => -5,
2773 );
2774 }
2775
2776 return $form;
2777 }
2778
2779 /**
2780 * @} End of "defgroup node_content".
2781 */
2782
2783 /**
2784 * Implement hook_forms().
2785 * All node forms share the same form handler.
2786 */
2787 function node_forms() {
2788 $forms = array();
2789 if ($types = node_type_get_types()) {
2790 foreach (array_keys($types) as $type) {
2791 $forms[$type . '_node_form']['callback'] = 'node_form';
2792 }
2793 }
2794 return $forms;
2795 }
2796
2797 /**
2798 * Format the "Submitted by username on date/time" for each node
2799 *
2800 * @ingroup themeable
2801 */
2802 function theme_node_submitted($node) {
2803 return t('Submitted by !username on @datetime',
2804 array(
2805 '!username' => theme('username', $node),
2806 '@datetime' => format_date($node->created),
2807 ));
2808 }
2809
2810 /**
2811 * Implement hook_hook_info().
2812 */
2813 function node_hook_info() {
2814 return array(
2815 'node' => array(
2816 'node' => array(
2817 'presave' => array(
2818 'runs when' => t('When either saving a new post or updating an existing post'),
2819 ),
2820 'insert' => array(
2821 'runs when' => t('After saving a new post'),
2822 ),
2823 'update' => array(
2824 'runs when' => t('After saving an updated post'),
2825 ),
2826 'delete' => array(
2827 'runs when' => t('After deleting a post')
2828 ),
2829 'view' => array(
2830 'runs when' => t('When content is viewed by an authenticated user')
2831 ),
2832 ),
2833 ),
2834 );
2835 }
2836
2837 /**
2838 * Implement hook_action_info().
2839 */
2840 function node_action_info() {
2841 return array(
2842 'node_publish_action' => array(
2843 'type' => 'node',
2844 'description' => t('Publish post'),
2845 'configurable' => FALSE,
2846 'behavior' => array('changes_node_property'),
2847 'hooks' => array(
2848 'node' => array('presave'),
2849 'comment' => array('insert', 'update'),
2850 ),
2851 ),
2852 'node_unpublish_action' => array(
2853 'type' => 'node',
2854 'description' => t('Unpublish post'),
2855 'configurable' => FALSE,
2856 'behavior' => array('changes_node_property'),
2857 'hooks' => array(
2858 'node' => array('presave'),
2859 'comment' => array('delete', 'insert', 'update'),
2860 ),
2861 ),
2862 'node_make_sticky_action' => array(
2863 'type' => 'node',
2864 'description' => t('Make post sticky'),
2865 'configurable' => FALSE,
2866 'behavior' => array('changes_node_property'),
2867 'hooks' => array(
2868 'node' => array('presave'),
2869 'comment' => array('insert', 'update'),
2870 ),
2871 ),
2872 'node_make_unsticky_action' => array(
2873 'type' => 'node',
2874 'description' => t('Make post unsticky'),
2875 'configurable' => FALSE,
2876 'behavior' => array('changes_node_property'),
2877 'hooks' => array(
2878 'node' => array('presave'),
2879 'comment' => array('delete', 'insert', 'update'),
2880 ),
2881 ),
2882 'node_promote_action' => array(
2883 'type' => 'node',
2884 'description' => t('Promote post to front page'),
2885 'configurable' => FALSE,
2886 'behavior' => array('changes_node_property'),
2887 'hooks' => array(
2888 'node' => array('presave'),
2889 'comment' => array('insert', 'update'),
2890 ),
2891 ),
2892 'node_unpromote_action' => array(
2893 'type' => 'node',
2894 'description' => t('Remove post from front page'),
2895 'configurable' => FALSE,
2896 'behavior' => array('changes_node_property'),
2897 'hooks' => array(
2898 'node' => array('presave'),
2899 'comment' => array('delete', 'insert', 'update'),
2900 ),
2901 ),
2902 'node_assign_owner_action' => array(
2903 'type' => 'node',
2904 'description' => t('Change the author of a post'),
2905 'configurable' => TRUE,
2906 'behavior' => array('changes_node_property'),
2907 'hooks' => array(
2908 'any' => TRUE,
2909 'node' => array('presave'),
2910 'comment' => array('delete', 'insert', 'update'),
2911 ),
2912 ),
2913 'node_save_action' => array(
2914 'type' => 'node',
2915 'description' => t('Save post'),
2916 'configurable' => FALSE,
2917 'hooks' => array(
2918 'comment' => array('delete', 'insert', 'update'),
2919 ),
2920 ),
2921 'node_unpublish_by_keyword_action' => array(
2922 'type' => 'node',
2923 'description' => t('Unpublish post containing keyword(s)'),
2924 'configurable' => TRUE,
2925 'hooks' => array(
2926 'node' => array('presave', 'insert', 'update'),
2927 ),
2928 ),
2929 );
2930 }
2931
2932 /**
2933 * Implement a Drupal action.
2934 * Sets the status of a node to 1, meaning published.
2935 */
2936 function node_publish_action($node, $context = array()) {
2937 $node->status = 1;
2938 watchdog('action', 'Set @type %title to published.', array('@type' => node_type_get_name($node), '%title' => $node->title));
2939 }
2940
2941 /**
2942 * Implement a Drupal action.
2943 * Sets the status of a node to 0, meaning unpublished.
2944 */
2945 function node_unpublish_action($node, $context = array()) {
2946 $node->status = 0;
2947 watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_type_get_name($node), '%title' => $node->title));
2948 }
2949
2950 /**
2951 * Implement a Drupal action.
2952 * Sets the sticky-at-top-of-list property of a node to 1.
2953 */
2954 function node_make_sticky_action($node, $context = array()) {
2955 $node->sticky = 1;
2956 watchdog('action', 'Set @type %title to sticky.', array('@type' => node_type_get_name($node), '%title' => $node->title));
2957 }
2958
2959 /**
2960 * Implement a Drupal action.
2961 * Sets the sticky-at-top-of-list property of a node to 0.
2962 */
2963 function node_make_unsticky_action($node, $context = array()) {
2964 $node->sticky = 0;
2965 watchdog('action', 'Set @type %title to unsticky.', array('@type' => node_type_get_name($node), '%title' => $node->title));
2966 }
2967
2968 /**
2969 * Implement a Drupal action.
2970 * Sets the promote property of a node to 1.
2971 */
2972 function node_promote_action($node, $context = array()) {
2973 $node->promote = 1;
2974 watchdog('action', 'Promoted @type %title to front page.', array('@type' => node_type_get_name($node), '%title' => $node->title));
2975 }
2976
2977 /**
2978 * Implement a Drupal action.
2979 * Sets the promote property of a node to 0.
2980 */
2981 function node_unpromote_action($node, $context = array()) {
2982 $node->promote = 0;
2983 watchdog('action', 'Removed @type %title from front page.', array('@type' => node_type_get_name($node), '%title' => $node->title));
2984 }
2985
2986 /**
2987 * Implement a Drupal action.
2988 * Saves a node.
2989 */
2990 function node_save_action($node) {
2991 node_save($node);
2992 watchdog('action', 'Saved @type %title', array('@type' => node_type_get_name($node), '%title' => $node->title));
2993 }
2994
2995 /**
2996 * Implement a configurable Drupal action.
2997 * Assigns ownership of a node to a user.
2998 */
2999 function node_assign_owner_action($node, $context) {
3000 $node->uid = $context['owner_uid'];
3001 $owner_name = db_query("SELECT name FROM {users} WHERE uid = :uid", array(':uid' => $context['owner_uid']))->fetchField();
3002 watchdog('action', 'Changed owner of @type %title to uid %name.', array('@type' => node_type_get_type($node), '%title' => $node->title, '%name' => $owner_name));
3003 }
3004
3005 function node_assign_owner_action_form($context) {
3006 $description = t('The username of the user to which you would like to assign ownership.');
3007 $count = db_query("SELECT COUNT(*) FROM {users}")->fetchField();
3008 $owner_name = '';
3009 if (isset($context['owner_uid'])) {
3010 $owner_name = db_query("SELECT name FROM {users} WHERE uid = :uid", array(':uid' => $context['owner_uid']))->fetchField();
3011 }
3012
3013 // Use dropdown for fewer than 200 users; textbox for more than that.
3014 if (intval($count) < 200) {
3015 $options = array();
3016 $result = db_query("SELECT uid, name FROM {users} WHERE uid > 0 ORDER BY name");
3017 foreach ($result as $data) {
3018 $options[$data->name] = $data->name;
3019 }
3020 $form['owner_name'] = array(
3021 '#type' => 'select',
3022 '#title' => t('Username'),
3023 '#default_value' => $owner_name,
3024 '#options' => $options,
3025 '#description' => $description,
3026 );
3027 }
3028 else {
3029 $form['owner_name'] = array(
3030 '#type' => 'textfield',
3031 '#title' => t('Username'),
3032 '#default_value' => $owner_name,
3033 '#autocomplete_path' => 'user/autocomplete',
3034 '#size' => '6',
3035 '#maxlength' => '7',
3036 '#description' => $description,
3037 );
3038 }
3039 return $form;
3040 }
3041
3042 function node_assign_owner_action_validate($form, $form_state) {
3043 $exists = (bool) db_query_range('SELECT 1 FROM {users} WHERE name = :name', array(':name' => $form_state['values']['owner_name']), 0, 1)->fetchField();
3044 if (!$exists) {
3045 form_set_error('owner_name', t('Please enter a valid username.'));
3046 }
3047 }
3048
3049 function node_assign_owner_action_submit($form, $form_state) {
3050 // Username can change, so we need to store the ID, not the username.
3051 $uid = db_query('SELECT uid from {users} WHERE name = :name', array(':name' => $form_state['values']['owner_name']))->fetchField();
3052 return array('owner_uid' => $uid);
3053 }
3054
3055 function node_unpublish_by_keyword_action_form($context) {
3056 $form['keywords'] = array(
3057 '#title' => t('Keywords'),
3058 '#type' => 'textarea',
3059 '#description' => t('The post will be unpublished if it contains any of the phrases above. Use a case-sensitive, comma-separated list of phrases. Example: funny, bungee jumping, "Company, Inc."'),
3060 '#default_value' => isset($context['keywords']) ? drupal_implode_tags($context['keywords']) : '',
3061 );
3062 return $form;
3063 }
3064
3065 function node_unpublish_by_keyword_action_submit($form, $form_state) {
3066 return array('keywords' => drupal_explode_tags($form_state['values']['keywords']));
3067 }
3068
3069 /**
3070 * Implement a configurable Drupal action.
3071 * Unpublish a node if it contains a certain string.
3072 *
3073 * @param $context
3074 * An array providing more information about the context of the call to this
3075 * action.
3076 * @param $comment
3077 * A node object.
3078 */
3079 function node_unpublish_by_keyword_action($node, $context) {
3080 foreach ($context['keywords'] as $keyword) {
3081 if (strpos(drupal_render(node_build(clone $node)), $keyword) !== FALSE || strpos($node->title, $keyword) !== FALSE) {
3082 $node->status = 0;
3083 watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_type_get_name($node), '%title' => $node->title));
3084 break;
3085 }
3086 }
3087 }
3088
3089 /**
3090 * Helper function to generate standard node permission list for a given type.
3091 *
3092 * @param $type
3093 * The machine-readable name of the node type.
3094 * @return array
3095 * An array of permission names and descriptions.
3096 */
3097 function node_list_permissions($type) {
3098 $info = node_type_get_type($type);
3099 $type = check_plain($info->type);
3100
3101 // Build standard list of node permissions for this type.
3102 $perms = array(
3103 "create $type content" => array(
3104 'title' => t('Create %type_name content', array('%type_name' => $info->name)),
3105 'description' => t('Create new %type_name content.', array('%type_name' => $info->name)),
3106 ),
3107 "edit own $type content" => array(
3108 'title' => t('Edit own %type_name content', array('%type_name' => $info->name)),
3109 'description' => t('Edit %type_name content created by the user.', array('%type_name' => $info->name)),
3110 ),
3111 "edit any $type content" => array(
3112 'title' => t('Edit any %type_name content', array('%type_name' => $info->name)),
3113 'description' => t('Edit any %type_name content, regardless of its author.', array('%type_name' => $info->name)),
3114 ),
3115 "delete own $type content" => array(
3116 'title' => t('Delete own %type_name content', array('%type_name' => $info->name)),
3117 'description' => t('Delete %type_name content created by the user.', array('%type_name' => $info->name)),
3118 ),
3119 "delete any $type content" => array(
3120 'title' => t('Delete any %type_name content', array('%type_name' => $info->name)),
3121 'description' => t('Delete any %type_name content, regardless of its author.', array('%type_name' => $info->name)),
3122 ),
3123 );
3124
3125 return $perms;
3126 }
3127
3128 /**
3129 * Implement hook_requirements().
3130 */
3131 function node_requirements($phase) {
3132 $requirements = array();
3133 // Ensure translations don't break at install time
3134 $t = get_t();
3135 // Only show rebuild button if there are either 0, or 2 or more, rows
3136 // in the {node_access} table, or if there are modules that
3137 // implement hook_node_grants().
3138 $grant_count = db_query('SELECT COUNT(*) FROM {node_access}')->fetchField();
3139 if ($grant_count != 1 || count(module_implements('node_grants')) > 0) {
3140 $value = format_plural($grant_count, 'One permission in use', '@count permissions in use', array('@count' => $grant_count));
3141 } else {
3142 $value = $t('Disabled');
3143 }
3144 $description = $t('If the site is experiencing problems with permissions to content, you may have to rebuild the permissions cache. Rebuilding will remove all privileges to posts, and replace them with permissions based on the current modules and settings. Rebuilding may take some time if there is a lot of content or complex permission settings. After rebuilding has completed posts will automatically use the new permissions.');
3145
3146 $requirements['node_access'] = array(
3147 'title' => $t('Node Access Permissions'),
3148 'value' => $value,
3149 'description' => $description . ' ' . l(t('Rebuild permissions'), 'admin/reports/status/rebuild'),
3150 );
3151 return $requirements;
3152 }
3153

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.