6 * The core that allows content to be submitted to the site.
9 define('NODE_NEW_LIMIT', time() - 30 * 24 * 60 * 60);
12 * Implementation of hook_help().
14 function node_help($section) {
16 case 'admin/help#node':
17 $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>';
18 $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>';
19 $output .= t('<p>Node module features</p>
21 <li>The list tab provides an interface to search and sort all content on your site.</li>
22 <li>The configure settings tab has basic settings for content on your site.</li>
23 <li>The configure content types tab lists all content types for your site and lets you configure their default workflow.</li>
24 <li>The search tab lets you search all content on your site</li>
27 $output .= t('<p>You can</p>
29 <li>search for content at <a href="%search">search</a>.</li>
30 <li>administer nodes at <a href="%admin-settings-content-types">administer >> settings >> content types</a>.</li>
32 ', array('%search' => url('search'), '%admin-settings-content-types' => url('admin/settings/content-types')));
33 $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>';
35 case 'admin/modules#description':
36 return t('Allows content to be submitted to the site and displayed on pages.');
37 case 'admin/node/configure':
38 case 'admin/node/configure/settings':
39 return t('<p>Settings for the core of Drupal. Almost everything is a node so these settings will affect most of the site.</p>');
41 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')));
42 case 'admin/node/search':
43 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>');
46 if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == 'revisions' && !arg(3)) {
47 return t('The revisions let you track differences between multiple versions of a post.');
50 if (arg(0) == 'node' && arg(1) == 'add' && $type = arg(2)) {
51 return filter_xss_admin(variable_get($type .'_help', ''));
56 * Implementation of hook_cron().
58 function node_cron() {
59 db_query('DELETE FROM {history} WHERE timestamp < %d', NODE_NEW_LIMIT);
63 * Gather a listing of links to nodes.
66 * 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.
68 * A heading for the resulting list.
71 * An HTML list suitable as content for a block.
73 function node_title_list($result, $title = NULL) {
74 while ($node = db_fetch_object($result)) {
75 $items[] = l($node->title, 'node/'. $node->nid, $node->comment_count ? array('title' => format_plural($node->comment_count, '1 comment', '%count comments')) : '');
78 return theme('node_list', $items, $title);
82 * Format a listing of links to nodes.
84 function theme_node_list($items, $title = NULL) {
85 return theme('item_list', $items, $title);
89 * Update the 'last viewed' timestamp of the specified node for current user.
91 function node_tag_new($nid) {
95 if (node_last_viewed($nid)) {
96 db_query('UPDATE {history} SET timestamp = %d WHERE uid = %d AND nid = %d', time(), $user->uid, $nid);
99 @db_query('INSERT INTO {history} (uid, nid, timestamp) VALUES (%d, %d, %d)', $user->uid, $nid, time());
105 * Retrieves the timestamp at which the current user last viewed the
108 function node_last_viewed($nid) {
112 if (!isset($history[$nid])) {
113 $history[$nid] = db_fetch_object(db_query("SELECT timestamp FROM {history} WHERE uid = '$user->uid' AND nid = %d", $nid));
116 return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp : 0);
120 * Decide on the type of marker to be displayed for a given node.
123 * Node ID whose history supplies the "last viewed" timestamp.
125 * Time which is compared against node's "last viewed" timestamp.
127 * One of the MARK constants.
129 function node_mark($nid, $timestamp) {
136 if (!isset($cache[$nid])) {
137 $cache[$nid] = node_last_viewed($nid);
139 if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) {
142 elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) {
149 * Automatically generate a teaser for a node body in a given format.
151 function node_teaser($body, $format = NULL) {
153 $size = variable_get('teaser_length', 600);
155 // find where the delimiter is in the body
156 $delimiter = strpos($body, '<!--break-->');
158 // If the size is zero, and there is no delimiter, the entire body is the teaser.
159 if ($size == 0 && $delimiter === FALSE) {
163 // If a valid delimiter has been specified, use it to chop off the teaser.
164 if ($delimiter !== FALSE) {
165 return substr($body, 0, $delimiter);
168 // We check for the presence of the PHP evaluator filter in the current
169 // format. If the body contains PHP code, we do not split it up to prevent
171 if (isset($format)) {
172 $filters = filter_list_format($format);
173 if (isset($filters['filter/1']) && strpos($body, '<?') !== FALSE) {
178 // If we have a short body, the entire body is the teaser.
179 if (strlen($body) < $size) {
183 // The teaser may not be longer than maximum length specified. Initial slice.
184 $teaser = truncate_utf8($body, $size);
186 // Cache the reverse of the teaser.
187 $reversed = strrev($teaser);
189 // In some cases, no delimiter has been specified. In this case, we try to
190 // split at paragraph boundaries.
191 $breakpoints = array('</p>' => 0, '<br />' => 6, '<br>' => 4, "\n" => 1);
192 // We use strpos on the reversed needle and haystack for speed.
193 foreach ($breakpoints as $point => $offset) {
194 $length = strpos($reversed, strrev($point));
195 if ($length !== FALSE) {
196 $position = - $length - $offset;
197 return ($position == 0) ? $teaser : substr($teaser, 0, $position);
201 // When even the first paragraph is too long, we try to split at the end of
202 // the last full sentence.
203 $breakpoints = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟ ' => 1);
204 $min_length = strlen($reversed);
205 foreach ($breakpoints as $point => $offset) {
206 $length = strpos($reversed, strrev($point));
207 if ($length !== FALSE) {
208 $min_length = min($length, $min_length);
209 $position = 0 - $length - $offset;
212 return ($position == 0) ? $teaser : substr($teaser, 0, $position);
215 function _node_names($op = '', $node = NULL) {
216 static $node_names = array();
217 static $node_list = array();
219 if (empty($node_names)) {
220 $node_names = module_invoke_all('node_info');
221 foreach ($node_names as $type => $value) {
222 $node_list[$type] = $value['name'];
226 if (is_array($node)) {
227 $type = $node['type'];
229 elseif (is_object($node)) {
232 elseif (is_string($node)) {
235 if (!isset($node_names[$type])) {
241 return $node_names[$type]['base'];
245 return $node_list[$type];
250 * Determine the basename for hook_load etc.
253 * Either a node object, a node array, or a string containing the node type.
255 * The basename for hook_load, hook_nodeapi etc.
257 function node_get_base($node) {
258 return _node_names('base', $node);
262 * Determine the human readable name for a given type.
265 * Either a node object, a node array, or a string containing the node type.
267 * The human readable name of the node type.
269 function node_get_name($node) {
270 return _node_names('name', $node);
274 * Return the list of available node types.
277 * An array consisting ('#type' => name) pairs.
279 function node_get_types() {
280 return _node_names('list');
284 * Determine whether a node hook exists.
287 * Either a node object, node array, or a string containing the node type.
289 * A string containing the name of the hook.
291 * TRUE iff the $hook exists in the node type of $node.
293 function node_hook(&$node, $hook) {
294 return module_hook(node_get_base($node), $hook);
298 * Invoke a node hook.
301 * Either a node object, node array, or a string containing the node type.
303 * A string containing the name of the hook.
304 * @param $a2, $a3, $a4
305 * Arguments to pass on to the hook, after the $node argument.
307 * The returned value of the invoked hook.
309 function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
310 if (node_hook($node, $hook)) {
311 $function = node_get_base($node) ."_$hook";
312 return ($function($node, $a2, $a3, $a4));
317 * Invoke a hook_nodeapi() operation in all modules.
322 * A string containing the name of the nodeapi operation.
324 * Arguments to pass on to the hook, after the $node and $op arguments.
326 * The returned value of the invoked hooks.
328 function node_invoke_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
330 foreach (module_implements('nodeapi') as $name) {
331 $function = $name .'_nodeapi';
332 $result = $function($node, $op, $a3, $a4);
333 if (isset($result) && is_array($result)) {
334 $return = array_merge($return, $result);
336 else if (isset($result)) {
344 * Load a node object from the database.
347 * Either the nid of the node or an array of conditions to match against in the database query
349 * Which numbered revision to load. Defaults to the current version.
351 * Whether to reset the internal node_load cache.
354 * A fully-populated node object.
356 function node_load($param = array(), $revision = NULL, $reset = NULL) {
357 static $nodes = array();
363 $cachable = ($revision == NULL);
364 $arguments = array();
365 if (is_numeric($param)) {
366 if ($cachable && isset($nodes[$param])) {
367 return is_object($nodes[$param]) ? drupal_clone($nodes[$param]) : $nodes[$param];
369 $cond = 'n.nid = %d';
370 $arguments[] = $param;
373 // Turn the conditions into a query.
374 foreach ($param as $key => $value) {
375 $cond[] = 'n.'. db_escape_string($key) ." = '%s'";
376 $arguments[] = $value;
378 $cond = implode(' AND ', $cond);
381 // Retrieve the node.
382 // No db_rewrite_sql is applied so as to get complete indexing for search.
384 array_unshift($arguments, $revision);
385 $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));
388 $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));
392 // Call the node specific callback (if any) and piggy-back the
393 // results to the node or overwrite some values.
394 if ($extra = node_invoke($node, 'load')) {
395 foreach ($extra as $key => $value) {
396 $node->$key = $value;
400 if ($extra = node_invoke_nodeapi($node, 'load')) {
401 foreach ($extra as $key => $value) {
402 $node->$key = $value;
406 $nodes[$node->nid] = is_object($node) ? drupal_clone($node) : $node;
414 * Save a node object into the database.
416 function node_save(&$node) {
419 $node->is_new = false;
421 // Apply filters to some default node fields:
422 if (empty($node->nid)) {
423 // Insert a new node.
424 $node->is_new = true;
426 $node->nid = db_next_id('{node}_nid');
427 $node->vid = db_next_id('{node_revisions}_vid');;
430 // We need to ensure that all node fields are filled.
431 $node_current = node_load($node->nid);
432 foreach ($node as $field => $data) {
433 $node_current->$field = $data;
435 $node = $node_current;
437 if ($node->revision) {
438 $node->old_vid = $node->vid;
439 $node->vid = db_next_id('{node_revisions}_vid');
443 // Set some required fields:
444 if (empty($node->created)) {
445 $node->created = time();
447 // The changed timestamp is always updated for bookkeeping purposes (revisions, searching, ...)
448 $node->changed = time();
450 // Split off revisions data to another structure
451 $revisions_table_values = array('nid' => $node->nid, 'vid' => $node->vid,
452 'title' => $node->title, 'body' => $node->body,
453 'teaser' => $node->teaser, 'log' => $node->log, 'timestamp' => $node->changed,
454 'uid' => $user->uid, 'format' => $node->format);
455 $revisions_table_types = array('nid' => '%d', 'vid' => '%d',
456 'title' => "'%s'", 'body' => "'%s'",
457 'teaser' => "'%s'", 'log' => "'%s'", 'timestamp' => '%d',
458 'uid' => '%d', 'format' => '%d');
459 $node_table_values = array('nid' => $node->nid, 'vid' => $node->vid,
460 'title' => $node->title, 'type' => $node->type, 'uid' => $node->uid,
461 'status' => $node->status, 'created' => $node->created,
462 'changed' => $node->changed, 'comment' => $node->comment,
463 'promote' => $node->promote, 'moderate' => $node->moderate,
464 'sticky' => $node->sticky);
465 $node_table_types = array('nid' => '%d', 'vid' => '%d',
466 'title' => "'%s'", 'type' => "'%s'", 'uid' => '%d',
467 'status' => '%d', 'created' => '%d',
468 'changed' => '%d', 'comment' => '%d',
469 'promote' => '%d', 'moderate' => '%d',
472 //Generate the node table query and the
473 //the node_revisions table query
475 $node_query = 'INSERT INTO {node} ('. implode(', ', array_keys($node_table_types)) .') VALUES ('. implode(', ', $node_table_types) .')';
476 $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')';
480 foreach ($node_table_types as $key => $value) {
481 $arr[] = $key .' = '. $value;
483 $node_table_values[] = $node->nid;
484 $node_query = 'UPDATE {node} SET '. implode(', ', $arr) .' WHERE nid = %d';
485 if ($node->revision) {
486 $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')';
490 foreach ($revisions_table_types as $key => $value) {
491 $arr[] = $key .' = '. $value;
493 $revisions_table_values[] = $node->vid;
494 $revisions_query = 'UPDATE {node_revisions} SET '. implode(', ', $arr) .' WHERE vid = %d';
498 // Insert the node into the database:
499 db_query($node_query, $node_table_values);
500 db_query($revisions_query, $revisions_table_values);
502 // Call the node specific callback (if any):
504 node_invoke($node, 'insert');
505 node_invoke_nodeapi($node, 'insert');
508 node_invoke($node, 'update');
509 node_invoke_nodeapi($node, 'update');
512 // Clear the cache so an anonymous poster can see the node being added or updated.
517 * Generate a display of the given node.
520 * A node array or node object.
522 * Whether to display the teaser only, as on the main page.
524 * Whether the node is being displayed by itself as a page.
526 * Whether or not to display node links. Links are omitted for node previews.
529 * An HTML representation of the themed node.
531 function node_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) {
532 $node = (object)$node;
534 // Remove the delimiter (if any) that separates the teaser from the body.
535 // TODO: this strips legitimate uses of '<!--break-->' also.
536 $node->body = str_replace('<!--break-->', '', $node->body);
538 if ($node->log != '' && !$teaser && $node->moderate) {
539 $node->body .= '<div class="log"><div class="title">'. t('Log') .':</div>'. filter_xss($node->log) .'</div>';
542 // The 'view' hook can be implemented to overwrite the default function
544 if (node_hook($node, 'view')) {
545 node_invoke($node, 'view', $teaser, $page);
548 $node = node_prepare($node, $teaser);
550 // Allow modules to change $node->body before viewing.
551 node_invoke_nodeapi($node, 'view', $teaser, $page);
553 $node->links = module_invoke_all('link', 'node', $node, !$page);
555 // unset unused $node part so that a bad theme can not open a security hole
560 unset($node->teaser);
563 return theme('node', $node, $teaser, $page);
567 * Apply filters to a node in preparation for theming.
569 function node_prepare($node, $teaser = FALSE) {
570 $node->readmore = (strlen($node->teaser) < strlen($node->body));
571 if ($teaser == FALSE) {
572 $node->body = check_markup($node->body, $node->format, FALSE);
575 $node->teaser = check_markup($node->teaser, $node->format, FALSE);
581 * Generate a page displaying a single node, along with its comments.
583 function node_show($node, $cid) {
584 $output = node_view($node, FALSE, TRUE);
586 if (function_exists('comment_render') && $node->comment) {
587 $output .= comment_render($node, $cid);
590 // Update the history table, stating that this user viewed this node.
591 node_tag_new($node->nid);
597 * Implementation of hook_perm().
599 function node_perm() {
600 return array('administer nodes', 'access content', 'view revisions', 'revert revisions');
604 * Implementation of hook_search().
606 function node_search($op = 'search', $keys = null) {
612 variable_del('node_cron_last');
613 variable_del('node_cron_last_nid');
617 $last = variable_get('node_cron_last', 0);
618 $last_nid = variable_get('node_cron_last_nid', 0);
619 $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1'));
620 $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));
621 return array('remaining' => $remaining, 'total' => $total);
625 // Output form for defining rank factor weights.
626 $form['content_ranking'] = array('#type' => 'fieldset', '#title' => t('Content ranking'));
627 $form['content_ranking']['#theme'] = 'node_search_admin';
628 $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>');
630 $ranking = array('node_rank_relevance' => t('Keyword relevance'),
631 'node_rank_recent' => t('Recently posted'));
632 if (module_exist('comment')) {
633 $ranking['node_rank_comments'] = t('Number of comments');
635 if (module_exist('statistics') && variable_get('statistics_count_content_views', 0)) {
636 $ranking['node_rank_views'] = t('Number of views');
639 // Note: reversed to reflect that higher number = higher ranking.
640 $options = drupal_map_assoc(range(0, 10));
641 foreach ($ranking as $var => $title) {
642 $form['content_ranking']['factors'][$var] = array('#title' => $title, '#type' => 'select', '#options' => $options, '#default_value' => variable_get($var, 5));
647 // Build matching conditions
648 list($join1, $where1) = _db_rewrite_sql();
649 $arguments1 = array();
650 $conditions1 = 'n.status = 1';
652 if ($type = search_query_extract($keys, 'type')) {
654 foreach (explode(',', $type) as $t) {
655 $types[] = "n.type = '%s'";
658 $conditions1 .= ' AND ('. implode(' OR ', $types) .')';
659 $keys = search_query_insert($keys, 'type');
662 if ($category = search_query_extract($keys, 'category')) {
663 $categories = array();
664 foreach (explode(',', $category) as $c) {
665 $categories[] = "tn.tid = %d";
668 $conditions1 .= ' AND ('. implode(' OR ', $categories) .')';
669 $join1 .= ' INNER JOIN {term_node} tn ON n.nid = tn.nid';
670 $keys = search_query_insert($keys, 'category');
673 // Build ranking expression (we try to map each parameter to a
674 // uniform distribution in the range 0..1).
676 $arguments2 = array();
679 // Used to avoid joining on node_comment_statistics twice
681 if ($weight = (int)variable_get('node_rank_relevance', 5)) {
682 // Average relevance values hover around 0.15
683 $ranking[] = '%d * i.relevance';
684 $arguments2[] = $weight;
687 if ($weight = (int)variable_get('node_rank_recent', 5)) {
688 // Exponential decay with half-life of 6 months, starting at last indexed node
689 $ranking[] = '%d * POW(2, (GREATEST(n.created, n.changed, c.last_comment_timestamp) - %d) * 6.43e-8)';
690 $arguments2[] = $weight;
691 $arguments2[] = (int)variable_get('node_cron_last', 0);
692 $join2 .= ' INNER JOIN {node} n ON n.nid = i.sid LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
696 if (module_exist('comment') && $weight = (int)variable_get('node_rank_comments', 5)) {
697 // Inverse law that maps the highest reply count on the site to 1 and 0 to 0.
698 $scale = variable_get('node_cron_comments_scale', 0.0);
699 $ranking[] = '%d * (2.0 - 2.0 / (1.0 + c.comment_count * %f))';
700 $arguments2[] = $weight;
701 $arguments2[] = $scale;
703 $join2 .= ' LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
707 if (module_exist('statistics') && variable_get('statistics_count_content_views', 0) &&
708 $weight = (int)variable_get('node_rank_views', 5)) {
709 // Inverse law that maps the highest view count on the site to 1 and 0 to 0.
710 $scale = variable_get('node_cron_views_scale', 0.0);
711 $ranking[] = '%d * (2.0 - 2.0 / (1.0 + nc.totalcount * %f))';
712 $arguments2[] = $weight;
713 $arguments2[] = $scale;
714 $join2 .= ' LEFT JOIN {node_counter} nc ON nc.nid = i.sid';
717 $select2 = (count($ranking) ? implode(' + ', $ranking) : 'i.relevance') . ' AS score';
720 $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);
724 foreach ($find as $item) {
725 $node = node_load($item->sid);
727 // Get node output (filtered and with module-specific fields).
728 if (node_hook($node, 'view')) {
729 node_invoke($node, 'view', false, false);
732 $node = node_prepare($node, false);
734 // Allow modules to change $node->body before viewing.
735 node_invoke_nodeapi($node, 'view', false, false);
737 // Fetch comments for snippet
738 $node->body .= module_invoke('comment', 'nodeapi', $node, 'update index');
739 // Fetch terms for snippet
740 $node->body .= module_invoke('taxonomy', 'nodeapi', $node, 'update index');
742 $extra = node_invoke_nodeapi($node, 'search result');
743 $results[] = array('link' => url('node/'. $item->sid),
744 'type' => node_get_name($node),
745 'title' => $node->title,
746 'user' => theme('username', $node),
747 'date' => $node->changed,
750 'score' => $item->score / $total,
751 'snippet' => search_excerpt($keys, $node->body));
758 * Implementation of hook_user().
760 function node_user($op, &$edit, &$user) {
761 if ($op == 'delete') {
762 db_query('UPDATE {node} SET uid = 0 WHERE uid = %d', $user->uid);
763 db_query('UPDATE {node_revisions} SET uid = 0 WHERE uid = %d', $user->uid);
767 function theme_node_search_admin($form) {
768 $output = form_render($form['info']);
770 $header = array(t('Factor'), t('Weight'));
771 foreach (element_children($form['factors']) as $key) {
773 $row[] = $form['factors'][$key]['#title'];
774 unset($form['factors'][$key]['#title']);
775 $row[] = form_render($form['factors'][$key]);
778 $output .= theme('table', $header, $rows);
780 $output .= form_render($form);
785 * Menu callback; presents general node configuration options.
787 function node_configure() {
789 $form['default_nodes_main'] = array(
790 '#type' => 'select', '#title' => t('Number of posts on main page'), '#default_value' => variable_get('default_nodes_main', 10),
791 '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30)),
792 '#description' => t('The default maximum number of posts to display per page on overview pages such as the main page.')
795 $form['teaser_length'] = array(
796 '#type' => 'select', '#title' => t('Length of trimmed posts'), '#default_value' => variable_get('teaser_length', 600),
797 '#options' => array(0 => t('Unlimited'), 200 => t('200 characters'), 400 => t('400 characters'), 600 => t('600 characters'),
798 800 => t('800 characters'), 1000 => t('1000 characters'), 1200 => t('1200 characters'), 1400 => t('1400 characters'),
799 1600 => t('1600 characters'), 1800 => t('1800 characters'), 2000 => t('2000 characters')),
800 '#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.")
803 $form['node_preview'] = array(
804 '#type' => 'radios', '#title' => t('Preview post'), '#default_value' => variable_get('node_preview', 0),
805 '#options' => array(t('Optional'), t('Required')), '#description' => t('Must users preview posts before submitting?')
808 return system_settings_form('node_configure', $form);
812 * Retrieve the comment mode for the given node ID (none, read, or read/write).
814 function node_comment_mode($nid) {
815 static $comment_mode;
816 if (!isset($comment_mode[$nid])) {
817 $comment_mode[$nid] = db_result(db_query('SELECT comment FROM {node} WHERE nid = %d', $nid));
819 return $comment_mode[$nid];
823 * Implementation of hook_link().
825 function node_link($type, $node = 0, $main = 0) {
828 if ($type == 'node') {
829 if ($main == 1 && $node->teaser && $node->readmore) {
830 $links[] = l(t('read more'), "node/$node->nid", array('title' => t('Read the rest of this posting.'), 'class' => 'read-more'));
838 * Implementation of hook_menu().
840 function node_menu($may_cache) {
844 $items[] = array('path' => 'admin/node', 'title' => t('content'),
845 'callback' => 'node_admin_nodes',
846 'access' => user_access('administer nodes'));
847 $items[] = array('path' => 'admin/node/overview', 'title' => t('list'),
848 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
850 if (module_exist('search')) {
851 $items[] = array('path' => 'admin/node/search', 'title' => t('search'),
852 'callback' => 'node_admin_search',
853 'access' => user_access('administer nodes'),
854 'type' => MENU_LOCAL_TASK);
857 $items[] = array('path' => 'admin/settings/node', 'title' => t('posts'),
858 'callback' => 'node_configure',
859 'access' => user_access('administer nodes'));
860 $items[] = array('path' => 'admin/settings/content-types', 'title' => t('content types'),
861 'callback' => 'node_types_configure',
862 'access' => user_access('administer nodes'));
864 $items[] = array('path' => 'node', 'title' => t('content'),
865 'callback' => 'node_page',
866 'access' => user_access('access content'),
867 'type' => MENU_MODIFIABLE_BY_ADMIN);
868 $items[] = array('path' => 'node/add', 'title' => t('create content'),
869 'callback' => 'node_page',
870 'access' => user_access('access content'),
871 'type' => MENU_ITEM_GROUPING,
873 $items[] = array('path' => 'rss.xml', 'title' => t('rss feed'),
874 'callback' => 'node_feed',
875 'access' => user_access('access content'),
876 'type' => MENU_CALLBACK);
879 if (arg(0) == 'node' && is_numeric(arg(1))) {
880 $node = node_load(arg(1));
882 $items[] = array('path' => 'node/'. arg(1), 'title' => t('view'),
883 'callback' => 'node_page',
884 'access' => node_access('view', $node),
885 'type' => MENU_CALLBACK);
886 $items[] = array('path' => 'node/'. arg(1) .'/view', 'title' => t('view'),
887 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
888 $items[] = array('path' => 'node/'. arg(1) .'/edit', 'title' => t('edit'),
889 'callback' => 'node_page',
890 'access' => node_access('update', $node),
892 'type' => MENU_LOCAL_TASK);
893 $items[] = array('path' => 'node/'. arg(1) .'/delete', 'title' => t('delete'),
894 'callback' => 'node_delete_confirm',
895 'access' => node_access('delete', $node),
897 'type' => MENU_CALLBACK);
898 $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);
899 $items[] = array('path' => 'node/'. arg(1) .'/revisions', 'title' => t('revisions'),
900 'callback' => 'node_revisions',
901 'access' => $revisions_access,
903 'type' => MENU_LOCAL_TASK);
904 $items[] = array('path' => 'node/'. arg(1) .'/revisions/' . arg(3) . '/delete',
905 'title' => t('revisions'),
906 'callback' => 'node_revisions',
907 'access' => $revisions_access,
909 'type' => MENU_CALLBACK);
910 $items[] = array('path' => 'node/'. arg(1) .'/revisions/' . arg(3) . '/revert',
911 'title' => t('revisions'),
912 'callback' => 'node_revisions',
913 'access' => $revisions_access,
915 'type' => MENU_CALLBACK);
918 else if (arg(0) == 'admin' && arg(1) == 'settings' && arg(2) == 'content-types' && is_string(arg(3))) {
919 $items[] = array('path' => 'admin/settings/content-types/'. arg(3),
920 'title' => t("'%name' content type", array('%name' => node_get_name(arg(3)))),
921 'type' => MENU_CALLBACK);
928 function node_last_changed($nid) {
929 $node = db_fetch_object(db_query('SELECT changed FROM {node} WHERE nid = %d', $nid));
930 return ($node->changed);
934 * List node administration operations that can be performed.
936 function node_operations() {
938 'approve' => array(t('Approve the selected posts'), 'UPDATE {node} SET status = 1, moderate = 0 WHERE nid = %d'),
939 'promote' => array(t('Promote the selected posts'), 'UPDATE {node} SET status = 1, promote = 1, moderate = 0 WHERE nid = %d'),
940 'sticky' => array(t('Make the selected posts sticky'), 'UPDATE {node} SET status = 1, sticky = 1 WHERE nid = %d'),
941 'demote' => array(t('Demote the selected posts'), 'UPDATE {node} SET promote = 0 WHERE nid = %d'),
942 'unpublish' => array(t('Unpublish the selected posts'), 'UPDATE {node} SET status = 0 WHERE nid = %d'),
943 'delete' => array(t('Delete the selected posts'), '')
949 * List node administration filters that can be applied.
951 function node_filters() {
953 $filters['status'] = array('title' => t('status'),
954 'options' => array('status-1' => t('published'), 'status-0' => t('not published'),
955 'moderate-1' => t('in moderation'), 'moderate-0' => t('not in moderation'),
956 'promote-1' => t('promoted'), 'promote-0' => t('not promoted'),
957 'sticky-1' => t('sticky'), 'sticky-0' => t('not sticky')));
958 $filters['type'] = array('title' => t('type'), 'options' => node_get_types());
959 // The taxonomy filter
960 if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
961 $filters['category'] = array('title' => t('category'), 'options' => $taxonomy);
968 * Build query for node administration filters based on session.
970 function node_build_filter_query() {
971 $filters = node_filters();
974 $where = $args = array();
976 foreach ($_SESSION['node_overview_filter'] as $index => $filter) {
977 list($key, $value) = $filter;
980 // Note: no exploitable hole as $key/$value have already been checked when submitted
981 list($key, $value) = explode('-', $value, 2);
982 $where[] = 'n.'. $key .' = %d';
986 $where[] = "$table.tid = %d";
987 $join .= "INNER JOIN {term_node} $table ON n.nid = $table.nid ";
990 $where[] = "n.type = '%s'";
994 $where = count($where) ? 'WHERE '. implode(' AND ', $where) : '';
996 return array('where' => $where, 'join' => $join, 'args' => $args);
1000 * Return form for node administration filters.
1002 function node_filter_form() {
1003 $session = &$_SESSION['node_overview_filter'];
1004 $session = is_array($session) ? $session : array();
1005 $filters = node_filters();
1008 $form['filters'] = array('#type' => 'fieldset',
1009 '#title' => t('Show only items where'),
1010 '#theme' => 'node_filters',
1012 foreach ($session as $filter) {
1013 list($type, $value) = $filter;
1014 if ($type == 'category') {
1015 // Load term name from DB rather than search and parse options array.
1016 $value = module_invoke('taxonomy', 'get_term', $value);
1017 $value = $value->name;
1020 $value = $filters[$type]['options'][$value];
1022 $string = ($i++ ? '<em>and</em> where <strong>%a</strong> is <strong>%b</strong>' : '<strong>%a</strong> is <strong>%b</strong>');
1023 $form['filters']['current'][] = array('#value' => t($string, array('%a' => $filters[$type]['title'] , '%b' => $value)));
1026 foreach ($filters as $key => $filter) {
1027 $names[$key] = $filter['title'];
1028 $form['filters']['status'][$key] = array('#type' => 'select', '#options' => $filter['options']);
1031 $form['filters']['filter'] = array('#type' => 'radios', '#options' => $names, '#default_value' => 'status');
1032 $form['filters']['buttons']['submit'] = array('#type' => 'submit', '#value' => (count($session) ? t('Refine') : t('Filter')));
1033 if (count($session)) {
1034 $form['filters']['buttons']['undo'] = array('#type' => 'submit', '#value' => t('Undo'));
1035 $form['filters']['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset'));
1038 return drupal_get_form('node_filter_form', $form);
1042 * Theme node administration filter form.
1044 function theme_node_filter_form(&$form) {
1045 $output .= '<div id="node-admin-filter">';
1046 $output .= form_render($form['filters']);
1047 $output .= '</div>';
1048 $output .= form_render($form);
1053 * Theme node administraton filter selector.
1055 function theme_node_filters(&$form) {
1057 if (sizeof($form['current'])) {
1058 foreach (element_children($form['current']) as $key) {
1059 $output .= '<li>' . form_render($form['current'][$key]) . '</li>';
1063 $output .= '<li><dl class="multiselect">' . (sizeof($form['current']) ? '<dt><em>'. t('and') .'</em> '. t('where') .'</dt>' : '') . '<dd class="a">';
1064 foreach (element_children($form['filter']) as $key) {
1065 $output .= form_render($form['filter'][$key]);
1069 $output .= '<dt>'. t('is') .'</dt>' . '<dd class="b">';
1071 foreach (element_children($form['status']) as $key) {
1072 $output .= form_render($form['status'][$key]);
1077 $output .= '<div class="container-inline" id="node-admin-buttons">'. form_render($form['buttons']) .'</div>';
1078 $output .= '</li></ul><br class="clear" />';
1084 * Process result from node administration filter form.
1086 function node_filter_form_submit() {
1087 global $form_values;
1089 $filters = node_filters();
1093 if (isset($form_values['filter'])) {
1094 $filter = $form_values['filter'];
1096 // Flatten the options array to accommodate hierarchical/nested options.
1097 $flat_options = form_options_flatten($filters[$filter]['options']);
1099 if (isset($flat_options[$form_values[$filter]])) {
1100 $_SESSION['node_overview_filter'][] = array($filter, $form_values[$filter]);
1105 array_pop($_SESSION['node_overview_filter']);
1108 $_SESSION['node_overview_filter'] = array();
1114 * Generate the content administration overview.
1116 function node_admin_nodes_submit($form_id, $edit) {
1117 $operations = node_operations();
1118 if ($operations[$edit['operation']][1]) {
1120 $operation = $operations[$edit['operation']][1];
1121 foreach ($edit['nodes'] as $nid => $value) {
1123 db_query($operation, $nid);
1127 drupal_set_message(t('The update has been performed.'));
1131 function node_admin_nodes_validate($form_id, $edit) {
1132 $edit['nodes'] = array_diff($edit['nodes'], array(0));
1133 if (count($edit['nodes']) == 0) {
1134 if ($edit['operation'] == 'delete') {
1135 form_set_error('', t('Please select some items to perform the delete operation.'));
1138 form_set_error('', t('Please select some items to perform the update on.'));
1144 * Menu callback: content administration.
1146 function node_admin_nodes() {
1147 global $form_values;
1148 $output = node_filter_form();
1150 if ($_POST['edit']['operation'] == 'delete' && $_POST['edit']['nodes']) {
1151 return node_multiple_delete_confirm();
1154 $filter = node_build_filter_query();
1156 $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']);
1158 $form['options'] = array('#type' => 'fieldset',
1159 '#title' => t('Update options'),
1160 '#prefix' => '<div class="container-inline">',
1161 '#suffix' => '</div>',
1164 foreach (node_operations() as $key => $value) {
1165 $options[$key] = $value[0];
1167 $form['options']['operation'] = array('#type' => 'select', '#options' => $options, '#default_value' => 'approve');
1168 $form['options']['submit'] = array('#type' => 'submit', '#value' => t('Update'));
1170 $destination = drupal_get_destination();
1171 while ($node = db_fetch_object($result)) {
1172 $nodes[$node->nid] = '';
1173 $form['title'][$node->nid] = array('#value' => l($node->title, 'node/'. $node->nid) .' '. theme('mark', node_mark($node->nid, $node->changed)));
1174 $form['name'][$node->nid] = array('#value' => node_get_name($node));
1175 $form['username'][$node->nid] = array('#value' => theme('username', $node));
1176 $form['status'][$node->nid] = array('#value' => ($node->status ? t('published') : t('not published')));
1177 $form['operations'][$node->nid] = array('#value' => l(t('edit'), 'node/'. $node->nid .'/edit', array(), $destination));
1179 $form['nodes'] = array('#type' => 'checkboxes', '#options' => $nodes);
1180 $form['pager'] = array('#value' => theme('pager', NULL, 50, 0));
1182 // Call the form first, to allow for the form_values array to be populated.
1183 $output .= drupal_get_form('node_admin_nodes', $form);
1189 * Theme node administration overview.
1191 function theme_node_admin_nodes($form) {
1193 $header = array(NULL, t('Title'), t('Type'), t('Author'), t('Status'), t('Operations'));
1195 $output .= form_render($form['options']);
1196 if (isset($form['title']) && is_array($form['title'])) {
1197 foreach (element_children($form['title']) as $key) {
1199 $row[] = form_render($form['nodes'][$key]);
1200 $row[] = form_render($form['title'][$key]);
1201 $row[] = form_render($form['name'][$key]);
1202 $row[] = form_render($form['username'][$key]);
1203 $row[] = form_render($form['status'][$key]);
1204 $row[] = form_render($form['operations'][$key]);
1210 $rows[] = array(array('data' => t('No posts available.'), 'colspan' => '6'));
1213 $output .= theme('table', $header, $rows);
1214 if ($form['pager']['#value']) {
1215 $output .= form_render($form['pager']);
1218 $output .= form_render($form);
1223 function node_multiple_delete_confirm() {
1224 $edit = $_POST['edit'];
1226 $form['nodes'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
1227 // array_filter returns only elements with true values
1228 foreach (array_filter($edit['nodes']) as $nid => $value) {
1229 $title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $nid));
1230 $form['nodes'][$nid] = array('#type' => 'hidden', '#value' => $nid, '#prefix' => '<li>', '#suffix' => check_plain($title) ."</li>\n");
1232 $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
1234 return confirm_form('node_multiple_delete_confirm', $form,
1235 t('Are you sure you want to delete these items?'),
1236 'admin/node', t('This action cannot be undone.'),
1237 t('Delete all'), t('Cancel'));
1240 function node_multiple_delete_confirm_submit($form_id, $edit) {
1241 if ($edit['confirm']) {
1242 foreach ($edit['nodes'] as $nid => $value) {
1245 drupal_set_message(t('The items have been deleted.'));
1247 return 'admin/node';
1251 * Menu callback; presents each node type configuration page.
1253 function node_types_configure($type = NULL) {
1255 $node = new stdClass();
1256 $node->type = $type;
1257 $form['submission'] = array('#type' => 'fieldset', '#title' =>t('Submission form') );
1258 $form['submission'][$type . '_help'] = array(
1259 '#type' => 'textarea', '#title' => t('Explanation or submission guidelines'), '#default_value' => variable_get($type .'_help', ''),
1260 '#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)))
1262 $form['submission']['minimum_'. $type .'_size'] = array(
1263 '#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)),
1264 '#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)))
1266 $form['workflow'] = array('#type' => 'fieldset', '#title' =>t('Workflow'));
1267 $form['type'] = array('#type' => 'value', '#value' => $type);
1269 $form['array_filter'] = array('#type' => 'value', '#value' => TRUE);
1270 return system_settings_form($type .'_node_settings', $form);
1273 $header = array(t('Type'), t('Operations'));
1276 foreach (node_get_types() as $type => $name) {
1277 $rows[] = array($name, l(t('configure'), 'admin/settings/content-types/'. $type));
1280 return theme('table', $header, $rows);
1285 * Generate an overview table of older revisions of a node.
1287 function node_revision_overview($node) {
1288 drupal_set_title(t('Revisions for %title', array('%title' => check_plain($node->title))));
1290 $header = array(t('Revision'), array('data' => t('Operations'), 'colspan' => 2));
1292 $revisions = node_revision_list($node);
1295 $revert_permission = FALSE;
1296 if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) {
1297 $revert_permission = TRUE;
1299 $delete_permission = FALSE;
1300 if (user_access('administer nodes')) {
1301 $delete_permission = TRUE;
1303 foreach ($revisions as $revision) {
1305 $operations = array();
1307 if ($revision->current_vid > 0) {
1308 $row[] = array('data' => t('%date by %username', array('%date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid"), '%username' => theme('username', $revision)))
1309 . (($revision->log != '') ? '<p class="revision-log">'. filter_xss($revision->log) .'</p>' : ''),
1310 'class' => 'revision-current');
1311 $operations[] = array('data' => theme('placeholder', t('current revision')), 'class' => 'revision-current', 'colspan' => 2);
1314 $row[] = t('%date by %username', array('%date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid/revisions/$revision->vid/view"), '%username' => theme('username', $revision)))
1315 . (($revision->log != '') ? '<p class="revision-log">'. filter_xss($revision->log) .'</p>' : '');
1316 if ($revert_permission) {
1317 $operations[] = l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert");
1319 if ($delete_permission) {
1320 $operations[] = l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete");
1323 $rows[] = array_merge($row, $operations);
1325 $output .= theme('table', $header, $rows);
1331 * Revert to the revision with the specified revision number. A node and nodeapi "update" event is triggered
1332 * (via the node_save() call) when a revision is reverted.
1334 function node_revision_revert($nid, $revision) {
1337 $node = node_load($nid, $revision);
1338 if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) {
1341 $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
1342 $form['vid'] = array('#type' => 'value', '#value' => $node->vid);
1343 return confirm_form('node_revision_revert_confirm', $form,
1344 t('Are you sure you want to revert %title to the revision from %revision-date?', array('%title' => theme('placeholder', $node->title), '%revision-date' => theme('placeholder', format_date($node->revision_timestamp)))),
1345 "node/$nid/revisions", ' ', t('Revert'), t('Cancel'));
1348 drupal_set_message(t('You tried to revert to an invalid revision.'), 'error');
1350 drupal_goto('node/'. $nid .'/revisions');
1352 drupal_access_denied();
1355 function node_revision_revert_confirm_submit($form_id, $form_values) {
1356 $nid = $form_values['nid'];
1357 $revision = $form_values['vid'];
1358 $node = node_load($nid, $revision);
1359 $node->revision = 1;
1360 $node->log = t('Copy of the revision from %date.', array('%date' => theme('placeholder', format_date($node->revision_timestamp))));
1361 if (module_exist('taxonomy')) {
1362 $node->taxonomy = array_keys($node->taxonomy);
1366 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)))));
1367 watchdog('content', t('%type: reverted %title revision %revision.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision))));
1368 return 'node/'. $nid .'/revisions';
1372 * Delete the revision with specified revision number. A "delete revision" nodeapi event is invoked when a
1373 * revision is deleted.
1375 function node_revision_delete($nid, $revision) {
1376 if (user_access('administer nodes')) {
1377 $node = node_load($nid);
1378 if (node_access('delete', $node)) {
1379 // Don't delete the current revision
1380 if ($revision != $node->vid) {
1381 $node = node_load($nid, $revision);
1383 $form['nid'] = array('#type' => 'value', '#value' => $nid);
1384 $form['vid'] = array('#type' => 'value', '#value' => $revision);
1385 return confirm_form('node_revision_delete_confirm', $form,
1386 t('Are you sure you want to delete %title revision %revision?', array('%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision))),
1387 "node/$nid/revisions", '', t('Delete'), t('Cancel'));
1390 drupal_set_message(t('Deletion failed. You tried to delete the current revision.'));
1392 if (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $nid)) > 1) {
1393 drupal_goto("node/$nid/revisions");
1396 drupal_goto("node/$nid");
1400 drupal_access_denied();
1403 function node_revision_delete_confirm_submit($form_id, $form_values) {
1404 $node = node_load($form_values['nid'], $form_values['vid']);
1405 db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $node->nid, $node->vid);
1406 node_invoke_nodeapi($node, 'delete revision');
1407 drupal_set_message(t('Deleted %title revision %revision.', array('%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $node->vid))));
1408 watchdog('content', t('%type: deleted %title revision %revision.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $node->revision))));
1410 if (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $node->nid)) > 1) {
1411 return "node/$node->nid/revisions";
1413 return "node/$node->nid";
1417 * Return a list of all the existing revision numbers.
1419 function node_revision_list($node) {
1420 $revisions = array();
1421 $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);
1422 while ($revision = db_fetch_object($result)) {
1423 $revisions[] = $revision;
1429 function node_admin_search() {
1430 $output = search_form(url('admin/node/search'), $_POST['edit']['keys'], 'node') . search_data($_POST['edit']['keys'], 'node');
1435 * Implementation of hook_block().
1437 function node_block($op = 'list', $delta = 0) {
1438 if ($op == 'list') {
1439 $blocks[0]['info'] = t('Syndicate');
1442 else if ($op == 'view') {
1443 $block['subject'] = t('Syndicate');
1444 $block['content'] = theme('feed_icon', url('rss.xml'));
1451 * A generic function for generating RSS feeds from a set of nodes.
1454 * An object as returned by db_query() which contains the nid field.
1456 * An associative array containing title, link, description and other keys.
1457 * The link should be an absolute URL.
1459 function node_feed($nodes = 0, $channel = array()) {
1460 global $base_url, $locale;
1463 $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));
1466 $item_length = variable_get('feed_item_length', 'teaser');
1467 $namespaces = array('xmlns:dc="http://purl.org/dc/elements/1.1/"');
1469 while ($node = db_fetch_object($nodes)) {
1470 // Load the specified node:
1471 $item = node_load($node->nid);
1472 $link = url("node/$node->nid", NULL, NULL, 1);
1474 if ($item_length != 'title') {
1475 $teaser = ($item_length == 'teaser') ? TRUE : FALSE;
1477 // Filter and prepare node teaser
1478 if (node_hook($item, 'view')) {
1479 node_invoke($item, 'view', $teaser, FALSE);
1482 $item = node_prepare($item, $teaser);
1485 // Allow modules to change $node->teaser before viewing.
1486 node_invoke_nodeapi($item, 'view', $teaser, FALSE);
1489 // Allow modules to add additional item fields
1490 $extra = node_invoke_nodeapi($item, 'rss item');
1491 $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'))));
1492 foreach ($extra as $element) {
1493 if ($element['namespace']) {
1494 $namespaces = array_merge($namespaces, $element['namespace']);
1498 // Prepare the item description
1499 switch ($item_length) {
1501 $item_text = $item->body;
1504 $item_text = $item->teaser;
1505 if ($item->readmore) {
1506 $item_text .= '<p>'. l(t('read more'), 'node/'. $item->nid, NULL, NULL, NULL, TRUE) .'</p>';
1514 $items .= format_rss_item($item->title, $link, $item_text, $extra);
1517 $channel_defaults = array(
1519 'title' => variable_get('site_name', 'drupal') .' - '. variable_get('site_slogan', ''),
1520 'link' => $base_url,
1521 'description' => variable_get('site_mission', ''),
1522 'language' => $locale
1524 $channel = array_merge($channel_defaults, $channel);
1526 $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1527 $output .= "<rss version=\"". $channel["version"] . "\" xml:base=\"". $base_url ."\" ". implode(' ', $namespaces) .">\n";
1528 $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language']);
1529 $output .= "</rss>\n";
1531 drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
1536 * Prepare node for save and allow modules to make changes.
1538 function node_submit($node) {
1541 // Convert the node to an object, if necessary.
1542 $node = (object)$node;
1544 // Auto-generate the teaser, but only if it hasn't been set (e.g. by a
1545 // module-provided 'teaser' form item).
1546 if (!isset($node->teaser)) {
1547 $node->teaser = isset($node->body) ? node_teaser($node->body, isset($node->format) ? $node->format : NULL) : '';
1550 $access = user_access('administer nodes');
1552 // Populate the "authored by" field.
1553 if ($account = user_load(array('name' => $node->name))) {
1554 $node->uid = $account->uid;
1560 $node->created = $node->date ? strtotime($node->date) : NULL;
1562 // Force defaults in case people modify the form:
1563 $node_options = variable_get('node_options_'. $node->type, array('status', 'promote'));
1564 foreach (array('status', 'moderate', 'promote', 'sticky', 'revision') as $key) {
1565 if (!$access || !isset($node->$key)) {
1566 $node->$key = in_array($key, $node_options);
1570 // Do node-type-specific validation checks.
1571 node_invoke($node, 'submit');
1572 node_invoke_nodeapi($node, 'submit');
1574 $node->validated = TRUE;
1580 * Perform validation checks on the given node.
1582 function node_validate($node, $form = array()) {
1583 // Convert the node to an object, if necessary.
1584 $node = (object)$node;
1586 // Make sure the body has the minimum number of words.
1587 // todo use a better word counting algorithm that will work in other languages
1588 if (isset($node->body) && count(explode(' ', $node->body)) < variable_get('minimum_'. $node->type .'_size', 0)) {
1589 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))));
1592 if (isset($node->nid) && (node_last_changed($node->nid) > $node->changed)) {
1593 form_set_error('changed', t('This content has been modified by another user, changes cannot be saved.'));
1596 if (user_access('administer nodes')) {
1597 // Validate the "authored by" field.
1598 if (!empty($node->name) && !($account = user_load(array('name' => $node->name)))) {
1599 // The use of empty() is mandatory in the context of usernames
1600 // as the empty string denotes the anonymous user. In case we
1601 // are dealing with an anonymous user we set the user ID to 0.
1602 form_set_error('name', t('The username %name does not exist.', array ('%name' => theme('placeholder', $node->name))));
1605 // Validate the "authored on" field. As of PHP 5.1.0, strtotime returns FALSE instead of -1 upon failure.
1606 if (!empty($node->date) && strtotime($node->date) <= 0) {
1607 form_set_error('date', t('You have to specify a valid date.'));
1611 // Do node-type-specific validation checks.
1612 node_invoke($node, 'validate', $form);
1613 node_invoke_nodeapi($node, 'validate', $form);
1616 function node_form_validate($form_id, $form_values, $form) {
1617 node_validate($form_values, $form);
1620 function node_object_prepare(&$node) {
1621 if (user_access('administer nodes')) {
1622 // Set up default values, if required.
1623 if (!isset($node->created)) {
1624 $node->created = time();
1627 if (!isset($node->date)) {
1628 $node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O');
1631 node_invoke($node, 'prepare');
1632 node_invoke_nodeapi($node, 'prepare');
1636 * Generate the node editing form.
1638 function node_form($node) {
1639 $node = (object)$node;
1640 $form = node_form_array($node);
1641 return drupal_get_form($node->type .'_node_form', $form, 'node_form');
1645 * Generate the node editing form array.
1647 function node_form_array($node) {
1648 node_object_prepare($node);
1650 // Set the id of the top-level form tag
1651 $form['#id'] = 'node-form';
1654 * Basic node information.
1655 * These elements are just values so they are not even sent to the client.
1657 foreach (array('nid', 'vid', 'uid', 'created', 'type') as $key) {
1658 $form[$key] = array('#type' => 'value', '#value' => $node->$key);
1661 // Changed must be sent to the client, for later overwrite error checking.
1662 $form['changed'] = array('#type' => 'hidden', '#default_value' => $node->changed);
1664 // Get the node-specific bits.
1665 $form = array_merge_recursive($form, node_invoke($node, 'form'));
1666 if (!isset($form['title']['#weight'])) {
1667 $form['title']['#weight'] = -5;
1670 $node_options = variable_get('node_options_'. $node->type, array('status', 'promote'));
1671 // If this is a new node, fill in the default values.
1672 if (!isset($node->nid)) {
1673 foreach (array('status', 'moderate', 'promote', 'sticky', 'revision') as $key) {
1674 $node->$key = in_array($key, $node_options);
1677 $node->uid = $user->uid;
1680 // Nodes being edited should always be preset with the default revision setting.
1681 $node->revision = in_array('revision', $node_options);
1683 $form['#node'] = $node;
1685 if (user_access('administer nodes')) {
1686 // Node author information
1687 $form['author'] = array('#type' => 'fieldset', '#title' => t('Authoring information'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => 20);
1688 $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')))));
1689 $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)));
1691 if (isset($node->nid)) {
1692 $form['author']['date']['#default_value'] = $node->date;
1695 // Node options for administrators
1696 $form['options'] = array('#type' => 'fieldset', '#title' => t('Publishing options'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => 25);
1697 $form['options']['status'] = array('#type' => 'checkbox', '#title' => t('Published'), '#default_value' => $node->status);
1698 $form['options']['moderate'] = array('#type' => 'checkbox', '#title' => t('In moderation queue'), '#default_value' => $node->moderate);
1699 $form['options']['promote'] = array('#type' => 'checkbox', '#title' => t('Promoted to front page'), '#default_value' => $node->promote);
1700 $form['options']['sticky'] = array('#type' => 'checkbox', '#title' => t('Sticky at top of lists'), '#default_value' => $node->sticky);
1701 $form['options']['revision'] = array('#type' => 'checkbox', '#title' => t('Create new revision'), '#default_value' => $node->revision);
1704 // Put all of these through as values if the user doesn't have access to them.
1705 foreach (array('uid', 'created') as $key) {
1706 $form[$key] = array('#type' => 'value', '#value' => $node->$key);
1711 $form['preview'] = array('#type' => 'button', '#value' => t('Preview'), '#weight' => 40);
1712 $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'), '#weight' => 45);
1713 if ($node->nid && node_access('delete', $node)) {
1714 $form['delete'] = array('#type' => 'button', '#value' => t('Delete'), '#weight' => 50);
1717 $form['#after_build'] = array('node_form_add_preview');
1722 function node_form_add_preview($form) {
1723 global $form_values;
1725 $op = isset($_POST['op']) ? $_POST['op'] : '';
1726 if ($op == t('Preview')) {
1727 // Invoke full validation for the form, to protect against cross site
1728 // request forgeries (CSRF) and setting arbitrary values for fields such as
1729 // the input format. Preview the node only when form validation does not
1731 drupal_validate_form($form['form_id']['#value'], $form);
1732 if (!form_get_errors()) {
1733 // Because the node preview may display a form, we must render it
1734 // outside the node submission form tags using the #prefix property
1735 // (i.e. to prevent illegally nested forms).
1736 // If the node form already has a #prefix, we must preserve it.
1737 // In this case, we put the preview before the #prefix so we keep
1738 // the #prefix as "close" to the rest of the form as possible,
1739 // for example, to keep a <div> only around the form, not the
1740 // preview. We pass the global $form_values here to preserve
1741 // changes made during form validation.
1742 $preview = node_preview((object)$form_values);
1743 $form['#prefix'] = isset($form['#prefix']) ? $preview . $form['#prefix'] : $preview;
1746 if (variable_get('node_preview', 0) && (form_get_errors() || $op != t('Preview'))) {
1747 unset($form['submit']);
1752 function theme_node_form($form) {
1753 $output = "\n<div class=\"node-form\">\n";
1755 // Admin form fields and submit buttons must be rendered first, because
1756 // they need to go to the bottom of the form, and so should not be part of
1757 // the catch-all call to form_render().
1759 if (isset($form['author'])) {
1760 $admin .= " <div class=\"authored\">\n";
1761 $admin .= form_render($form['author']);
1762 $admin .= " </div>\n";
1764 if (isset($form['options'])) {
1765 $admin .= " <div class=\"options\">\n";
1766 $admin .= form_render($form['options']);
1767 $admin .= " </div>\n";
1769 $buttons = form_render($form['preview']);
1770 $buttons .= form_render($form['submit']);
1771 $buttons .= isset($form['delete']) ? form_render($form['delete']) : '';
1773 // Everything else gets rendered here, and is displayed before the admin form
1774 // field and the submit buttons.
1775 $output .= " <div class=\"standard\">\n";
1776 $output .= form_render($form);
1777 $output .= " </div>\n";
1779 if (!empty($admin)) {
1780 $output .= " <div class=\"admin\">\n";
1782 $output .= " </div>\n";
1784 $output .= $buttons;
1785 $output .= "</div>\n";
1791 * Present a node submission form or a set of links to such forms.
1793 function node_add($type) {
1796 // If a node type has been specified, validate its existence.
1797 if (array_key_exists($type, node_get_types()) && node_access('create', $type)) {
1798 // Initialize settings:
1799 $node = array('uid' => $user->uid, 'name' => $user->name, 'type' => $type);
1801 $output = node_form($node);
1802 drupal_set_title(t('Submit %name', array('%name' => node_get_name($node))));
1805 // If no (valid) node type has been provided, display a node type overview.
1806 foreach (node_get_types() as $type => $name) {
1807 if (node_access('create', $type)) {
1808 $out = '<dt>'. l($name, "node/add/$type", array('title' => t('Add a new %s.', array('%s' => $name)))) .'</dt>';
1809 $out .= '<dd>'. implode("\n", module_invoke_all('help', 'node/add#'. $type)) .'</dd>';
1810 $item[$name] = $out;
1815 uksort($item, 'strnatcasecmp');
1816 $output = t('Choose the appropriate item from the list:') .'<dl>'. implode('', $item) .'</dl>';
1819 $output = t('You are not allowed to create content.');
1827 * Generate a node preview.
1829 function node_preview($node) {
1830 if (node_access('create', $node) || node_access('update', $node)) {
1831 // Load the user's name when needed:
1832 if (isset($node->name)) {
1833 // The use of isset() is mandatory in the context of user IDs, because
1834 // user ID 0 denotes the anonymous user.
1835 if ($user = user_load(array('name' => $node->name))) {
1836 $node->uid = $user->uid;
1837 $node->picture = $user->picture;
1840 $node->uid = 0; // anonymous user
1843 else if ($node->uid) {
1844 $user = user_load(array('uid' => $node->uid));
1845 $node->name = $user->name;
1846 $node->picture = $user->picture;
1849 // Set the timestamps when needed:
1851 $node->created = strtotime($node->date);
1853 $node->changed = time();
1855 // Extract a teaser, if it hasn't been set (e.g. by a module-provided
1856 // 'teaser' form item).
1857 if (!isset($node->teaser)) {
1858 $node->teaser = node_teaser($node->body, $node->format);
1861 // Display a preview of the node:
1862 // Previewing alters $node so it needs to be cloned.
1863 if (!form_get_errors()) {
1864 $cloned_node = drupal_clone($node);
1865 $cloned_node->in_preview = TRUE;
1866 $output = theme('node_preview', $cloned_node);
1868 drupal_set_title(t('Preview'));
1869 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)));
1876 * Display a node preview for display during node creation and editing.
1879 * The node object which is being previewed.
1881 function theme_node_preview($node) {
1882 $output = '<div class="preview">';
1883 if ($node->teaser && $node->teaser != $node->body) {
1884 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.'));
1885 $output .= '<h3>'. t('Preview trimmed version') .'</h3>';
1886 $output .= node_view(drupal_clone($node), 1, FALSE, 0);
1887 $output .= '<h3>'. t('Preview full version') .'</h3>';
1888 $output .= node_view($node, 0, FALSE, 0);
1891 $output .= node_view($node, 0, FALSE, 0);
1893 $output .= "</div>\n";
1898 function node_form_submit($form_id, $edit) {
1901 // Fix up the node when required:
1902 $node = node_submit($edit);
1904 // Prepare the node's body:
1906 // Check whether the current user has the proper access rights to
1907 // perform this operation:
1908 $original_node = node_load($node->nid); //check access rights using the unmodified node
1909 if (node_access('update', $original_node)) {
1911 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));
1912 drupal_set_message(t('The %post was updated.', array ('%post' => node_get_name($node))));
1916 // Check whether the current user has the proper access rights to
1917 // perform this operation:
1918 if (node_access('create', $node)) {
1920 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"));
1921 drupal_set_message(t('Your %post was created.', array ('%post' => node_get_name($node))));
1925 if (node_access('view', $node)) {
1926 return 'node/'. $node->nid;
1932 // it is very unlikely we get here
1937 * Menu callback -- ask for confirmation of node deletion
1939 function node_delete_confirm() {
1940 $edit = $_POST['edit'];
1941 $edit['nid'] = $edit['nid'] ? $edit['nid'] : arg(1);
1942 $node = node_load($edit['nid']);
1944 if (node_access('delete', $node)) {
1945 $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
1946 $output = confirm_form('node_delete_confirm', $form,
1947 t('Are you sure you want to delete %title?', array('%title' => theme('placeholder', $node->title))),
1948 $_GET['destination'] ? $_GET['destination'] : 'node/'. $node->nid, t('This action cannot be undone.'),
1949 t('Delete'), t('Cancel') );
1956 * Execute node deletion
1958 function node_delete_confirm_submit($form_id, $form_values) {
1959 if ($form_values['confirm']) {
1960 node_delete($form_values['nid']);
1969 function node_delete($nid) {
1971 $node = node_load($nid);
1973 if (node_access('delete', $node)) {
1974 db_query('DELETE FROM {node} WHERE nid = %d', $node->nid);
1975 db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);
1977 // Call the node-specific callback (if any):
1978 node_invoke($node, 'delete');
1979 node_invoke_nodeapi($node, 'delete');
1981 // Clear the cache so an anonymous poster can see the node being deleted.
1984 // Remove this node from the search index if needed.
1985 if (function_exists('search_wipe')) {
1986 search_wipe($node->nid, 'node');
1988 drupal_set_message(t('%title has been deleted.', array('%title' => theme('placeholder', $node->title))));
1989 watchdog('content', t('%type: deleted %title.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title))));
1994 * Menu callback for revisions related activities.
1996 function node_revisions() {
1997 if (is_numeric(arg(1)) && arg(2) == 'revisions') {
1998 $op = arg(4) ? arg(4) : 'overview';
2001 $node = node_load(arg(1));
2002 if ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node)) {
2003 return node_revision_overview($node);
2005 drupal_access_denied();
2008 if (is_numeric(arg(3))) {
2009 $node = node_load(arg(1), arg(3));
2011 if ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node)) {
2012 drupal_set_title(t('Revision of %title from %date', array('%title' => theme('placeholder', $node->title), '%date' => format_date($node->revision_timestamp))));
2013 return node_show($node, arg(2));
2015 drupal_access_denied();
2021 return node_revision_revert(arg(1), arg(3));
2024 return node_revision_delete(arg(1), arg(3));
2032 * Generate a listing of promoted nodes.
2034 function node_page_default() {
2035 $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));
2037 if (db_num_rows($result)) {
2038 drupal_add_link(array('rel' => 'alternate',
2039 'type' => 'application/rss+xml',
2040 'title' => t('RSS'),
2041 'href' => url('rss.xml', NULL, NULL, TRUE)));
2044 while ($node = db_fetch_object($result)) {
2045 $output .= node_view(node_load($node->nid), 1);
2047 $output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
2051 <h1 class="title">Welcome to your new Drupal-based MyPLC website!</h1>
2052 <p>Please follow these steps to set up and start using your website:</p>
2055 <strong>Use your administrator account</strong>
2056 Enter the site using the credentials that you have provided when configuring MyPLC (see PLC_ROOT_USER and PLC_ROOT_PASSWORD). This account has full administration rights and allows you to configure your website using Drupal tools.
2059 <strong>Configure your website</strong>
2060 Once logged in, visit the <a href="%admin">administration section</a>, where you can <a href="%config">customize and configure</a> all aspects of your website.
2063 <strong>Enable additional functionality</strong>
2064 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>.
2067 <strong>Customize your website design</strong>
2068 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>.
2071 <strong>Start posting content</strong>
2072 Finally, you can <a href="%content">create content</a> for your website. This message will disappear once you have published your first post.
2075 <p>For more information, 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>',
2076 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')
2078 $output = '<div id="first-time">'. $output .'</div>';
2085 * Menu callback; dispatches control to the appropriate operation handler.
2087 function node_page() {
2090 if (is_numeric($op)) {
2091 $op = (arg(2) && !is_numeric(arg(2))) ? arg(2) : 'view';
2096 if (is_numeric(arg(1))) {
2097 $node = node_load(arg(1));
2099 drupal_set_title(check_plain($node->title));
2100 return node_show($node, arg(2));
2102 else if (db_result(db_query('SELECT nid FROM {node} WHERE nid = %d', arg(1)))) {
2103 drupal_access_denied();
2111 return node_add(arg(2));
2114 if ($_POST['op'] == t('Delete')) {
2115 // Note: we redirect from node/uid/edit to node/uid/delete to make the tabs disappear.
2116 if ($_REQUEST['destination']) {
2117 $destination = drupal_get_destination();
2118 unset($_REQUEST['destination']);
2120 drupal_goto('node/'. arg(1) .'/delete', $destination);
2123 if (is_numeric(arg(1))) {
2124 $node = node_load(arg(1));
2126 drupal_set_title(check_plain($node->title));
2127 return node_form($node);
2129 else if (db_result(db_query('SELECT nid FROM {node} WHERE nid = %d', arg(1)))) {
2130 drupal_access_denied();
2138 drupal_set_title('');
2139 return node_page_default();
2144 * shutdown function to make sure we always mark the last node processed.
2146 function node_update_shutdown() {
2147 global $last_change, $last_nid;
2149 if ($last_change && $last_nid) {
2150 variable_set('node_cron_last', $last_change);
2151 variable_set('node_cron_last_nid', $last_nid);
2156 * Implementation of hook_update_index().
2158 function node_update_index() {
2159 global $last_change, $last_nid;
2161 register_shutdown_function('node_update_shutdown');
2163 $last = variable_get('node_cron_last', 0);
2164 $last_nid = variable_get('node_cron_last_nid', 0);
2165 $limit = (int)variable_get('search_cron_limit', 100);
2167 // Store the maximum possible comments per thread (used for ranking by reply count)
2168 variable_set('node_cron_comments_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(comment_count) FROM {node_comment_statistics}'))));
2169 variable_set('node_cron_views_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(totalcount) FROM {node_counter}'))));
2171 $result = db_query_range('SELECT GREATEST(IF(c.last_comment_timestamp IS NULL, 0, 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);
2173 while ($node = db_fetch_object($result)) {
2174 $last_change = $node->last_change;
2175 $last_nid = $node->nid;
2176 $node = node_load($node->nid);
2178 // Get node output (filtered and with module-specific fields).
2179 if (node_hook($node, 'view')) {
2180 node_invoke($node, 'view', false, false);
2183 $node = node_prepare($node, false);
2185 // Allow modules to change $node->body before viewing.
2186 node_invoke_nodeapi($node, 'view', false, false);
2188 $text = '<h1>'. check_plain($node->title) .'</h1>'. $node->body;
2190 // Fetch extra data normally not visible
2191 $extra = node_invoke_nodeapi($node, 'update index');
2192 foreach ($extra as $t) {
2197 search_index($node->nid, 'node', $text);
2202 * Implementation of hook_form_alter().
2204 function node_form_alter($form_id, &$form) {
2205 // Node publishing options
2206 if (isset($form['type']) && $form['type']['#value'] .'_node_settings' == $form_id) {
2207 $form['workflow']['node_options_'. $form['type']['#value']] = array('#type' => 'checkboxes',
2208 '#title' => t('Default options'),
2209 '#default_value' => variable_get('node_options_'. $form['type']['#value'], array('status', 'promote')),
2210 '#options' => array(
2211 'status' => t('Published'),
2212 'moderate' => t('In moderation queue'),
2213 'promote' => t('Promoted to front page'),
2214 'sticky' => t('Sticky at top of lists'),
2215 'revision' => t('Create new revision'),
2217 '#description' => t('Users with the <em>administer nodes</em> permission will be able to override these options.'),
2221 // Advanced node search form
2222 elseif ($form_id == 'search_form' && arg(1) == 'node') {
2224 $form['advanced'] = array(
2225 '#type' => 'fieldset',
2226 '#title' => t('Advanced search'),
2227 '#collapsible' => TRUE,
2228 '#collapsed' => TRUE,
2229 '#attributes' => array('class' => 'search-advanced'),
2231 $form['advanced']['keywords'] = array(
2232 '#prefix' => '<div class="criterion">',
2233 '#suffix' => '</div>',
2235 $form['advanced']['keywords']['or'] = array(
2236 '#type' => 'textfield',
2237 '#title' => t('Containing any of the words'),
2239 '#maxlength' => 255,
2241 $form['advanced']['keywords']['phrase'] = array(
2242 '#type' => 'textfield',
2243 '#title' => t('Containing the phrase'),
2245 '#maxlength' => 255,
2247 $form['advanced']['keywords']['negative'] = array(
2248 '#type' => 'textfield',
2249 '#title' => t('Containing none of the words'),
2251 '#maxlength' => 255,
2255 if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
2256 $form['advanced']['category'] = array(
2257 '#type' => 'select',
2258 '#title' => t('Only in the category(s)'),
2259 '#prefix' => '<div class="criterion">',
2261 '#suffix' => '</div>',
2262 '#options' => $taxonomy,
2263 '#multiple' => TRUE,
2268 $types = node_get_types();
2269 $form['advanced']['type'] = array(
2270 '#type' => 'checkboxes',
2271 '#title' => t('Only of the type(s)'),
2272 '#prefix' => '<div class="criterion">',
2273 '#suffix' => '</div>',
2274 '#options' => $types,
2276 $form['advanced']['submit'] = array(
2277 '#type' => 'submit',
2278 '#value' => t('Advanced search'),
2279 '#prefix' => '<div class="action">',
2280 '#suffix' => '</div><br class="clear" />',
2283 $form['#validate']['node_search_validate'] = array();
2288 * Form API callback for the search form. Registered in node_form_alter().
2290 function node_search_validate($form_id, $form_values, $form) {
2291 // Initialise using any existing basic search keywords.
2292 $keys = $form_values['processed_keys'];
2294 // Insert extra restrictions into the search keywords string.
2295 if (isset($form_values['type']) && is_array($form_values['type'])) {
2296 // Retrieve selected types - Forms API sets the value of unselected checkboxes to 0.
2297 $form_values['type'] = array_filter($form_values['type']);
2298 if (count($form_values['type'])) {
2299 $keys = search_query_insert($keys, 'type', implode(',', array_keys($form_values['type'])));
2303 if (isset($form_values['category']) && is_array($form_values['category'])) {
2304 $keys = search_query_insert($keys, 'category', implode(',', $form_values['category']));
2306 if ($form_values['or'] != '') {
2307 if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $form_values['or'], $matches)) {
2308 $keys .= ' '. implode(' OR ', $matches[1]);
2311 if ($form_values['negative'] != '') {
2312 if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $form_values['negative'], $matches)) {
2313 $keys .= ' -'. implode(' -', $matches[1]);
2316 if ($form_values['phrase'] != '') {
2317 $keys .= ' "'. str_replace('"', ' ', $form_values['phrase']) .'"';
2319 if (!empty($keys)) {
2320 form_set_value($form['basic']['inline']['processed_keys'], trim($keys));
2325 * @defgroup node_access Node access rights
2327 * The node access system determines who can do what to which nodes.
2329 * In determining access rights for a node, node_access() first checks
2330 * whether the user has the "administer nodes" permission. Such users have
2331 * unrestricted access to all nodes. Then the node module's hook_access()
2332 * is called, and a TRUE or FALSE return value will grant or deny access.
2333 * This allows, for example, the blog module to always grant access to the
2334 * blog author, and for the book module to always deny editing access to
2337 * If node module does not intervene (returns NULL), then the
2338 * node_access table is used to determine access. All node access
2339 * modules are queried using hook_node_grants() to assemble a list of
2340 * "grant IDs" for the user. This list is compared against the table.
2341 * If any row contains the node ID in question (or 0, which stands for "all
2342 * nodes"), one of the grant IDs returned, and a value of TRUE for the
2343 * operation in question, then access is granted. Note that this table is a
2344 * list of grants; any matching row is sufficient to grant access to the
2347 * In node listings, the process above is followed except that
2348 * hook_access() is not called on each node for performance reasons and for
2349 * proper functioning of the pager system. When adding a node listing to your
2350 * module, be sure to use db_rewrite_sql() to add
2351 * the appropriate clauses to your query for access checks.
2353 * To see how to write a node access module of your own, see
2354 * node_access_example.module.
2358 * Determine whether the current user may perform the given operation on the
2362 * The operation to be performed on the node. Possible values are:
2368 * The node object (or node array) on which the operation is to be performed,
2369 * or node type (e.g. 'forum') for "create" operation.
2371 * The user ID on which the operation is to be performed.
2373 * TRUE if the operation may be performed.
2375 function node_access($op, $node = NULL, $uid = NULL) {
2376 // Convert the node to an object if necessary:
2377 if ($op != 'create') {
2378 $node = (object)$node;
2380 // If the node is in a restricted format, disallow editing.
2381 if ($op == 'update' && !filter_access($node->format)) {
2385 if (user_access('administer nodes')) {
2389 if (!user_access('access content')) {
2393 // Can't use node_invoke(), because the access hook takes the $op parameter
2394 // before the $node parameter.
2395 $access = module_invoke(node_get_base($node), 'access', $op, $node);
2396 if (!is_null($access)) {
2400 // If the module did not override the access rights, use those set in the
2401 // node_access table.
2402 if ($op != 'create' && $node->nid && $node->status) {
2404 foreach (node_access_grants($op, $uid) as $realm => $gids) {
2405 foreach ($gids as $gid) {
2406 $grants[] = "(gid = $gid AND realm = '$realm')";
2411 if (count($grants)) {
2412 $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
2415 $sql = "SELECT COUNT(*) FROM {node_access} WHERE (nid = 0 OR nid = %d) $grants_sql AND grant_$op >= 1";
2416 $result = db_query($sql, $node->nid);
2417 return (db_result($result));
2423 * Generate an SQL join clause for use in fetching a node listing.
2425 * @param $node_alias
2426 * If the node table has been given an SQL alias other than the default
2427 * "n", that must be passed here.
2428 * @param $node_access_alias
2429 * If the node_access table has been given an SQL alias other than the default
2430 * "na", that must be passed here.
2432 * An SQL join clause.
2434 function _node_access_join_sql($node_alias = 'n', $node_access_alias = 'na') {
2435 if (user_access('administer nodes')) {
2439 return 'INNER JOIN {node_access} '. $node_access_alias .' ON '. $node_access_alias .'.nid = '. $node_alias .'.nid';
2443 * Generate an SQL where clause for use in fetching a node listing.
2446 * The operation that must be allowed to return a node.
2447 * @param $node_access_alias
2448 * If the node_access table has been given an SQL alias other than the default
2449 * "na", that must be passed here.
2451 * An SQL where clause.
2453 function _node_access_where_sql($op = 'view', $node_access_alias = 'na', $uid = NULL) {
2454 if (user_access('administer nodes')) {
2459 foreach (node_access_grants($op, $uid) as $realm => $gids) {
2460 foreach ($gids as $gid) {
2461 $grants[] = "($node_access_alias.gid = $gid AND $node_access_alias.realm = '$realm')";
2466 if (count($grants)) {
2467 $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
2470 $sql = "$node_access_alias.grant_$op >= 1 $grants_sql";
2475 * Fetch an array of permission IDs granted to the given user ID.
2477 * The implementation here provides only the universal "all" grant. A node
2478 * access module should implement hook_node_grants() to provide a grant
2479 * list for the user.
2482 * The operation that the user is trying to perform.
2484 * The user ID performing the operation. If omitted, the current user is used.
2486 * An associative array in which the keys are realms, and the values are
2487 * arrays of grants for those realms.
2489 function node_access_grants($op, $uid = NULL) {
2493 $user_object = user_load(array('uid' => $uid));
2496 $user_object = $user;
2499 return array_merge(array('all' => array(0)), module_invoke_all('node_grants', $user_object, $op));
2503 * Determine whether the user has a global viewing grant for all nodes.
2505 function node_access_view_all_nodes() {
2508 if (!isset($access)) {
2510 foreach (node_access_grants('view') as $realm => $gids) {
2511 foreach ($gids as $gid) {
2512 $grants[] = "(gid = $gid AND realm = '$realm')";
2517 if (count($grants)) {
2518 $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
2521 $sql = "SELECT COUNT(*) FROM {node_access} WHERE nid = 0 $grants_sql AND grant_view >= 1";
2522 $result = db_query($sql);
2523 $access = db_result($result);
2530 * Implementation of hook_db_rewrite_sql
2532 function node_db_rewrite_sql($query, $primary_table, $primary_field) {
2533 if ($primary_field == 'nid' && !node_access_view_all_nodes()) {
2534 $return['join'] = _node_access_join_sql($primary_table);
2535 $return['where'] = _node_access_where_sql();
2536 $return['distinct'] = 1;
2542 * @} End of "defgroup node_access".