2 // $Id: node.module 144 2007-03-28 07:52:20Z thierry $
6 * The core that allows content to be submitted to the site.
9 // PlanetLab variable definitions
10 include_once 'plc_config.php';
12 define('NODE_NEW_LIMIT', time() - 30 * 24 * 60 * 60);
15 * Implementation of hook_help().
17 function node_help($section) {
19 case 'admin/help#node':
20 $output = '<p>'. t('All content in a website is stored and treated as <b>nodes</b>. Therefore nodes are any postings such as blogs, stories, polls and forums. The node module manages these content types and is one of the strengths of Drupal over other content management systems.') .'</p>';
21 $output .= '<p>'. t('Treating all content as nodes allows the flexibility of creating new types of content. It also allows you to painlessly apply new features or changes to all content. Comments are not stored as nodes but are always associated with a node.') .'</p>';
22 $output .= t('<p>Node module features</p>
24 <li>The list tab provides an interface to search and sort all content on your site.</li>
25 <li>The configure settings tab has basic settings for content on your site.</li>
26 <li>The configure content types tab lists all content types for your site and lets you configure their default workflow.</li>
27 <li>The search tab lets you search all content on your site</li>
30 $output .= t('<p>You can</p>
32 <li>search for content at <a href="%search">search</a>.</li>
33 <li>administer nodes at <a href="%admin-settings-content-types">administer >> settings >> content types</a>.</li>
35 ', array('%search' => url('search'), '%admin-settings-content-types' => url('admin/settings/content-types')));
36 $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="%node">Node page</a>.', array('%node' => 'http://drupal.org/handbook/modules/node/')) .'</p>';
38 case 'admin/modules#description':
39 return t('Allows content to be submitted to the site and displayed on pages.');
40 case 'admin/node/configure':
41 case 'admin/node/configure/settings':
42 return t('<p>Settings for the core of Drupal. Almost everything is a node so these settings will affect most of the site.</p>');
44 return t('<p>Below is a list of all of the posts on your site. Other forms of content are listed elsewhere (e.g. <a href="%comments">comments</a>).</p><p>Clicking a title views the post, while clicking an author\'s name views their user information.</p>', array('%comments' => url('admin/comment')));
45 case 'admin/node/search':
46 return t('<p>Enter a simple pattern to search for a post. This can include the wildcard character *.<br />For example, a search for "br*" might return "bread bakers", "our daily bread" and "brenda".</p>');
49 if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == 'revisions') {
50 return t('The revisions let you track differences between multiple versions of a post.');
53 if (arg(0) == 'node' && arg(1) == 'add' && $type = arg(2)) {
54 return filter_xss_admin(variable_get($type .'_help', ''));
59 * Implementation of hook_cron().
61 function node_cron() {
62 db_query('DELETE FROM {history} WHERE timestamp < %d', NODE_NEW_LIMIT);
66 * Gather a listing of links to nodes.
69 * A DB result object from a query to fetch node objects. If your query joins the <code>node_comment_statistics</code> table so that the <code>comment_count</code> field is available, a title attribute will be added to show the number of comments.
71 * A heading for the resulting list.
74 * An HTML list suitable as content for a block.
76 function node_title_list($result, $title = NULL) {
77 while ($node = db_fetch_object($result)) {
78 $items[] = l($node->title, 'node/'. $node->nid, $node->comment_count ? array('title' => format_plural($node->comment_count, '1 comment', '%count comments')) : '');
81 return theme('node_list', $items, $title);
85 * Format a listing of links to nodes.
87 function theme_node_list($items, $title = NULL) {
88 return theme('item_list', $items, $title);
92 * Update the 'last viewed' timestamp of the specified node for current user.
94 function node_tag_new($nid) {
98 if (node_last_viewed($nid)) {
99 db_query('UPDATE {history} SET timestamp = %d WHERE uid = %d AND nid = %d', time(), $user->uid, $nid);
102 @db_query('INSERT INTO {history} (uid, nid, timestamp) VALUES (%d, %d, %d)', $user->uid, $nid, time());
108 * Retrieves the timestamp at which the current user last viewed the
111 function node_last_viewed($nid) {
115 if (!isset($history[$nid])) {
116 $history[$nid] = db_fetch_object(db_query("SELECT timestamp FROM {history} WHERE uid = '$user->uid' AND nid = %d", $nid));
119 return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp : 0);
123 * Decide on the type of marker to be displayed for a given node.
126 * Node ID whose history supplies the "last viewed" timestamp.
128 * Time which is compared against node's "last viewed" timestamp.
130 * One of the MARK constants.
132 function node_mark($nid, $timestamp) {
139 if (!isset($cache[$nid])) {
140 $cache[$nid] = node_last_viewed($nid);
142 if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) {
145 elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) {
152 * Automatically generate a teaser for a node body in a given format.
154 function node_teaser($body, $format = NULL) {
156 $size = variable_get('teaser_length', 600);
158 // find where the delimiter is in the body
159 $delimiter = strpos($body, '<!--break-->');
161 // If the size is zero, and there is no delimiter, the entire body is the teaser.
162 if ($size == 0 && $delimiter === FALSE) {
166 // We check for the presence of the PHP evaluator filter in the current
167 // format. If the body contains PHP code, we do not split it up to prevent
169 if (isset($format)) {
170 $filters = filter_list_format($format);
171 if (isset($filters['filter/1']) && strpos($body, '<?') !== FALSE) {
176 // If a valid delimiter has been specified, use it to chop of the teaser.
177 if ($delimiter !== FALSE) {
178 return substr($body, 0, $delimiter);
181 // If we have a short body, the entire body is the teaser.
182 if (strlen($body) < $size) {
186 // In some cases, no delimiter has been specified (e.g. when posting using
187 // the Blogger API). In this case, we try to split at paragraph boundaries.
188 // When even the first paragraph is too long, we try to split at the end of
189 // the next sentence.
190 $breakpoints = array('</p>' => 4, '<br />' => 0, '<br>' => 0, "\n" => 0, '. ' => 1, '! ' => 1, '? ' => 1, '。' => 3, '؟ ' => 1);
191 foreach ($breakpoints as $point => $charnum) {
192 if ($length = strpos($body, $point, $size)) {
193 return substr($body, 0, $length + $charnum);
197 // If all else fails, we simply truncate the string.
198 return truncate_utf8($body, $size);
201 function _node_names($op = '', $node = NULL) {
202 static $node_names = array();
203 static $node_list = array();
205 if (empty($node_names)) {
206 $node_names = module_invoke_all('node_info');
207 foreach ($node_names as $type => $value) {
208 $node_list[$type] = $value['name'];
212 if (is_array($node)) {
213 $type = $node['type'];
215 elseif (is_object($node)) {
218 elseif (is_string($node)) {
221 if (!isset($node_names[$type])) {
227 return $node_names[$type]['base'];
231 return $node_list[$type];
236 * Determine the basename for hook_load etc.
239 * Either a node object, a node array, or a string containing the node type.
241 * The basename for hook_load, hook_nodeapi etc.
243 function node_get_base($node) {
244 return _node_names('base', $node);
248 * Determine the human readable name for a given type.
251 * Either a node object, a node array, or a string containing the node type.
253 * The human readable name of the node type.
255 function node_get_name($node) {
256 return _node_names('name', $node);
260 * Return the list of available node types.
263 * Either a node object, a node array, or a string containing the node type.
265 * An array consisting ('#type' => name) pairs.
267 function node_get_types() {
268 return _node_names('list');
272 * Determine whether a node hook exists.
275 * Either a node object, node array, or a string containing the node type.
277 * A string containing the name of the hook.
279 * TRUE iff the $hook exists in the node type of $node.
281 function node_hook(&$node, $hook) {
282 return module_hook(node_get_base($node), $hook);
286 * Invoke a node hook.
289 * Either a node object, node array, or a string containing the node type.
291 * A string containing the name of the hook.
292 * @param $a2, $a3, $a4
293 * Arguments to pass on to the hook, after the $node argument.
295 * The returned value of the invoked hook.
297 function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
298 if (node_hook($node, $hook)) {
299 $function = node_get_base($node) ."_$hook";
300 return ($function($node, $a2, $a3, $a4));
305 * Invoke a hook_nodeapi() operation in all modules.
310 * A string containing the name of the nodeapi operation.
312 * Arguments to pass on to the hook, after the $node and $op arguments.
314 * The returned value of the invoked hooks.
316 function node_invoke_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
318 foreach (module_implements('nodeapi') as $name) {
319 $function = $name .'_nodeapi';
320 $result = $function($node, $op, $a3, $a4);
321 if (isset($result) && is_array($result)) {
322 $return = array_merge($return, $result);
324 else if (isset($result)) {
332 * Load a node object from the database.
335 * Either the nid of the node or an array of conditions to match against in the database query
337 * Which numbered revision to load. Defaults to the current version.
339 * Whether to reset the internal node_load cache.
342 * A fully-populated node object.
344 function node_load($param = array(), $revision = NULL, $reset = NULL) {
345 static $nodes = array();
351 $arguments = array();
352 if (is_numeric($param)) {
353 $cachable = $revision == NULL;
354 if ($cachable && isset($nodes[$param])) {
355 return $nodes[$param];
357 $cond = 'n.nid = %d';
358 $arguments[] = $param;
361 // Turn the conditions into a query.
362 foreach ($param as $key => $value) {
363 $cond[] = 'n.'. db_escape_string($key) ." = '%s'";
364 $arguments[] = $value;
366 $cond = implode(' AND ', $cond);
369 // Retrieve the node.
370 // No db_rewrite_sql is applied so as to get complete indexing for search.
372 array_unshift($arguments, $revision);
373 $node = db_fetch_object(db_query('SELECT n.nid, r.vid, n.type, n.status, n.created, n.changed, n.comment, n.promote, n.moderate, n.sticky, r.timestamp AS revision_timestamp, r.title, r.body, r.teaser, r.log, r.format, u.uid, u.name, u.picture, u.data FROM {node} n INNER JOIN {users} u ON u.uid = n.uid INNER JOIN {node_revisions} r ON r.nid = n.nid AND r.vid = %d WHERE '. $cond, $arguments));
376 $node = db_fetch_object(db_query('SELECT n.nid, n.vid, n.type, n.status, n.created, n.changed, n.comment, n.promote, n.moderate, n.sticky, r.timestamp AS revision_timestamp, r.title, r.body, r.teaser, r.log, r.format, u.uid, u.name, u.picture, u.data FROM {node} n INNER JOIN {users} u ON u.uid = n.uid INNER JOIN {node_revisions} r ON r.vid = n.vid WHERE '. $cond, $arguments));
380 // Call the node specific callback (if any) and piggy-back the
381 // results to the node or overwrite some values.
382 if ($extra = node_invoke($node, 'load')) {
383 foreach ($extra as $key => $value) {
384 $node->$key = $value;
388 if ($extra = node_invoke_nodeapi($node, 'load')) {
389 foreach ($extra as $key => $value) {
390 $node->$key = $value;
396 $nodes[$param] = $node;
403 * Save a node object into the database.
405 function node_save(&$node) {
408 $node->is_new = false;
410 // Apply filters to some default node fields:
411 if (empty($node->nid)) {
412 // Insert a new node.
413 $node->is_new = true;
415 $node->nid = db_next_id('{node}_nid');
416 $node->vid = db_next_id('{node_revisions}_vid');;
419 // We need to ensure that all node fields are filled.
420 $node_current = node_load($node->nid);
421 foreach ($node as $field => $data) {
422 $node_current->$field = $data;
424 $node = $node_current;
426 if ($node->revision) {
427 $node->old_vid = $node->vid;
428 $node->vid = db_next_id('{node_revisions}_vid');
432 // Set some required fields:
433 if (empty($node->created)) {
434 $node->created = time();
436 // The changed timestamp is always updated for bookkeeping purposes (revisions, searching, ...)
437 $node->changed = time();
439 // Split off revisions data to another structure
440 $revisions_table_values = array('nid' => $node->nid, 'vid' => $node->vid,
441 'title' => $node->title, 'body' => $node->body,
442 'teaser' => $node->teaser, 'log' => $node->log, 'timestamp' => $node->changed,
443 'uid' => $user->uid, 'format' => $node->format);
444 $revisions_table_types = array('nid' => '%d', 'vid' => '%d',
445 'title' => "'%s'", 'body' => "'%s'",
446 'teaser' => "'%s'", 'log' => "'%s'", 'timestamp' => '%d',
447 'uid' => '%d', 'format' => '%d');
448 $node_table_values = array('nid' => $node->nid, 'vid' => $node->vid,
449 'title' => $node->title, 'type' => $node->type, 'uid' => $node->uid,
450 'status' => $node->status, 'created' => $node->created,
451 'changed' => $node->changed, 'comment' => $node->comment,
452 'promote' => $node->promote, 'moderate' => $node->moderate,
453 'sticky' => $node->sticky);
454 $node_table_types = array('nid' => '%d', 'vid' => '%d',
455 'title' => "'%s'", 'type' => "'%s'", 'uid' => '%d',
456 'status' => '%d', 'created' => '%d',
457 'changed' => '%d', 'comment' => '%d',
458 'promote' => '%d', 'moderate' => '%d',
461 //Generate the node table query and the
462 //the node_revisions table query
464 $node_query = 'INSERT INTO {node} ('. implode(', ', array_keys($node_table_types)) .') VALUES ('. implode(', ', $node_table_types) .')';
465 $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')';
469 foreach ($node_table_types as $key => $value) {
470 $arr[] = $key .' = '. $value;
472 $node_table_values[] = $node->nid;
473 $node_query = 'UPDATE {node} SET '. implode(', ', $arr) .' WHERE nid = %d';
474 if ($node->revision) {
475 $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')';
479 foreach ($revisions_table_types as $key => $value) {
480 $arr[] = $key .' = '. $value;
482 $revisions_table_values[] = $node->vid;
483 $revisions_query = 'UPDATE {node_revisions} SET '. implode(', ', $arr) .' WHERE vid = %d';
487 // Insert the node into the database:
488 db_query($node_query, $node_table_values);
489 db_query($revisions_query, $revisions_table_values);
491 // Call the node specific callback (if any):
493 node_invoke($node, 'insert');
494 node_invoke_nodeapi($node, 'insert');
497 node_invoke($node, 'update');
498 node_invoke_nodeapi($node, 'update');
501 // Clear the cache so an anonymous poster can see the node being added or updated.
506 * Generate a display of the given node.
509 * A node array or node object.
511 * Whether to display the teaser only, as on the main page.
513 * Whether the node is being displayed by itself as a page.
515 * Whether or not to display node links. Links are omitted for node previews.
518 * An HTML representation of the themed node.
520 function node_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) {
521 $node = (object)$node;
523 // Remove the delimiter (if any) that separates the teaser from the body.
524 // TODO: this strips legitimate uses of '<!--break-->' also.
525 $node->body = str_replace('<!--break-->', '', $node->body);
527 if ($node->log != '' && !$teaser && $node->moderate) {
528 $node->body .= '<div class="log"><div class="title">'. t('Log') .':</div>'. filter_xss($node->log) .'</div>';
531 // The 'view' hook can be implemented to overwrite the default function
533 if (node_hook($node, 'view')) {
534 node_invoke($node, 'view', $teaser, $page);
537 $node = node_prepare($node, $teaser);
539 // Allow modules to change $node->body before viewing.
540 node_invoke_nodeapi($node, 'view', $teaser, $page);
542 $node->links = module_invoke_all('link', 'node', $node, !$page);
544 // unset unused $node part so that a bad theme can not open a security hole
549 unset($node->teaser);
552 return theme('node', $node, $teaser, $page);
556 * Apply filters to a node in preparation for theming.
558 function node_prepare($node, $teaser = FALSE) {
559 $node->readmore = (strlen($node->teaser) < strlen($node->body));
560 if ($teaser == FALSE) {
561 $node->body = check_markup($node->body, $node->format, FALSE);
564 $node->teaser = check_markup($node->teaser, $node->format, FALSE);
570 * Generate a page displaying a single node, along with its comments.
572 function node_show($node, $cid) {
573 $output = node_view($node, FALSE, TRUE);
575 if (function_exists('comment_render') && $node->comment) {
576 $output .= comment_render($node, $cid);
579 // Update the history table, stating that this user viewed this node.
580 node_tag_new($node->nid);
586 * Implementation of hook_perm().
588 function node_perm() {
589 return array('administer nodes', 'access content', 'view revisions', 'revert revisions');
593 * Implementation of hook_search().
595 function node_search($op = 'search', $keys = null) {
601 variable_del('node_cron_last');
602 variable_del('node_cron_last_nid');
606 $last = variable_get('node_cron_last', 0);
607 $last_nid = variable_get('node_cron_last_nid', 0);
608 $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1'));
609 $remaining = db_result(db_query('SELECT COUNT(*) FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND ((GREATEST(n.created, n.changed, c.last_comment_timestamp) = %d AND n.nid > %d ) OR (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d))', $last, $last_nid, $last, $last, $last));
610 return array('remaining' => $remaining, 'total' => $total);
614 // Output form for defining rank factor weights.
615 $form['content_ranking'] = array('#type' => 'fieldset', '#title' => t('Content ranking'));
616 $form['content_ranking']['#theme'] = 'node_search_admin';
617 $form['content_ranking']['info'] = array('#type' => 'markup', '#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>');
619 $ranking = array('node_rank_relevance' => t('Keyword relevance'),
620 'node_rank_recent' => t('Recently posted'));
621 if (module_exist('comment')) {
622 $ranking['node_rank_comments'] = t('Number of comments');
624 if (module_exist('statistics') && variable_get('statistics_count_content_views', 0)) {
625 $ranking['node_rank_views'] = t('Number of views');
628 // Note: reversed to reflect that higher number = higher ranking.
629 $options = drupal_map_assoc(range(0, 10));
630 foreach ($ranking as $var => $title) {
631 $form['content_ranking']['factors'][$var] = array('#title' => $title, '#type' => 'select', '#options' => $options, '#default_value' => variable_get($var, 5));
636 // Build matching conditions
637 list($join1, $where1) = _db_rewrite_sql();
638 $arguments1 = array();
639 $conditions1 = 'n.status = 1';
641 if ($type = search_query_extract($keys, 'type')) {
643 foreach (explode(',', $type) as $t) {
644 $types[] = "n.type = '%s'";
647 $conditions1 .= ' AND ('. implode(' OR ', $types) .')';
648 $keys = search_query_insert($keys, 'type');
651 if ($category = search_query_extract($keys, 'category')) {
652 $categories = array();
653 foreach (explode(',', $category) as $c) {
654 $categories[] = "tn.tid = %d";
657 $conditions1 .= ' AND ('. implode(' OR ', $categories) .')';
658 $join1 .= ' INNER JOIN {term_node} tn ON n.nid = tn.nid';
659 $keys = search_query_insert($keys, 'category');
662 // Build ranking expression (we try to map each parameter to a
663 // uniform distribution in the range 0..1).
665 $arguments2 = array();
667 // Used to avoid joining on node_comment_statistics twice
669 if ($weight = (int)variable_get('node_rank_relevance', 5)) {
670 // Average relevance values hover around 0.15
671 $ranking[] = '%d * i.relevance';
672 $arguments2[] = $weight;
674 if ($weight = (int)variable_get('node_rank_recent', 5)) {
675 // Exponential decay with half-life of 6 months, starting at last indexed node
676 $ranking[] = '%d * POW(2, (GREATEST(n.created, n.changed, c.last_comment_timestamp) - %d) * 6.43e-8)';
677 $arguments2[] = $weight;
678 $arguments2[] = (int)variable_get('node_cron_last', 0);
679 $join2 .= ' INNER JOIN {node} n ON n.nid = i.sid LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
682 if (module_exist('comment') && $weight = (int)variable_get('node_rank_comments', 5)) {
683 // Inverse law that maps the highest reply count on the site to 1 and 0 to 0.
684 $scale = variable_get('node_cron_comments_scale', 0.0);
685 $ranking[] = '%d * (2.0 - 2.0 / (1.0 + c.comment_count * %f))';
686 $arguments2[] = $weight;
687 $arguments2[] = $scale;
689 $join2 .= ' LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
692 if (module_exist('statistics') && variable_get('statistics_count_content_views', 0) &&
693 $weight = (int)variable_get('node_rank_views', 5)) {
694 // Inverse law that maps the highest view count on the site to 1 and 0 to 0.
695 $scale = variable_get('node_cron_views_scale', 0.0);
696 $ranking[] = '%d * (2.0 - 2.0 / (1.0 + nc.totalcount * %f))';
697 $arguments2[] = $weight;
698 $arguments2[] = $scale;
699 $join2 .= ' LEFT JOIN {node_counter} nc ON nc.nid = i.sid';
701 $select2 = (count($ranking) ? implode(' + ', $ranking) : 'i.relevance') . ' AS score';
704 $find = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid = i.sid '. $join1 .' INNER JOIN {users} u ON n.uid = u.uid', $conditions1 . (empty($where1) ? '' : ' AND '. $where1), $arguments1, $select2, $join2, $arguments2);
708 foreach ($find as $item) {
709 $node = node_load($item->sid);
711 // Get node output (filtered and with module-specific fields).
712 if (node_hook($node, 'view')) {
713 node_invoke($node, 'view', false, false);
716 $node = node_prepare($node, false);
718 // Allow modules to change $node->body before viewing.
719 node_invoke_nodeapi($node, 'view', false, false);
721 // Fetch comments for snippet
722 $node->body .= module_invoke('comment', 'nodeapi', $node, 'update index');
723 // Fetch terms for snippet
724 $node->body .= module_invoke('taxonomy', 'nodeapi', $node, 'update index');
726 $extra = node_invoke_nodeapi($node, 'search result');
727 $results[] = array('link' => url('node/'. $item->sid),
728 'type' => node_get_name($node),
729 'title' => $node->title,
730 'user' => theme('username', $node),
731 'date' => $node->changed,
734 'snippet' => search_excerpt($keys, $node->body));
741 * Implementation of hook_user().
743 function node_user($op, &$edit, &$user) {
744 if ($op == 'delete') {
745 db_query('UPDATE {node} SET uid = 0 WHERE uid = %d', $user->uid);
746 db_query('UPDATE {node_revisions} SET uid = 0 WHERE uid = %d', $user->uid);
750 function theme_node_search_admin($form) {
751 $output = form_render($form['info']);
753 $header = array(t('Factor'), t('Weight'));
754 foreach (element_children($form['factors']) as $key) {
756 $row[] = $form['factors'][$key]['#title'];
757 unset($form['factors'][$key]['#title']);
758 $row[] = form_render($form['factors'][$key]);
761 $output .= theme('table', $header, $rows);
763 $output .= form_render($form);
768 * Menu callback; presents general node configuration options.
770 function node_configure() {
772 $form['default_nodes_main'] = array(
773 '#type' => 'select', '#title' => t('Number of posts on main page'), '#default_value' => variable_get('default_nodes_main', 10),
774 '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30)),
775 '#description' => t('The default maximum number of posts to display per page on overview pages such as the main page.')
778 $form['teaser_length'] = array(
779 '#type' => 'select', '#title' => t('Length of trimmed posts'), '#default_value' => variable_get('teaser_length', 600),
780 '#options' => array(0 => t('Unlimited'), 200 => t('200 characters'), 400 => t('400 characters'), 600 => t('600 characters'),
781 800 => t('800 characters'), 1000 => t('1000 characters'), 1200 => t('1200 characters'), 1400 => t('1400 characters'),
782 1600 => t('1600 characters'), 1800 => t('1800 characters'), 2000 => t('2000 characters')),
783 '#description' => t("The maximum number of characters used in the trimmed version of a post. Drupal will use this setting to determine at which offset long posts should be trimmed. The trimmed version of a post is typically used as a teaser when displaying the post on the main page, in XML feeds, etc. To disable teasers, set to 'Unlimited'. Note that this setting will only affect new or updated content and will not affect existing teasers.")
786 $form['node_preview'] = array(
787 '#type' => 'radios', '#title' => t('Preview post'), '#default_value' => variable_get('node_preview', 0),
788 '#options' => array(t('Optional'), t('Required')), '#description' => t('Must users preview posts before submitting?')
791 return system_settings_form('node_configure', $form);
795 * Retrieve the comment mode for the given node ID (none, read, or read/write).
797 function node_comment_mode($nid) {
798 static $comment_mode;
799 if (!isset($comment_mode[$nid])) {
800 $comment_mode[$nid] = db_result(db_query('SELECT comment FROM {node} WHERE nid = %d', $nid));
802 return $comment_mode[$nid];
806 * Implementation of hook_link().
808 function node_link($type, $node = 0, $main = 0) {
811 if ($type == 'node') {
812 if (array_key_exists('links', $node)) {
813 $links = $node->links;
816 if ($main == 1 && $node->teaser && $node->readmore) {
817 $links[] = l(t('read more'), "node/$node->nid", array('title' => t('Read the rest of this posting.'), 'class' => 'read-more'));
825 * Implementation of hook_menu().
827 function node_menu($may_cache) {
831 $items[] = array('path' => 'admin/node', 'title' => t('content'),
832 'callback' => 'node_admin_nodes',
833 'access' => user_access('administer nodes'));
834 $items[] = array('path' => 'admin/node/overview', 'title' => t('list'),
835 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
837 if (module_exist('search')) {
838 $items[] = array('path' => 'admin/node/search', 'title' => t('search'),
839 'callback' => 'node_admin_search',
840 'access' => user_access('administer nodes'),
841 'type' => MENU_LOCAL_TASK);
844 $items[] = array('path' => 'admin/settings/node', 'title' => t('posts'),
845 'callback' => 'node_configure',
846 'access' => user_access('administer nodes'));
847 $items[] = array('path' => 'admin/settings/content-types', 'title' => t('content types'),
848 'callback' => 'node_types_configure',
849 'access' => user_access('administer nodes'));
851 $items[] = array('path' => 'node', 'title' => t('content'),
852 'callback' => 'node_page',
853 'access' => user_access('access content'),
854 'type' => MENU_MODIFIABLE_BY_ADMIN);
855 $items[] = array('path' => 'node/add', 'title' => t('create content'),
856 'callback' => 'node_page',
857 'access' => user_access('access content'),
858 'type' => MENU_ITEM_GROUPING,
860 $items[] = array('path' => 'rss.xml', 'title' => t('rss feed'),
861 'callback' => 'node_feed',
862 'access' => user_access('access content'),
863 'type' => MENU_CALLBACK);
866 if (arg(0) == 'node' && is_numeric(arg(1))) {
867 $node = node_load(arg(1));
869 $items[] = array('path' => 'node/'. arg(1), 'title' => t('view'),
870 'callback' => 'node_page',
871 'access' => node_access('view', $node),
872 'type' => MENU_CALLBACK);
873 $items[] = array('path' => 'node/'. arg(1) .'/view', 'title' => t('view'),
874 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
875 $items[] = array('path' => 'node/'. arg(1) .'/edit', 'title' => t('edit'),
876 'callback' => 'node_page',
877 'access' => node_access('update', $node),
879 'type' => MENU_LOCAL_TASK);
880 $items[] = array('path' => 'node/'. arg(1) .'/delete', 'title' => t('delete'),
881 'callback' => 'node_delete_confirm',
882 'access' => node_access('delete', $node),
884 'type' => MENU_CALLBACK);
885 $revisions_access = ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node) && db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', arg(1))) > 1);
886 $items[] = array('path' => 'node/'. arg(1) .'/revisions', 'title' => t('revisions'),
887 'callback' => 'node_revisions',
888 'access' => $revisions_access,
890 'type' => MENU_LOCAL_TASK);
893 else if (arg(0) == 'admin' && arg(1) == 'settings' && arg(2) == 'content-types' && is_string(arg(3))) {
894 $items[] = array('path' => 'admin/settings/content-types/'. arg(3),
895 'title' => t("'%name' content type", array('%name' => node_get_name(arg(3)))),
896 'type' => MENU_CALLBACK);
903 function node_last_changed($nid) {
904 $node = db_fetch_object(db_query('SELECT changed FROM {node} WHERE nid = %d', $nid));
905 return ($node->changed);
909 * List node administration operations that can be performed.
911 function node_operations() {
913 'approve' => array(t('Approve the selected posts'), 'UPDATE {node} SET status = 1, moderate = 0 WHERE nid = %d'),
914 'promote' => array(t('Promote the selected posts'), 'UPDATE {node} SET status = 1, promote = 1 WHERE nid = %d'),
915 'sticky' => array(t('Make the selected posts sticky'), 'UPDATE {node} SET status = 1, sticky = 1 WHERE nid = %d'),
916 'demote' => array(t('Demote the selected posts'), 'UPDATE {node} SET promote = 0 WHERE nid = %d'),
917 'unpublish' => array(t('Unpublish the selected posts'), 'UPDATE {node} SET status = 0 WHERE nid = %d'),
918 'delete' => array(t('Delete the selected posts'), '')
924 * List node administration filters that can be applied.
926 function node_filters() {
928 $filters['status'] = array('title' => t('status'),
929 'options' => array('status-1' => t('published'), 'status-0' => t('not published'),
930 'moderate-1' => t('in moderation'), 'moderate-0' => t('not in moderation'),
931 'promote-1' => t('promoted'), 'promote-0' => t('not promoted'),
932 'sticky-1' => t('sticky'), 'sticky-0' => t('not sticky')));
933 $filters['type'] = array('title' => t('type'), 'options' => node_get_types());
934 // The taxonomy filter
935 if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
936 $filters['category'] = array('title' => t('category'), 'options' => $taxonomy);
943 * Build query for node administration filters based on session.
945 function node_build_filter_query() {
946 $filters = node_filters();
949 $where = $args = array();
951 foreach ($_SESSION['node_overview_filter'] as $index => $filter) {
952 list($key, $value) = $filter;
955 // Note: no exploitable hole as $key/$value have already been checked when submitted
956 list($key, $value) = explode('-', $value, 2);
957 $where[] = 'n.'. $key .' = %d';
961 $where[] = "$table.tid = %d";
962 $join .= "INNER JOIN {term_node} $table ON n.nid = $table.nid ";
965 $where[] = "n.type = '%s'";
969 $where = count($where) ? 'WHERE '. implode(' AND ', $where) : '';
971 return array('where' => $where, 'join' => $join, 'args' => $args);
975 * Return form for node administration filters.
977 function node_filter_form() {
978 $session = &$_SESSION['node_overview_filter'];
979 $session = is_array($session) ? $session : array();
980 $filters = node_filters();
983 $form['filters'] = array('#type' => 'fieldset',
984 '#title' => t('Show only items where'),
985 '#theme' => 'node_filters',
987 foreach ($session as $filter) {
988 list($type, $value) = $filter;
989 if ($type == 'category') {
990 // Load term name from DB rather than search and parse options array.
991 $value = module_invoke('taxonomy', 'get_term', $value);
992 $value = $value->name;
995 $value = $filters[$type]['options'][$value];
997 $string = ($i++ ? '<em>and</em> where <strong>%a</strong> is <strong>%b</strong>' : '<strong>%a</strong> is <strong>%b</strong>');
998 $form['filters']['current'][] = array('#value' => t($string, array('%a' => $filters[$type]['title'] , '%b' => $value)));
1001 foreach ($filters as $key => $filter) {
1002 $names[$key] = $filter['title'];
1003 $form['filters']['status'][$key] = array('#type' => 'select', '#options' => $filter['options']);
1006 $form['filters']['filter'] = array('#type' => 'radios', '#options' => $names, '#default_value' => 'status');
1007 $form['filters']['buttons']['submit'] = array('#type' => 'submit', '#value' => (count($session) ? t('Refine') : t('Filter')));
1008 if (count($session)) {
1009 $form['filters']['buttons']['undo'] = array('#type' => 'submit', '#value' => t('Undo'));
1010 $form['filters']['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset'));
1013 return drupal_get_form('node_filter_form', $form);
1017 * Theme node administration filter form.
1019 function theme_node_filter_form(&$form) {
1020 $output .= '<div id="node-admin-filter">';
1021 $output .= form_render($form['filters']);
1022 $output .= '</div>';
1023 $output .= form_render($form);
1028 * Theme node administraton filter selector.
1030 function theme_node_filters(&$form) {
1032 if (sizeof($form['current'])) {
1033 foreach (element_children($form['current']) as $key) {
1034 $output .= '<li>' . form_render($form['current'][$key]) . '</li>';
1038 $output .= '<li><dl class="multiselect">' . (sizeof($form['current']) ? '<dt><em>'. t('and') .'</em> '. t('where') .'</dt>' : '') . '<dd class="a">';
1039 foreach (element_children($form['filter']) as $key) {
1040 $output .= form_render($form['filter'][$key]);
1044 $output .= '<dt>'. t('is') .'</dt>' . '<dd class="b">';
1046 foreach (element_children($form['status']) as $key) {
1047 $output .= form_render($form['status'][$key]);
1052 $output .= '<div class="container-inline" id="node-admin-buttons">'. form_render($form['buttons']) .'</div>';
1053 $output .= '</li></ul><br class="clear" />';
1059 * Process result from node administration filter form.
1061 function node_filter_form_submit() {
1062 global $form_values;
1064 $filters = node_filters();
1068 if (isset($form_values['filter'])) {
1069 $filter = $form_values['filter'];
1071 // Flatten the options array to accommodate hierarchical/nested options.
1072 $flat_options = form_options_flatten($filters[$filter]['options']);
1074 if (isset($flat_options[$form_values[$filter]])) {
1075 $_SESSION['node_overview_filter'][] = array($filter, $form_values[$filter]);
1080 array_pop($_SESSION['node_overview_filter']);
1083 $_SESSION['node_overview_filter'] = array();
1089 * Generate the content administration overview.
1091 function node_admin_nodes_submit($form_id, $edit) {
1092 $operations = node_operations();
1093 if ($operations[$edit['operation']][1]) {
1095 $operation = $operations[$edit['operation']][1];
1096 foreach ($edit['nodes'] as $nid => $value) {
1098 db_query($operation, $nid);
1102 drupal_set_message(t('The update has been performed.'));
1106 function node_admin_nodes_validate($form_id, $edit) {
1107 $edit['nodes'] = array_diff($edit['nodes'], array(0));
1108 if (count($edit['nodes']) == 0) {
1109 if ($edit['operation'] == 'delete') {
1110 form_set_error('', t('Please select some items to perform the delete operation.'));
1113 form_set_error('', t('Please select some items to perform the update on.'));
1119 * Menu callback: content administration.
1121 function node_admin_nodes() {
1122 global $form_values;
1123 $output = node_filter_form();
1125 if ($_POST['edit']['operation'] == 'delete' && $_POST['edit']['nodes']) {
1126 return node_multiple_delete_confirm();
1129 $filter = node_build_filter_query();
1131 $result = pager_query('SELECT n.*, u.name, u.uid FROM {node} n '. $filter['join'] .' INNER JOIN {users} u ON n.uid = u.uid '. $filter['where'] .' ORDER BY n.changed DESC', 50, 0, NULL, $filter['args']);
1133 $form['options'] = array('#type' => 'fieldset',
1134 '#title' => t('Update options'),
1135 '#prefix' => '<div class="container-inline">',
1136 '#suffix' => '</div>',
1139 foreach (node_operations() as $key => $value) {
1140 $options[$key] = $value[0];
1142 $form['options']['operation'] = array('#type' => 'select', '#options' => $options, '#default_value' => 'approve');
1143 $form['options']['submit'] = array('#type' => 'submit', '#value' => t('Update'));
1145 $destination = drupal_get_destination();
1146 while ($node = db_fetch_object($result)) {
1147 $nodes[$node->nid] = '';
1148 $form['title'][$node->nid] = array('#value' => l($node->title, 'node/'. $node->nid) .' '. theme('mark', node_mark($node->nid, $node->changed)));
1149 $form['name'][$node->nid] = array('#value' => node_get_name($node));
1150 $form['username'][$node->nid] = array('#value' => theme('username', $node));
1151 $form['status'][$node->nid] = array('#value' => ($node->status ? t('published') : t('not published')));
1152 $form['operations'][$node->nid] = array('#value' => l(t('edit'), 'node/'. $node->nid .'/edit', array(), $destination));
1154 $form['nodes'] = array('#type' => 'checkboxes', '#options' => $nodes);
1155 $form['pager'] = array('#value' => theme('pager', NULL, 50, 0));
1157 // Call the form first, to allow for the form_values array to be populated.
1158 $output .= drupal_get_form('node_admin_nodes', $form);
1164 * Theme node administration overview.
1166 function theme_node_admin_nodes($form) {
1168 $header = array(NULL, t('Title'), t('Type'), t('Author'), t('Status'), t('Operations'));
1170 $output .= form_render($form['options']);
1171 if (isset($form['title']) && is_array($form['title'])) {
1172 foreach (element_children($form['title']) as $key) {
1174 $row[] = form_render($form['nodes'][$key]);
1175 $row[] = form_render($form['title'][$key]);
1176 $row[] = form_render($form['name'][$key]);
1177 $row[] = form_render($form['username'][$key]);
1178 $row[] = form_render($form['status'][$key]);
1179 $row[] = form_render($form['operations'][$key]);
1185 $rows[] = array(array('data' => t('No posts available.'), 'colspan' => '6'));
1188 $output .= theme('table', $header, $rows);
1189 if ($form['pager']['#value']) {
1190 $output .= form_render($form['pager']);
1193 $output .= form_render($form);
1198 function node_multiple_delete_confirm() {
1199 $edit = $_POST['edit'];
1201 $form['nodes'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
1202 // array_filter returns only elements with true values
1203 foreach (array_filter($edit['nodes']) as $nid => $value) {
1204 $title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $nid));
1205 $form['nodes'][$nid] = array('#type' => 'hidden', '#value' => $nid, '#prefix' => '<li>', '#suffix' => check_plain($title) ."</li>\n");
1207 $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
1209 return confirm_form('node_multiple_delete_confirm', $form,
1210 t('Are you sure you want to delete these items?'),
1211 'admin/node', t('This action cannot be undone.'),
1212 t('Delete all'), t('Cancel'));
1215 function node_multiple_delete_confirm_submit($form_id, $edit) {
1216 if ($edit['confirm']) {
1217 foreach ($edit['nodes'] as $nid => $value) {
1220 drupal_set_message(t('The items have been deleted.'));
1222 return 'admin/node';
1226 * Menu callback; presents each node type configuration page.
1228 function node_types_configure($type = NULL) {
1230 $node = new stdClass();
1231 $node->type = $type;
1232 $form['submission'] = array('#type' => 'fieldset', '#title' =>t('Submission form') );
1233 $form['submission'][$type . '_help'] = array(
1234 '#type' => 'textarea', '#title' => t('Explanation or submission guidelines'), '#default_value' => variable_get($type .'_help', ''),
1235 '#description' => t('This text will be displayed at the top of the %type submission form. It is useful for helping or instructing your users.', array('%type' => node_get_name($type)))
1237 $form['submission']['minimum_'. $type .'_size'] = array(
1238 '#type' => 'select', '#title' => t('Minimum number of words'), '#default_value' => variable_get('minimum_'. $type .'_size', 0), '#options' => drupal_map_assoc(array(0, 10, 25, 50, 75, 100, 125, 150, 175, 200)),
1239 '#description' => t('The minimum number of words a %type must be to be considered valid. This can be useful to rule out submissions that do not meet the site\'s standards, such as short test posts.', array('%type' => node_get_name($type)))
1241 $form['workflow'] = array('#type' => 'fieldset', '#title' =>t('Workflow'));
1242 $form['type'] = array('#type' => 'value', '#value' => $type);
1244 $form['array_filter'] = array('#type' => 'value', '#value' => TRUE);
1245 return system_settings_form($type .'_node_settings', $form);
1248 $header = array(t('Type'), t('Operations'));
1251 foreach (node_get_types() as $type => $name) {
1252 $rows[] = array($name, l(t('configure'), 'admin/settings/content-types/'. $type));
1255 return theme('table', $header, $rows);
1260 * Generate an overview table of older revisions of a node.
1262 function node_revision_overview($node) {
1263 drupal_set_title(t('Revisions for %title', array('%title' => check_plain($node->title))));
1265 $header = array(t('Revision'), array('data' => t('Operations'), 'colspan' => 2));
1267 $revisions = node_revision_list($node);
1270 $revert_permission = FALSE;
1271 if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) {
1272 $revert_permission = TRUE;
1274 $delete_permission = FALSE;
1275 if (user_access('administer nodes')) {
1276 $delete_permission = TRUE;
1278 foreach ($revisions as $revision) {
1280 $operations = array();
1282 if ($revision->current_vid > 0) {
1283 $row[] = array('data' => t('%date by %username', array('%date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid"), '%username' => theme('username', $revision)))
1284 . (($revision->log != '') ? '<p class="revision-log">'. filter_xss($revision->log) .'</p>' : ''),
1285 'class' => 'revision-current');
1286 $operations[] = array('data' => theme('placeholder', t('current revision')), 'class' => 'revision-current', 'colspan' => 2);
1289 $row[] = t('%date by %username', array('%date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid/revisions/$revision->vid/view"), '%username' => theme('username', $revision)))
1290 . (($revision->log != '') ? '<p class="revision-log">'. filter_xss($revision->log) .'</p>' : '');
1291 if ($revert_permission) {
1292 $operations[] = l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert");
1294 if ($delete_permission) {
1295 $operations[] = l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete");
1298 $rows[] = array_merge($row, $operations);
1300 $output .= theme('table', $header, $rows);
1306 * Revert to the revision with the specified revision number. A node and nodeapi "update" event is triggered
1307 * (via the node_save() call) when a revision is reverted.
1309 function node_revision_revert($nid, $revision) {
1312 $node = node_load($nid, $revision);
1313 if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) {
1315 $node->revision = 1;
1316 $node->log = t('Copy of the revision from %date.', array('%date' => theme('placeholder', format_date($node->revision_timestamp))));
1317 $node->taxonomy = array_keys($node->taxonomy);
1321 drupal_set_message(t('%title has been reverted back to the revision from %revision-date', array('%revision-date' => theme('placeholder', format_date($node->revision_timestamp)), '%title' => theme('placeholder', check_plain($node->title)))));
1322 watchdog('content', t('%type: reverted %title revision %revision.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision))));
1325 drupal_set_message(t('You tried to revert to an invalid revision.'), 'error');
1327 drupal_goto('node/'. $nid .'/revisions');
1329 drupal_access_denied();
1333 * Delete the revision with specified revision number. A "delete revision" nodeapi event is invoked when a
1334 * revision is deleted.
1336 function node_revision_delete($nid, $revision) {
1337 if (user_access('administer nodes')) {
1338 $node = node_load($nid);
1339 if (node_access('delete', $node)) {
1340 // Don't delete the current revision
1341 if ($revision != $node->vid) {
1342 $node = node_load($nid, $revision);
1344 db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $nid, $revision);
1345 node_invoke_nodeapi($node, 'delete revision');
1346 drupal_set_message(t('Deleted %title revision %revision.', array('%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision))));
1347 watchdog('content', t('%type: deleted %title revision %revision.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision))));
1351 drupal_set_message(t('Deletion failed. You tried to delete the current revision.'));
1353 if (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $nid)) > 1) {
1354 drupal_goto("node/$nid/revisions");
1357 drupal_goto("node/$nid");
1362 drupal_access_denied();
1366 * Return a list of all the existing revision numbers.
1368 function node_revision_list($node) {
1369 $revisions = array();
1370 $result = db_query('SELECT r.vid, r.title, r.log, r.uid, n.vid AS current_vid, r.timestamp, u.name FROM {node_revisions} r LEFT JOIN {node} n ON n.vid = r.vid INNER JOIN {users} u ON u.uid = r.uid WHERE r.nid = %d ORDER BY r.timestamp DESC', $node->nid);
1371 while ($revision = db_fetch_object($result)) {
1372 $revisions[] = $revision;
1378 function node_admin_search() {
1379 $output = search_form(url('admin/node/search'), $_POST['edit']['keys'], 'node') . search_data($_POST['edit']['keys'], 'node');
1384 * Implementation of hook_block().
1386 function node_block($op = 'list', $delta = 0) {
1387 if ($op == 'list') {
1388 $blocks[0]['info'] = t('Syndicate');
1391 else if ($op == 'view') {
1392 $block['subject'] = t('Syndicate');
1393 $block['content'] = theme('feed_icon', url('rss.xml'));
1400 * A generic function for generating RSS feeds from a set of nodes.
1403 * An object as returned by db_query() which contains the nid field.
1405 * An associative array containing title, link, description and other keys.
1406 * The link should be an absolute URL.
1408 function node_feed($nodes = 0, $channel = array()) {
1409 global $base_url, $locale;
1412 $nodes = db_query_range(db_rewrite_sql('SELECT n.nid, n.created FROM {node} n WHERE n.promote = 1 AND n.status = 1 ORDER BY n.created DESC'), 0, variable_get('feed_default_items', 10));
1415 $item_length = variable_get('feed_item_length', 'teaser');
1416 $namespaces = array('xmlns:dc="http://purl.org/dc/elements/1.1/"');
1418 while ($node = db_fetch_object($nodes)) {
1419 // Load the specified node:
1420 $item = node_load($node->nid);
1421 $link = url("node/$node->nid", NULL, NULL, 1);
1423 if ($item_length != 'title') {
1424 $teaser = ($item_length == 'teaser') ? TRUE : FALSE;
1426 // Filter and prepare node teaser
1427 if (node_hook($item, 'view')) {
1428 node_invoke($item, 'view', $teaser, FALSE);
1431 $item = node_prepare($item, $teaser);
1434 // Allow modules to change $node->teaser before viewing.
1435 node_invoke_nodeapi($item, 'view', $teaser, FALSE);
1438 // Prepare the item description
1439 switch ($item_length) {
1441 $item_text = $item->body;
1444 $item_text = $item->teaser;
1445 if ($item->readmore) {
1446 $item_text .= '<p>'. l(t('read more'), 'node/'. $item->nid, NULL, NULL, NULL, TRUE) .'</p>';
1454 // Allow modules to add additional item fields
1455 $extra = node_invoke_nodeapi($item, 'rss item');
1456 $extra = array_merge($extra, array(array('key' => 'pubDate', 'value' => date('r', $item->created)), array('key' => 'dc:creator', 'value' => $item->name), array('key' => 'guid', 'value' => $item->nid . ' at ' . $base_url, 'attributes' => array('isPermaLink' => 'false'))));
1457 foreach ($extra as $element) {
1458 if ($element['namespace']) {
1459 $namespaces = array_merge($namespaces, $element['namespace']);
1462 $items .= format_rss_item($item->title, $link, $item_text, $extra);
1465 $channel_defaults = array(
1467 'title' => variable_get('site_name', 'drupal') .' - '. variable_get('site_slogan', ''),
1468 'link' => $base_url,
1469 'description' => variable_get('site_mission', ''),
1470 'language' => $locale
1472 $channel = array_merge($channel_defaults, $channel);
1474 $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1475 $output .= "<rss version=\"". $channel["version"] . "\" xml:base=\"". $base_url ."\" ". implode(' ', $namespaces) .">\n";
1476 $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language']);
1477 $output .= "</rss>\n";
1479 drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
1484 * Prepare node for save and allow modules to make changes.
1486 function node_submit($node) {
1489 // Convert the node to an object, if necessary.
1490 $node = (object)$node;
1492 // Auto-generate the teaser, but only if it hasn't been set (e.g. by a
1493 // module-provided 'teaser' form item).
1494 if (!isset($node->teaser)) {
1495 $node->teaser = isset($node->body) ? node_teaser($node->body, isset($node->format) ? $node->format : NULL) : '';
1498 $access = user_access('administer nodes');
1500 // Populate the "authored by" field.
1501 if ($account = user_load(array('name' => $node->name))) {
1502 $node->uid = $account->uid;
1508 $node->created = $node->date ? strtotime($node->date) : NULL;
1510 // Force defaults in case people modify the form:
1511 $node_options = variable_get('node_options_'. $node->type, array('status', 'promote'));
1512 foreach (array('status', 'moderate', 'promote', 'sticky', 'revision') as $key) {
1513 if (!$access || !isset($node->$key)) {
1514 $node->$key = in_array($key, $node_options);
1518 // Do node-type-specific validation checks.
1519 node_invoke($node, 'submit');
1520 node_invoke_nodeapi($node, 'submit');
1522 $node->validated = TRUE;
1528 * Perform validation checks on the given node.
1530 function node_validate($node, $form = array()) {
1531 // Convert the node to an object, if necessary.
1532 $node = (object)$node;
1534 // Make sure the body has the minimum number of words.
1535 // todo use a better word counting algorithm that will work in other languages
1536 if (isset($node->body) && count(explode(' ', $node->body)) < variable_get('minimum_'. $node->type .'_size', 0)) {
1537 form_set_error('body', t('The body of your %type is too short. You need at least %words words.', array('%words' => variable_get('minimum_'. $node->type .'_size', 0), '%type' => node_get_name($node))));
1540 if (isset($node->nid) && (node_last_changed($node->nid) > $_POST['edit']['changed'])) {
1541 form_set_error('changed', t('This content has been modified by another user, changes cannot be saved.'));
1544 if (user_access('administer nodes')) {
1545 // Validate the "authored by" field.
1546 if (!empty($node->name) && !($account = user_load(array('name' => $node->name)))) {
1547 // The use of empty() is mandatory in the context of usernames
1548 // as the empty string denotes the anonymous user. In case we
1549 // are dealing with an anonymous user we set the user ID to 0.
1550 form_set_error('name', t('The username %name does not exist.', array ('%name' => theme('placeholder', $node->name))));
1553 // Validate the "authored on" field. As of PHP 5.1.0, strtotime returns FALSE instead of -1 upon failure.
1554 if (!empty($node->date) && strtotime($node->date) <= 0) {
1555 form_set_error('date', t('You have to specify a valid date.'));
1559 // Do node-type-specific validation checks.
1560 node_invoke($node, 'validate', $form);
1561 node_invoke_nodeapi($node, 'validate', $form);
1564 function node_form_validate($form_id, $form_values, $form) {
1565 node_validate($form_values, $form);
1568 function node_object_prepare(&$node) {
1569 if (user_access('administer nodes')) {
1570 // Set up default values, if required.
1571 if (!isset($node->created)) {
1572 $node->created = time();
1575 if (!isset($node->date)) {
1576 $node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O');
1579 node_invoke($node, 'prepare');
1580 node_invoke_nodeapi($node, 'prepare');
1584 * Generate the node editing form.
1586 function node_form($node) {
1587 $node = (object)$node;
1588 $form = node_form_array($node);
1589 return drupal_get_form($node->type .'_node_form', $form, 'node_form');
1593 * Generate the node editing form array.
1595 function node_form_array($node) {
1596 node_object_prepare($node);
1598 // Set the id of the top-level form tag
1599 $form['#id'] = 'node-form';
1602 * Basic node information.
1603 * These elements are just values so they are not even sent to the client.
1605 foreach (array('nid', 'vid', 'uid', 'created', 'type') as $key) {
1606 $form[$key] = array('#type' => 'value', '#value' => $node->$key);
1609 // Changed must be sent to the client, for later overwrite error checking.
1610 $form['changed'] = array('#type' => 'hidden', '#value' => $node->changed);
1612 // Get the node-specific bits.
1613 $form = array_merge_recursive($form, node_invoke($node, 'form'));
1614 if (!isset($form['title']['#weight'])) {
1615 $form['title']['#weight'] = -5;
1618 $node_options = variable_get('node_options_'. $node->type, array('status', 'promote'));
1619 // If this is a new node, fill in the default values.
1620 if (!isset($node->nid)) {
1621 foreach (array('status', 'moderate', 'promote', 'sticky', 'revision') as $key) {
1622 $node->$key = in_array($key, $node_options);
1625 $node->uid = $user->uid;
1628 // Nodes being edited should always be preset with the default revision setting.
1629 $node->revision = in_array('revision', $node_options);
1631 $form['#node'] = $node;
1633 if (user_access('administer nodes')) {
1634 // Node author information
1635 $form['author'] = array('#type' => 'fieldset', '#title' => t('Authoring information'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => 20);
1636 $form['author']['name'] = array('#type' => 'textfield', '#title' => t('Authored by'), '#maxlength' => 60, '#autocomplete_path' => 'user/autocomplete', '#default_value' => $node->name ? $node->name : '', '#weight' => -1, '#description' => t('Leave blank for %anonymous.', array('%anonymous' => theme('placeholder', variable_get('anonymous', 'Anonymous')))));
1637 $form['author']['date'] = array('#type' => 'textfield', '#title' => t('Authored on'), '#maxlength' => 25, '#description' => t('Format: %time. Leave blank to use the time of form submission.', array('%time' => $node->date)));
1639 if (isset($node->nid)) {
1640 $form['author']['date']['#default_value'] = $node->date;
1643 // Node options for administrators
1644 $form['options'] = array('#type' => 'fieldset', '#title' => t('Publishing options'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => 25);
1645 $form['options']['status'] = array('#type' => 'checkbox', '#title' => t('Published'), '#default_value' => $node->status);
1646 $form['options']['moderate'] = array('#type' => 'checkbox', '#title' => t('In moderation queue'), '#default_value' => $node->moderate);
1647 $form['options']['promote'] = array('#type' => 'checkbox', '#title' => t('Promoted to front page'), '#default_value' => $node->promote);
1648 $form['options']['sticky'] = array('#type' => 'checkbox', '#title' => t('Sticky at top of lists'), '#default_value' => $node->sticky);
1649 $form['options']['revision'] = array('#type' => 'checkbox', '#title' => t('Create new revision'), '#default_value' => $node->revision);
1652 // Put all of these through as values if the user doesn't have access to them.
1653 foreach (array('uid', 'created') as $key) {
1654 $form[$key] = array('#type' => 'value', '#value' => $node->$key);
1659 $form['preview'] = array('#type' => 'button', '#value' => t('Preview'), '#weight' => 40);
1660 $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'), '#weight' => 45);
1661 if ($node->nid && node_access('delete', $node)) {
1662 $form['delete'] = array('#type' => 'button', '#value' => t('Delete'), '#weight' => 50);
1665 $form['#after_build'] = array('node_form_add_preview');
1670 function node_form_add_preview($form) {
1671 global $form_values;
1673 $op = isset($_POST['op']) ? $_POST['op'] : '';
1674 if ($op == t('Preview')) {
1675 drupal_validate_form($form['form_id']['#value'], $form);
1676 if (!form_get_errors()) {
1677 // We pass the global $form_values here to preserve changes made during form validation
1678 $form['node_preview'] = array('#value' => node_preview((object)$form_values), '#weight' => -100);
1681 if (variable_get('node_preview', 0) && (form_get_errors() || $op != t('Preview'))) {
1682 unset($form['submit']);
1687 function theme_node_form($form) {
1688 $output = "\n<div class=\"node-form\">\n";
1689 if (isset($form['node_preview'])) {
1690 $output .= form_render($form['node_preview']);
1693 // Admin form fields and submit buttons must be rendered first, because
1694 // they need to go to the bottom of the form, and so should not be part of
1695 // the catch-all call to form_render().
1697 if (isset($form['author'])) {
1698 $admin .= " <div class=\"authored\">\n";
1699 $admin .= form_render($form['author']);
1700 $admin .= " </div>\n";
1702 if (isset($form['options'])) {
1703 $admin .= " <div class=\"options\">\n";
1704 $admin .= form_render($form['options']);
1705 $admin .= " </div>\n";
1707 $buttons = form_render($form['preview']);
1708 $buttons .= form_render($form['submit']);
1709 $buttons .= isset($form['delete']) ? form_render($form['delete']) : '';
1711 // Everything else gets rendered here, and is displayed before the admin form
1712 // field and the submit buttons.
1713 $output .= " <div class=\"standard\">\n";
1714 $output .= form_render($form);
1715 $output .= " </div>\n";
1717 if (!empty($admin)) {
1718 $output .= " <div class=\"admin\">\n";
1720 $output .= " </div>\n";
1722 $output .= $buttons;
1723 $output .= "</div>\n";
1729 * Present a node submission form or a set of links to such forms.
1731 function node_add($type) {
1734 // If a node type has been specified, validate its existence.
1735 if (array_key_exists($type, node_get_types()) && node_access('create', $type)) {
1736 // Initialize settings:
1737 $node = array('uid' => $user->uid, 'name' => $user->name, 'type' => $type);
1739 $output = node_form($node);
1740 drupal_set_title(t('Submit %name', array('%name' => node_get_name($node))));
1743 // If no (valid) node type has been provided, display a node type overview.
1744 foreach (node_get_types() as $type => $name) {
1745 if (node_access('create', $type)) {
1746 $out = '<dt>'. l($name, "node/add/$type", array('title' => t('Add a new %s.', array('%s' => $name)))) .'</dt>';
1747 $out .= '<dd>'. implode("\n", module_invoke_all('help', 'node/add#'. $type)) .'</dd>';
1748 $item[$name] = $out;
1753 uksort($item, 'strnatcasecmp');
1754 $output = t('Choose the appropriate item from the list:') .'<dl>'. implode('', $item) .'</dl>';
1757 $output = t('You are not allowed to create content.');
1765 * Generate a node preview.
1767 function node_preview($node) {
1768 if (node_access('create', $node) || node_access('update', $node)) {
1769 // Load the user's name when needed:
1770 if (isset($node->name)) {
1771 // The use of isset() is mandatory in the context of user IDs, because
1772 // user ID 0 denotes the anonymous user.
1773 if ($user = user_load(array('name' => $node->name))) {
1774 $node->uid = $user->uid;
1777 $node->uid = 0; // anonymous user
1780 else if ($node->uid) {
1781 $user = user_load(array('uid' => $node->uid));
1782 $node->name = $user->name;
1785 // Set the timestamps when needed:
1787 $node->created = strtotime($node->date);
1789 $node->changed = time();
1791 // Extract a teaser, if it hasn't been set (e.g. by a module-provided
1792 // 'teaser' form item).
1793 if (!isset($node->teaser)) {
1794 $node->teaser = node_teaser($node->body, $node->format);
1797 // Display a preview of the node:
1798 // Previewing alters $node so it needs to be cloned.
1799 if (!form_get_errors()) {
1800 $cloned_node = drupal_clone($node);
1801 $cloned_node->in_preview = TRUE;
1802 $output = theme('node_preview', $cloned_node);
1804 drupal_set_title(t('Preview'));
1805 drupal_set_breadcrumb(array(l(t('Home'), NULL), l(t('create content'), 'node/add'), l(t('Submit %name', array('%name' => node_get_name($node))), 'node/add/'. $node->type)));
1812 * Display a node preview for display during node creation and editing.
1815 * The node object which is being previewed.
1817 function theme_node_preview($node) {
1818 $output = '<div class="preview">';
1819 if ($node->teaser && $node->teaser != $node->body) {
1820 drupal_set_message(t('The trimmed version of your post shows what your post looks like when promoted to the main page or when exported for syndication. You can insert the delimiter "<!--break-->" (without the quotes) to fine-tune where your post gets split.'));
1821 $output .= '<h3>'. t('Preview trimmed version') .'</h3>';
1822 $output .= node_view(drupal_clone($node), 1, FALSE, 0);
1823 $output .= '<h3>'. t('Preview full version') .'</h3>';
1824 $output .= node_view($node, 0, FALSE, 0);
1827 $output .= node_view($node, 0, FALSE, 0);
1829 $output .= "</div>\n";
1834 function node_form_submit($form_id, $edit) {
1837 // Fix up the node when required:
1838 $node = node_submit($edit);
1840 // Prepare the node's body:
1842 // Check whether the current user has the proper access rights to
1843 // perform this operation:
1844 if (node_access('update', $node)) {
1846 watchdog('content', t('%type: updated %title.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title))), WATCHDOG_NOTICE, l(t('view'), 'node/'. $node->nid));
1847 drupal_set_message(t('The %post was updated.', array ('%post' => node_get_name($node))));
1851 // Check whether the current user has the proper access rights to
1852 // perform this operation:
1853 if (node_access('create', $node)) {
1855 watchdog('content', t('%type: added %title.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title))), WATCHDOG_NOTICE, l(t('view'), "node/$node->nid"));
1856 drupal_set_message(t('Your %post was created.', array ('%post' => node_get_name($node))));
1860 if (node_access('view', $node)) {
1861 return 'node/'. $node->nid;
1867 // it is very unlikely we get here
1872 * Menu callback -- ask for confirmation of node deletion
1874 function node_delete_confirm() {
1875 $edit = $_POST['edit'];
1876 $edit['nid'] = $edit['nid'] ? $edit['nid'] : arg(1);
1877 $node = node_load($edit['nid']);
1879 if (node_access('delete', $node)) {
1880 $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
1881 $output = confirm_form('node_delete_confirm', $form,
1882 t('Are you sure you want to delete %title?', array('%title' => theme('placeholder', $node->title))),
1883 $_GET['destination'] ? $_GET['destination'] : 'node/'. $node->nid, t('This action cannot be undone.'),
1884 t('Delete'), t('Cancel') );
1891 * Execute node deletion
1893 function node_delete_confirm_submit($form_id, $form_values) {
1894 if ($form_values['confirm']) {
1895 node_delete($form_values['nid']);
1904 function node_delete($nid) {
1906 $node = node_load($nid);
1908 if (node_access('delete', $node)) {
1909 db_query('DELETE FROM {node} WHERE nid = %d', $node->nid);
1910 db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);
1912 // Call the node-specific callback (if any):
1913 node_invoke($node, 'delete');
1914 node_invoke_nodeapi($node, 'delete');
1916 // Clear the cache so an anonymous poster can see the node being deleted.
1919 // Remove this node from the search index if needed.
1920 if (function_exists('search_wipe')) {
1921 search_wipe($node->nid, 'node');
1923 drupal_set_message(t('%title has been deleted.', array('%title' => theme('placeholder', $node->title))));
1924 watchdog('content', t('%type: deleted %title.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title))));
1929 * Menu callback for revisions related activities.
1931 function node_revisions() {
1932 if (is_numeric(arg(1)) && arg(2) == 'revisions') {
1933 $op = arg(4) ? arg(4) : 'overview';
1936 $node = node_load(arg(1));
1937 if ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node)) {
1938 return node_revision_overview($node);
1940 drupal_access_denied();
1943 if (is_numeric(arg(3))) {
1944 $node = node_load(arg(1), arg(3));
1946 if ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node)) {
1947 drupal_set_title(t('Revision of %title from %date', array('%title' => theme('placeholder', $node->title), '%date' => format_date($node->revision_timestamp))));
1948 return node_show($node, arg(2));
1950 drupal_access_denied();
1956 node_revision_revert(arg(1), arg(3));
1959 node_revision_delete(arg(1), arg(3));
1967 * Generate a listing of promoted nodes.
1969 function node_page_default() {
1970 $result = pager_query(db_rewrite_sql('SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.promote = 1 AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), variable_get('default_nodes_main', 10));
1972 if (db_num_rows($result)) {
1973 drupal_add_link(array('rel' => 'alternate',
1974 'type' => 'application/rss+xml',
1975 'title' => t('RSS'),
1976 'href' => url('rss.xml', NULL, NULL, TRUE)));
1979 while ($node = db_fetch_object($result)) {
1980 $output .= node_view(node_load($node->nid), 1);
1982 $output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
1986 <h1 class="title">Welcome to your new PlanetLab website!</h1>
1987 <p>Please follow these steps to set up and start using your website:</p>
1990 <strong>Login</strong>
1991 To begin, login using a PlanetLab administrative account (e.g., %plc_root_user). This account will have full administration rights and will allow you to configure both the PlanetLab and Drupal aspects of the website.
1994 <strong>Configure your website</strong>
1995 Once logged in, visit the <a href="%admin">Drupal administration section</a>, where you can <a href="%config">customize and configure</a> all Drupal aspects of the website. Visit the <a href="%db_adm">PlanetLab administration section</a> to configure your PlanetLab installation.
1998 <strong>Enable additional functionality</strong>
1999 Next, visit the <a href="%modules">module list</a> and enable features which suit your specific needs. You can find additional modules in the <a href="%download_modules">Drupal modules download section</a>.
2002 <strong>Customize your website design</strong>
2003 To change the "look and feel" of your website, visit the <a href="%themes">themes section</a>. You may choose from one of the included themes or download additional themes from the <a href="%download_themes">Drupal themes download section</a>.
2006 <strong>Start posting content</strong>
2007 Finally, you can <a href="%content">create content</a> for your website. This message will disappear once you have published your first post.
2010 <p>For more information about administering the PlanetLab portions of the website, please refer to the <a href="%myplc">MyPLC documentation</a>.</p>
2011 <p>For more information about Drupal, please refer to the <a href="%help">Help section</a>, or the <a href="%handbook">online Drupal handbooks</a>. You may also post at the <a href="%forum">Drupal forum</a>, or view the wide range of <a href="%support">other support options</a> available.</p>',
2012 array('%drupal' => 'http://drupal.org/', '%register' => url('user/register'), '%admin' => url('admin'), '%config' => url('admin/settings'), '%modules' => url('admin/modules'), '%download_modules' => 'http://drupal.org/project/modules', '%themes' => url('admin/themes'), '%download_themes' => 'http://drupal.org/project/themes', '%content' => url('node/add'), '%help' => url('admin/help'), '%handbook' => 'http://drupal.org/handbooks', '%forum' => 'http://drupal.org/forum', '%support' => 'http://drupal.org/support', '%plc_root_user' => PLC_ROOT_USER, '%db_adm' => '/db/adm/index.php', '%myplc' => '/doc/myplc/myplc.php')
2014 $output = '<div id="first-time">'. $output .'</div>';
2021 * Menu callback; dispatches control to the appropriate operation handler.
2023 function node_page() {
2026 if (is_numeric($op)) {
2027 $op = (arg(2) && !is_numeric(arg(2))) ? arg(2) : 'view';
2032 if (is_numeric(arg(1))) {
2033 $node = node_load(arg(1));
2035 drupal_set_title(check_plain($node->title));
2036 return node_show($node, arg(2));
2038 else if (db_result(db_query('SELECT nid FROM {node} WHERE nid = %d', arg(1)))) {
2039 drupal_access_denied();
2047 return node_add(arg(2));
2050 if ($_POST['op'] == t('Delete')) {
2051 // Note: we redirect from node/uid/edit to node/uid/delete to make the tabs disappear.
2052 if ($_REQUEST['destination']) {
2053 $destination = drupal_get_destination();
2054 unset($_REQUEST['destination']);
2056 drupal_goto('node/'. arg(1) .'/delete', $destination);
2059 if (is_numeric(arg(1))) {
2060 $node = node_load(arg(1));
2062 drupal_set_title(check_plain($node->title));
2063 return node_form($node);
2065 else if (db_result(db_query('SELECT nid FROM {node} WHERE nid = %d', arg(1)))) {
2066 drupal_access_denied();
2074 drupal_set_title('');
2075 return node_page_default();
2080 * shutdown function to make sure we always mark the last node processed.
2082 function node_update_shutdown() {
2083 global $last_change, $last_nid;
2085 if ($last_change && $last_nid) {
2086 variable_set('node_cron_last', $last_change);
2087 variable_set('node_cron_last_nid', $last_nid);
2092 * Implementation of hook_update_index().
2094 function node_update_index() {
2095 global $last_change, $last_nid;
2097 register_shutdown_function('node_update_shutdown');
2099 $last = variable_get('node_cron_last', 0);
2100 $last_nid = variable_get('node_cron_last_nid', 0);
2101 $limit = (int)variable_get('search_cron_limit', 100);
2103 // Store the maximum possible comments per thread (used for ranking by reply count)
2104 variable_set('node_cron_comments_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(comment_count) FROM {node_comment_statistics}'))));
2105 variable_set('node_cron_views_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(totalcount) FROM {node_counter}'))));
2107 $result = db_query_range('SELECT GREATEST(c.last_comment_timestamp, n.changed) as last_change, n.nid FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND ((GREATEST(n.changed, c.last_comment_timestamp) = %d AND n.nid > %d) OR (n.changed > %d OR c.last_comment_timestamp > %d)) ORDER BY GREATEST(n.changed, c.last_comment_timestamp) ASC, n.nid ASC', $last, $last_nid, $last, $last, $last, 0, $limit);
2109 while ($node = db_fetch_object($result)) {
2110 $last_change = $node->last_change;
2111 $last_nid = $node->nid;
2112 $node = node_load($node->nid);
2114 // Get node output (filtered and with module-specific fields).
2115 if (node_hook($node, 'view')) {
2116 node_invoke($node, 'view', false, false);
2119 $node = node_prepare($node, false);
2121 // Allow modules to change $node->body before viewing.
2122 node_invoke_nodeapi($node, 'view', false, false);
2124 $text = '<h1>'. check_plain($node->title) .'</h1>'. $node->body;
2126 // Fetch extra data normally not visible
2127 $extra = node_invoke_nodeapi($node, 'update index');
2128 foreach ($extra as $t) {
2133 search_index($node->nid, 'node', $text);
2138 * Implementation of hook_form_alter().
2140 function node_form_alter($form_id, &$form) {
2141 // Node publishing options
2142 if (isset($form['type']) && $form['type']['#value'] .'_node_settings' == $form_id) {
2143 $form['workflow']['node_options_'. $form['type']['#value']] = array('#type' => 'checkboxes',
2144 '#title' => t('Default options'),
2145 '#default_value' => variable_get('node_options_'. $form['type']['#value'], array('status', 'promote')),
2146 '#options' => array(
2147 'status' => t('Published'),
2148 'moderate' => t('In moderation queue'),
2149 'promote' => t('Promoted to front page'),
2150 'sticky' => t('Sticky at top of lists'),
2151 'revision' => t('Create new revision'),
2153 '#description' => t('Users with the <em>administer nodes</em> permission will be able to override these options.'),
2157 // Advanced node search form
2158 elseif ($form_id == 'search_form' && arg(1) == 'node') {
2160 $form['advanced'] = array(
2161 '#type' => 'fieldset',
2162 '#title' => t('Advanced search'),
2163 '#collapsible' => TRUE,
2164 '#collapsed' => TRUE,
2165 '#attributes' => array('class' => 'search-advanced'),
2167 $form['advanced']['keywords'] = array(
2168 '#prefix' => '<div class="criterion">',
2169 '#suffix' => '</div>',
2171 $form['advanced']['keywords']['or'] = array(
2172 '#type' => 'textfield',
2173 '#title' => t('Containing any of the words'),
2175 '#maxlength' => 255,
2177 $form['advanced']['keywords']['phrase'] = array(
2178 '#type' => 'textfield',
2179 '#title' => t('Containing the phrase'),
2181 '#maxlength' => 255,
2183 $form['advanced']['keywords']['negative'] = array(
2184 '#type' => 'textfield',
2185 '#title' => t('Containing none of the words'),
2187 '#maxlength' => 255,
2191 if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
2192 $form['advanced']['category'] = array(
2193 '#type' => 'select',
2194 '#title' => t('Only in the category(s)'),
2195 '#prefix' => '<div class="criterion">',
2197 '#suffix' => '</div>',
2198 '#options' => $taxonomy,
2199 '#multiple' => TRUE,
2204 $types = node_get_types();
2205 $form['advanced']['type'] = array(
2206 '#type' => 'checkboxes',
2207 '#title' => t('Only of the type(s)'),
2208 '#prefix' => '<div class="criterion">',
2209 '#suffix' => '</div>',
2210 '#options' => $types,
2212 $form['advanced']['submit'] = array(
2213 '#type' => 'submit',
2214 '#value' => t('Advanced search'),
2215 '#prefix' => '<div class="action">',
2216 '#suffix' => '</div><br class="clear" />',
2219 $form['#validate']['node_search_validate'] = array();
2224 * Form API callback for the search form. Registered in node_form_alter().
2226 function node_search_validate($form_id, $form_values, $form) {
2227 // Initialise using any existing basic search keywords.
2228 $keys = $form_values['processed_keys'];
2230 // Insert extra restrictions into the search keywords string.
2231 if (isset($form_values['type']) && is_array($form_values['type'])) {
2232 // Retrieve selected types - Forms API sets the value of unselected checkboxes to 0.
2233 $form_values['type'] = array_filter($form_values['type']);
2234 if (count($form_values['type'])) {
2235 $keys = search_query_insert($keys, 'type', implode(',', array_keys($form_values['type'])));
2239 if (isset($form_values['category']) && is_array($form_values['category'])) {
2240 $keys = search_query_insert($keys, 'category', implode(',', $form_values['category']));
2242 if ($form_values['or'] != '') {
2243 if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $form_values['or'], $matches)) {
2244 $keys .= ' '. implode(' OR ', $matches[1]);
2247 if ($form_values['negative'] != '') {
2248 if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $form_values['negative'], $matches)) {
2249 $keys .= ' -'. implode(' -', $matches[1]);
2252 if ($form_values['phrase'] != '') {
2253 $keys .= ' "'. str_replace('"', ' ', $form_values['phrase']) .'"';
2255 if (!empty($keys)) {
2256 form_set_value($form['basic']['inline']['processed_keys'], trim($keys));
2261 * @defgroup node_access Node access rights
2263 * The node access system determines who can do what to which nodes.
2265 * In determining access rights for a node, node_access() first checks
2266 * whether the user has the "administer nodes" permission. Such users have
2267 * unrestricted access to all nodes. Then the node module's hook_access()
2268 * is called, and a TRUE or FALSE return value will grant or deny access.
2269 * This allows, for example, the blog module to always grant access to the
2270 * blog author, and for the book module to always deny editing access to
2273 * If node module does not intervene (returns NULL), then the
2274 * node_access table is used to determine access. All node access
2275 * modules are queried using hook_node_grants() to assemble a list of
2276 * "grant IDs" for the user. This list is compared against the table.
2277 * If any row contains the node ID in question (or 0, which stands for "all
2278 * nodes"), one of the grant IDs returned, and a value of TRUE for the
2279 * operation in question, then access is granted. Note that this table is a
2280 * list of grants; any matching row is sufficient to grant access to the
2283 * In node listings, the process above is followed except that
2284 * hook_access() is not called on each node for performance reasons and for
2285 * proper functioning of the pager system. When adding a node listing to your
2286 * module, be sure to use db_rewrite_sql() to add
2287 * the appropriate clauses to your query for access checks.
2289 * To see how to write a node access module of your own, see
2290 * node_access_example.module.
2294 * Determine whether the current user may perform the given operation on the
2298 * The operation to be performed on the node. Possible values are:
2304 * The node object (or node array) on which the operation is to be performed,
2305 * or node type (e.g. 'forum') for "create" operation.
2307 * The user ID on which the operation is to be performed.
2309 * TRUE if the operation may be performed.
2311 function node_access($op, $node = NULL, $uid = NULL) {
2312 // Convert the node to an object if necessary:
2313 if ($op != 'create') {
2314 $node = (object)$node;
2316 // If the node is in a restricted format, disallow editing.
2317 if ($op == 'update' && !filter_access($node->format)) {
2321 if (user_access('administer nodes')) {
2325 if (!user_access('access content')) {
2329 // Can't use node_invoke(), because the access hook takes the $op parameter
2330 // before the $node parameter.
2331 $access = module_invoke(node_get_base($node), 'access', $op, $node);
2332 if (!is_null($access)) {
2336 // If the module did not override the access rights, use those set in the
2337 // node_access table.
2338 if ($op != 'create' && $node->nid && $node->status) {
2340 foreach (node_access_grants($op, $uid) as $realm => $gids) {
2341 foreach ($gids as $gid) {
2342 $grants[] = "(gid = $gid AND realm = '$realm')";
2347 if (count($grants)) {
2348 $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
2351 $sql = "SELECT COUNT(*) FROM {node_access} WHERE (nid = 0 OR nid = %d) $grants_sql AND grant_$op >= 1";
2352 $result = db_query($sql, $node->nid);
2353 return (db_result($result));
2359 * Generate an SQL join clause for use in fetching a node listing.
2361 * @param $node_alias
2362 * If the node table has been given an SQL alias other than the default
2363 * "n", that must be passed here.
2364 * @param $node_access_alias
2365 * If the node_access table has been given an SQL alias other than the default
2366 * "na", that must be passed here.
2368 * An SQL join clause.
2370 function _node_access_join_sql($node_alias = 'n', $node_access_alias = 'na') {
2371 if (user_access('administer nodes')) {
2375 return 'INNER JOIN {node_access} '. $node_access_alias .' ON '. $node_access_alias .'.nid = '. $node_alias .'.nid';
2379 * Generate an SQL where clause for use in fetching a node listing.
2382 * The operation that must be allowed to return a node.
2383 * @param $node_access_alias
2384 * If the node_access table has been given an SQL alias other than the default
2385 * "na", that must be passed here.
2387 * An SQL where clause.
2389 function _node_access_where_sql($op = 'view', $node_access_alias = 'na', $uid = NULL) {
2390 if (user_access('administer nodes')) {
2395 foreach (node_access_grants($op, $uid) as $realm => $gids) {
2396 foreach ($gids as $gid) {
2397 $grants[] = "($node_access_alias.gid = $gid AND $node_access_alias.realm = '$realm')";
2402 if (count($grants)) {
2403 $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
2406 $sql = "$node_access_alias.grant_$op >= 1 $grants_sql";
2411 * Fetch an array of permission IDs granted to the given user ID.
2413 * The implementation here provides only the universal "all" grant. A node
2414 * access module should implement hook_node_grants() to provide a grant
2415 * list for the user.
2418 * The operation that the user is trying to perform.
2420 * The user ID performing the operation. If omitted, the current user is used.
2422 * An associative array in which the keys are realms, and the values are
2423 * arrays of grants for those realms.
2425 function node_access_grants($op, $uid = NULL) {
2429 $user_object = user_load(array('uid' => $uid));
2432 $user_object = $user;
2435 return array_merge(array('all' => array(0)), module_invoke_all('node_grants', $user_object, $op));
2439 * Determine whether the user has a global viewing grant for all nodes.
2441 function node_access_view_all_nodes() {
2444 if (!isset($access)) {
2446 foreach (node_access_grants('view') as $realm => $gids) {
2447 foreach ($gids as $gid) {
2448 $grants[] = "(gid = $gid AND realm = '$realm')";
2453 if (count($grants)) {
2454 $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
2457 $sql = "SELECT COUNT(*) FROM {node_access} WHERE nid = 0 $grants_sql AND grant_view >= 1";
2458 $result = db_query($sql);
2459 $access = db_result($result);
2466 * Implementation of hook_db_rewrite_sql
2468 function node_db_rewrite_sql($query, $primary_table, $primary_field) {
2469 if ($primary_field == 'nid' && !node_access_view_all_nodes()) {
2470 $return['join'] = _node_access_join_sql($primary_table);
2471 $return['where'] = _node_access_where_sql();
2472 $return['distinct'] = 1;
2478 * @} End of "defgroup node_access".