specfile
[plewww.git] / modules / node.module
1 <?php
2 // $Id: node.module 144 2007-03-28 07:52:20Z thierry $
3
4 /**
5  * @file
6  * The core that allows content to be submitted to the site.
7  */
8
9 // PlanetLab variable definitions
10 include_once 'plc_config.php';
11
12 define('NODE_NEW_LIMIT', time() - 30 * 24 * 60 * 60);
13
14 /**
15  * Implementation of hook_help().
16  */
17 function node_help($section) {
18   switch ($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>
23 <ul>
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>
28 </ul>
29 ');
30       $output .= t('<p>You can</p>
31 <ul>
32 <li>search for content at <a href="%search">search</a>.</li>
33 <li>administer nodes at <a href="%admin-settings-content-types">administer &gt;&gt; settings &gt;&gt; content types</a>.</li>
34 </ul>
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>';
37       return $output;
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>');
43     case 'admin/node':
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>');
47   }
48
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.');
51   }
52
53   if (arg(0) == 'node' && arg(1) == 'add' && $type = arg(2)) {
54     return filter_xss_admin(variable_get($type .'_help', ''));
55   }
56 }
57
58 /**
59  * Implementation of hook_cron().
60  */
61 function node_cron() {
62   db_query('DELETE FROM {history} WHERE timestamp < %d', NODE_NEW_LIMIT);
63 }
64
65 /**
66  * Gather a listing of links to nodes.
67  *
68  * @param $result
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.
70  * @param $title
71  *   A heading for the resulting list.
72  *
73  * @return
74  *   An HTML list suitable as content for a block.
75  */
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')) : '');
79   }
80
81   return theme('node_list', $items, $title);
82 }
83
84 /**
85  * Format a listing of links to nodes.
86  */
87 function theme_node_list($items, $title = NULL) {
88   return theme('item_list', $items, $title);
89 }
90
91 /**
92  * Update the 'last viewed' timestamp of the specified node for current user.
93  */
94 function node_tag_new($nid) {
95   global $user;
96
97   if ($user->uid) {
98     if (node_last_viewed($nid)) {
99       db_query('UPDATE {history} SET timestamp = %d WHERE uid = %d AND nid = %d', time(), $user->uid, $nid);
100     }
101     else {
102       @db_query('INSERT INTO {history} (uid, nid, timestamp) VALUES (%d, %d, %d)', $user->uid, $nid, time());
103     }
104   }
105 }
106
107 /**
108  * Retrieves the timestamp at which the current user last viewed the
109  * specified node.
110  */
111 function node_last_viewed($nid) {
112   global $user;
113   static $history;
114
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));
117   }
118
119   return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp : 0);
120 }
121
122 /**
123  * Decide on the type of marker to be displayed for a given node.
124  *
125  * @param $nid
126  *   Node ID whose history supplies the "last viewed" timestamp.
127  * @param $timestamp
128  *   Time which is compared against node's "last viewed" timestamp.
129  * @return
130  *   One of the MARK constants.
131  */
132 function node_mark($nid, $timestamp) {
133   global $user;
134   static $cache;
135
136   if (!$user->uid) {
137     return MARK_READ;
138   }
139   if (!isset($cache[$nid])) {
140     $cache[$nid] = node_last_viewed($nid);
141   }
142   if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) {
143     return MARK_NEW;
144   }
145   elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) {
146     return MARK_UPDATED;
147   }
148   return MARK_READ;
149 }
150
151 /**
152  * Automatically generate a teaser for a node body in a given format.
153  */
154 function node_teaser($body, $format = NULL) {
155
156   $size = variable_get('teaser_length', 600);
157
158   // find where the delimiter is in the body
159   $delimiter = strpos($body, '<!--break-->');
160
161   // If the size is zero, and there is no delimiter, the entire body is the teaser.
162   if ($size == 0 && $delimiter === FALSE) {
163     return $body;
164   }
165
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
168   // parse errors.
169   if (isset($format)) {
170     $filters = filter_list_format($format);
171     if (isset($filters['filter/1']) && strpos($body, '<?') !== FALSE) {
172       return $body;
173     }
174   }
175
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);
179   }
180
181   // If we have a short body, the entire body is the teaser.
182   if (strlen($body) < $size) {
183     return $body;
184   }
185
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);
194     }
195   }
196
197   // If all else fails, we simply truncate the string.
198   return truncate_utf8($body, $size);
199 }
200
201 function _node_names($op = '', $node = NULL) {
202   static $node_names = array();
203   static $node_list = array();
204
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'];
209     }
210   }
211   if ($node) {
212     if (is_array($node)) {
213       $type = $node['type'];
214     }
215     elseif (is_object($node)) {
216       $type = $node->type;
217     }
218     elseif (is_string($node)) {
219       $type = $node;
220     }
221     if (!isset($node_names[$type])) {
222       return FALSE;
223     }
224   }
225   switch ($op) {
226     case 'base':
227       return $node_names[$type]['base'];
228     case 'list':
229       return $node_list;
230     case 'name':
231       return $node_list[$type];
232   }
233 }
234
235 /**
236  * Determine the basename for hook_load etc.
237  *
238  * @param $node
239  *   Either a node object, a node array, or a string containing the node type.
240  * @return
241  *   The basename for hook_load, hook_nodeapi etc.
242  */
243 function node_get_base($node) {
244   return _node_names('base', $node);
245 }
246
247 /**
248  * Determine the human readable name for a given type.
249  *
250  * @param $node
251  *   Either a node object, a node array, or a string containing the node type.
252  * @return
253  *   The human readable name of the node type.
254  */
255 function node_get_name($node) {
256   return _node_names('name', $node);
257 }
258
259 /**
260  * Return the list of available node types.
261  *
262  * @param $node
263  *   Either a node object, a node array, or a string containing the node type.
264  * @return
265  *   An array consisting ('#type' => name) pairs.
266  */
267 function node_get_types() {
268   return _node_names('list');
269 }
270
271 /**
272  * Determine whether a node hook exists.
273  *
274  * @param &$node
275  *   Either a node object, node array, or a string containing the node type.
276  * @param $hook
277  *   A string containing the name of the hook.
278  * @return
279  *   TRUE iff the $hook exists in the node type of $node.
280  */
281 function node_hook(&$node, $hook) {
282   return module_hook(node_get_base($node), $hook);
283 }
284
285 /**
286  * Invoke a node hook.
287  *
288  * @param &$node
289  *   Either a node object, node array, or a string containing the node type.
290  * @param $hook
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.
294  * @return
295  *   The returned value of the invoked hook.
296  */
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));
301   }
302 }
303
304 /**
305  * Invoke a hook_nodeapi() operation in all modules.
306  *
307  * @param &$node
308  *   A node object.
309  * @param $op
310  *   A string containing the name of the nodeapi operation.
311  * @param $a3, $a4
312  *   Arguments to pass on to the hook, after the $node and $op arguments.
313  * @return
314  *   The returned value of the invoked hooks.
315  */
316 function node_invoke_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
317   $return = array();
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);
323     }
324     else if (isset($result)) {
325       $return[] = $result;
326     }
327   }
328   return $return;
329 }
330
331 /**
332  * Load a node object from the database.
333  *
334  * @param $param
335  *   Either the nid of the node or an array of conditions to match against in the database query
336  * @param $revision
337  *   Which numbered revision to load. Defaults to the current version.
338  * @param $reset
339  *   Whether to reset the internal node_load cache.
340  *
341  * @return
342  *   A fully-populated node object.
343  */
344 function node_load($param = array(), $revision = NULL, $reset = NULL) {
345   static $nodes = array();
346
347   if ($reset) {
348     $nodes = array();
349   }
350
351   $arguments = array();
352   if (is_numeric($param)) {
353     $cachable = $revision == NULL;
354     if ($cachable && isset($nodes[$param])) {
355       return $nodes[$param];
356     }
357     $cond = 'n.nid = %d';
358     $arguments[] = $param;
359   }
360   else {
361     // Turn the conditions into a query.
362     foreach ($param as $key => $value) {
363       $cond[] = 'n.'. db_escape_string($key) ." = '%s'";
364       $arguments[] = $value;
365     }
366     $cond = implode(' AND ', $cond);
367   }
368
369   // Retrieve the node.
370   // No db_rewrite_sql is applied so as to get complete indexing for search.
371   if ($revision) {
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));
374   }
375   else {
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));
377   }
378
379   if ($node->nid) {
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;
385       }
386     }
387
388     if ($extra = node_invoke_nodeapi($node, 'load')) {
389       foreach ($extra as $key => $value) {
390         $node->$key = $value;
391       }
392     }
393   }
394
395   if ($cachable) {
396     $nodes[$param] = $node;
397   }
398
399   return $node;
400 }
401
402 /**
403  * Save a node object into the database.
404  */
405 function node_save(&$node) {
406   global $user;
407
408   $node->is_new = false;
409
410   // Apply filters to some default node fields:
411   if (empty($node->nid)) {
412     // Insert a new node.
413     $node->is_new = true;
414
415     $node->nid = db_next_id('{node}_nid');
416     $node->vid = db_next_id('{node_revisions}_vid');;
417   }
418   else {
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;
423     }
424     $node = $node_current;
425
426     if ($node->revision) {
427       $node->old_vid = $node->vid;
428       $node->vid = db_next_id('{node_revisions}_vid');
429     }
430   }
431
432   // Set some required fields:
433   if (empty($node->created)) {
434     $node->created = time();
435   }
436   // The changed timestamp is always updated for bookkeeping purposes (revisions, searching, ...)
437   $node->changed = time();
438
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',
459                     'sticky' => '%d');
460
461   //Generate the node table query and the
462   //the node_revisions table query
463   if ($node->is_new) {
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) .')';
466   }
467   else {
468     $arr = array();
469     foreach ($node_table_types as $key => $value) {
470       $arr[] = $key .' = '. $value;
471     }
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) .')';
476     }
477     else {
478       $arr = array();
479       foreach ($revisions_table_types as $key => $value) {
480         $arr[] = $key .' = '. $value;
481       }
482       $revisions_table_values[] = $node->vid;
483       $revisions_query = 'UPDATE {node_revisions} SET '. implode(', ', $arr) .' WHERE vid = %d';
484     }
485   }
486
487   // Insert the node into the database:
488   db_query($node_query, $node_table_values);
489   db_query($revisions_query, $revisions_table_values);
490
491   // Call the node specific callback (if any):
492   if ($node->is_new) {
493     node_invoke($node, 'insert');
494     node_invoke_nodeapi($node, 'insert');
495   }
496   else {
497     node_invoke($node, 'update');
498     node_invoke_nodeapi($node, 'update');
499   }
500
501   // Clear the cache so an anonymous poster can see the node being added or updated.
502   cache_clear_all();
503 }
504
505 /**
506  * Generate a display of the given node.
507  *
508  * @param $node
509  *   A node array or node object.
510  * @param $teaser
511  *   Whether to display the teaser only, as on the main page.
512  * @param $page
513  *   Whether the node is being displayed by itself as a page.
514  * @param $links
515  *   Whether or not to display node links. Links are omitted for node previews.
516  *
517  * @return
518  *   An HTML representation of the themed node.
519  */
520 function node_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) {
521   $node = (object)$node;
522
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);
526
527   if ($node->log != '' && !$teaser && $node->moderate) {
528     $node->body .= '<div class="log"><div class="title">'. t('Log') .':</div>'. filter_xss($node->log) .'</div>';
529   }
530
531   // The 'view' hook can be implemented to overwrite the default function
532   // to display nodes.
533   if (node_hook($node, 'view')) {
534     node_invoke($node, 'view', $teaser, $page);
535   }
536   else {
537     $node = node_prepare($node, $teaser);
538   }
539   // Allow modules to change $node->body before viewing.
540   node_invoke_nodeapi($node, 'view', $teaser, $page);
541   if ($links) {
542     $node->links = module_invoke_all('link', 'node', $node, !$page);
543   }
544   // unset unused $node part so that a bad theme can not open a security hole
545   if ($teaser) {
546     unset($node->body);
547   }
548   else {
549     unset($node->teaser);
550   }
551
552   return theme('node', $node, $teaser, $page);
553 }
554
555 /**
556  * Apply filters to a node in preparation for theming.
557  */
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);
562   }
563   else {
564     $node->teaser = check_markup($node->teaser, $node->format, FALSE);
565   }
566   return $node;
567 }
568
569 /**
570  * Generate a page displaying a single node, along with its comments.
571  */
572 function node_show($node, $cid) {
573   $output = node_view($node, FALSE, TRUE);
574
575   if (function_exists('comment_render') && $node->comment) {
576     $output .= comment_render($node, $cid);
577   }
578
579   // Update the history table, stating that this user viewed this node.
580   node_tag_new($node->nid);
581
582   return $output;
583 }
584
585 /**
586  * Implementation of hook_perm().
587  */
588 function node_perm() {
589   return array('administer nodes', 'access content', 'view revisions', 'revert revisions');
590 }
591
592 /**
593  * Implementation of hook_search().
594  */
595 function node_search($op = 'search', $keys = null) {
596   switch ($op) {
597     case 'name':
598       return t('content');
599
600     case 'reset':
601       variable_del('node_cron_last');
602       variable_del('node_cron_last_nid');
603       return;
604
605     case 'status':
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);
611
612     case 'admin':
613       $form = array();
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>');
618
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');
623       }
624       if (module_exist('statistics') && variable_get('statistics_count_content_views', 0)) {
625         $ranking['node_rank_views'] = t('Number of views');
626       }
627
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));
632       }
633       return $form;
634
635     case 'search':
636       // Build matching conditions
637       list($join1, $where1) = _db_rewrite_sql();
638       $arguments1 = array();
639       $conditions1 = 'n.status = 1';
640
641       if ($type = search_query_extract($keys, 'type')) {
642         $types = array();
643         foreach (explode(',', $type) as $t) {
644           $types[] = "n.type = '%s'";
645           $arguments1[] = $t;
646         }
647         $conditions1 .= ' AND ('. implode(' OR ', $types) .')';
648         $keys = search_query_insert($keys, 'type');
649       }
650
651       if ($category = search_query_extract($keys, 'category')) {
652         $categories = array();
653         foreach (explode(',', $category) as $c) {
654           $categories[] = "tn.tid = %d";
655           $arguments1[] = $c;
656         }
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');
660       }
661
662       // Build ranking expression (we try to map each parameter to a
663       // uniform distribution in the range 0..1).
664       $ranking = array();
665       $arguments2 = array();
666       $join2 = '';
667       // Used to avoid joining on node_comment_statistics twice
668       $stats_join = false;
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;
673       }
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';
680         $stats_join = true;
681       }
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;
688         if (!$stats_join) {
689           $join2 .= ' LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
690         }
691       }
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';
700       }
701       $select2 = (count($ranking) ? implode(' + ', $ranking) : 'i.relevance') . ' AS score';
702
703       // Do search
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);
705
706       // Load results
707       $results = array();
708       foreach ($find as $item) {
709         $node = node_load($item->sid);
710
711         // Get node output (filtered and with module-specific fields).
712         if (node_hook($node, 'view')) {
713           node_invoke($node, 'view', false, false);
714         }
715         else {
716           $node = node_prepare($node, false);
717         }
718         // Allow modules to change $node->body before viewing.
719         node_invoke_nodeapi($node, 'view', false, false);
720
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');
725
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,
732                            'node' => $node,
733                            'extra' => $extra,
734                            'snippet' => search_excerpt($keys, $node->body));
735       }
736       return $results;
737   }
738 }
739
740 /**
741  * Implementation of hook_user().
742  */
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);
747   }
748 }
749
750 function theme_node_search_admin($form) {
751   $output = form_render($form['info']);
752
753   $header = array(t('Factor'), t('Weight'));
754   foreach (element_children($form['factors']) as $key) {
755     $row = array();
756     $row[] = $form['factors'][$key]['#title'];
757     unset($form['factors'][$key]['#title']);
758     $row[] = form_render($form['factors'][$key]);
759     $rows[] = $row;
760   }
761   $output .= theme('table', $header, $rows);
762
763   $output .= form_render($form);
764   return $output;
765 }
766
767 /**
768  * Menu callback; presents general node configuration options.
769  */
770 function node_configure() {
771
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.')
776   );
777
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.")
784   );
785
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?')
789   );
790
791   return system_settings_form('node_configure', $form);
792 }
793
794 /**
795  * Retrieve the comment mode for the given node ID (none, read, or read/write).
796  */
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));
801   }
802   return $comment_mode[$nid];
803 }
804
805 /**
806  * Implementation of hook_link().
807  */
808 function node_link($type, $node = 0, $main = 0) {
809   $links = array();
810
811   if ($type == 'node') {
812     if (array_key_exists('links', $node)) {
813       $links = $node->links;
814     }
815
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'));
818     }
819   }
820
821   return $links;
822 }
823
824 /**
825  * Implementation of hook_menu().
826  */
827 function node_menu($may_cache) {
828   $items = array();
829
830   if ($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);
836
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);
842     }
843
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'));
850
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,
859       'weight' => 1);
860     $items[] = array('path' => 'rss.xml', 'title' => t('rss feed'),
861       'callback' => 'node_feed',
862       'access' => user_access('access content'),
863       'type' => MENU_CALLBACK);
864   }
865   else {
866     if (arg(0) == 'node' && is_numeric(arg(1))) {
867       $node = node_load(arg(1));
868       if ($node->nid) {
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),
878           'weight' => 1,
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),
883           'weight' => 1,
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,
889           'weight' => 2,
890           'type' => MENU_LOCAL_TASK);
891       }
892     }
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);
897     }
898   }
899
900   return $items;
901 }
902
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);
906 }
907
908 /**
909  * List node administration operations that can be performed.
910  */
911 function node_operations() {
912   $operations = array(
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'), '')
919   );
920   return $operations;
921 }
922
923 /**
924  * List node administration filters that can be applied.
925  */
926 function node_filters() {
927   // Regular 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);
937   }
938
939   return $filters;
940 }
941
942 /**
943  * Build query for node administration filters based on session.
944  */
945 function node_build_filter_query() {
946   $filters = node_filters();
947
948   // Build query
949   $where = $args = array();
950   $join = '';
951   foreach ($_SESSION['node_overview_filter'] as $index => $filter) {
952     list($key, $value) = $filter;
953     switch($key) {
954       case 'status':
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';
958         break;
959       case 'category':
960         $table = "tn$index";
961         $where[] = "$table.tid = %d";
962         $join .= "INNER JOIN {term_node} $table ON n.nid = $table.nid ";
963         break;
964       case 'type':
965         $where[] = "n.type = '%s'";
966     }
967     $args[] = $value;
968   }
969   $where = count($where) ? 'WHERE '. implode(' AND ', $where) : '';
970
971   return array('where' => $where, 'join' => $join, 'args' => $args);
972 }
973
974 /**
975  * Return form for node administration filters.
976  */
977 function node_filter_form() {
978   $session = &$_SESSION['node_overview_filter'];
979   $session = is_array($session) ? $session : array();
980   $filters = node_filters();
981
982   $i = 0;
983   $form['filters'] = array('#type' => 'fieldset',
984     '#title' => t('Show only items where'),
985     '#theme' => 'node_filters',
986   );
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;
993     }
994     else {
995       $value = $filters[$type]['options'][$value];
996     }
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)));
999   }
1000
1001   foreach ($filters as $key => $filter) {
1002     $names[$key] = $filter['title'];
1003     $form['filters']['status'][$key] = array('#type' => 'select', '#options' => $filter['options']);
1004   }
1005
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'));
1011   }
1012
1013   return drupal_get_form('node_filter_form', $form);
1014 }
1015
1016 /**
1017  * Theme node administration filter form.
1018  */
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);
1024   return $output;
1025 }
1026
1027 /**
1028  * Theme node administraton filter selector.
1029  */
1030 function theme_node_filters(&$form) {
1031   $output .= '<ul>';
1032   if (sizeof($form['current'])) {
1033     foreach (element_children($form['current']) as $key) {
1034       $output .= '<li>' . form_render($form['current'][$key]) . '</li>';
1035     }
1036   }
1037
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]);
1041   }
1042   $output .= '</dd>';
1043
1044   $output .= '<dt>'. t('is') .'</dt>' . '<dd class="b">';
1045
1046   foreach (element_children($form['status']) as $key) {
1047     $output .= form_render($form['status'][$key]);
1048   }
1049   $output .= '</dd>';
1050
1051   $output .= '</dl>';
1052   $output .= '<div class="container-inline" id="node-admin-buttons">'. form_render($form['buttons']) .'</div>';
1053   $output .= '</li></ul><br class="clear" />';
1054
1055   return $output;
1056 }
1057
1058 /**
1059  * Process result from node administration filter form.
1060  */
1061 function node_filter_form_submit() {
1062   global $form_values;
1063   $op = $_POST['op'];
1064   $filters = node_filters();
1065   switch ($op) {
1066     case t('Filter'):
1067     case t('Refine'):
1068       if (isset($form_values['filter'])) {
1069         $filter = $form_values['filter'];
1070
1071         // Flatten the options array to accommodate hierarchical/nested options.
1072         $flat_options = form_options_flatten($filters[$filter]['options']);
1073
1074         if (isset($flat_options[$form_values[$filter]])) {
1075           $_SESSION['node_overview_filter'][] = array($filter, $form_values[$filter]);
1076         }
1077       }
1078       break;
1079     case t('Undo'):
1080       array_pop($_SESSION['node_overview_filter']);
1081       break;
1082     case t('Reset'):
1083       $_SESSION['node_overview_filter'] = array();
1084       break;
1085   }
1086 }
1087
1088 /**
1089  * Generate the content administration overview.
1090  */
1091 function node_admin_nodes_submit($form_id, $edit) {
1092   $operations = node_operations();
1093   if ($operations[$edit['operation']][1]) {
1094     // Flag changes
1095     $operation = $operations[$edit['operation']][1];
1096     foreach ($edit['nodes'] as $nid => $value) {
1097       if ($value) {
1098         db_query($operation, $nid);
1099       }
1100     }
1101     cache_clear_all();
1102     drupal_set_message(t('The update has been performed.'));
1103   }
1104 }
1105
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.'));
1111     }
1112     else {
1113       form_set_error('', t('Please select some items to perform the update on.'));
1114     }
1115   }
1116 }
1117
1118 /**
1119  * Menu callback: content administration.
1120  */
1121 function node_admin_nodes() {
1122   global $form_values;
1123   $output = node_filter_form();
1124
1125   if ($_POST['edit']['operation'] == 'delete' && $_POST['edit']['nodes']) {
1126     return node_multiple_delete_confirm();
1127   }
1128
1129   $filter = node_build_filter_query();
1130
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']);
1132
1133   $form['options'] = array('#type' => 'fieldset',
1134     '#title' => t('Update options'),
1135     '#prefix' => '<div class="container-inline">',
1136     '#suffix' => '</div>',
1137   );
1138   $options = array();
1139   foreach (node_operations() as $key => $value) {
1140     $options[$key] = $value[0];
1141   }
1142   $form['options']['operation'] = array('#type' => 'select', '#options' => $options,  '#default_value' => 'approve');
1143   $form['options']['submit'] = array('#type' => 'submit', '#value' => t('Update'));
1144
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));
1153   }
1154   $form['nodes'] = array('#type' => 'checkboxes', '#options' => $nodes);
1155   $form['pager'] = array('#value' => theme('pager', NULL, 50, 0));
1156
1157   // Call the form first, to allow for the form_values array to be populated.
1158   $output .= drupal_get_form('node_admin_nodes', $form);
1159
1160   return $output;
1161 }
1162
1163 /**
1164  * Theme node administration overview.
1165  */
1166 function theme_node_admin_nodes($form) {
1167   // Overview table:
1168   $header = array(NULL, t('Title'), t('Type'), t('Author'), t('Status'), t('Operations'));
1169
1170   $output .= form_render($form['options']);
1171   if (isset($form['title']) && is_array($form['title'])) {
1172     foreach (element_children($form['title']) as $key) {
1173       $row = array();
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]);
1180       $rows[] = $row;
1181     }
1182
1183   }
1184   else  {
1185     $rows[] = array(array('data' => t('No posts available.'), 'colspan' => '6'));
1186   }
1187
1188   $output .= theme('table', $header, $rows);
1189   if ($form['pager']['#value']) {
1190     $output .= form_render($form['pager']);
1191   }
1192
1193   $output .= form_render($form);
1194
1195   return $output;
1196 }
1197
1198 function node_multiple_delete_confirm() {
1199   $edit = $_POST['edit'];
1200
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");
1206   }
1207   $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
1208
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'));
1213 }
1214
1215 function node_multiple_delete_confirm_submit($form_id, $edit) {
1216   if ($edit['confirm']) {
1217     foreach ($edit['nodes'] as $nid => $value) {
1218       node_delete($nid);
1219     }
1220     drupal_set_message(t('The items have been deleted.'));
1221   }
1222   return 'admin/node';
1223 }
1224
1225 /**
1226  * Menu callback; presents each node type configuration page.
1227  */
1228 function node_types_configure($type = NULL) {
1229   if (isset($type)) {
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)))
1236     );
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)))
1240     );
1241     $form['workflow'] = array('#type' => 'fieldset', '#title' =>t('Workflow'));
1242     $form['type'] = array('#type' => 'value', '#value' => $type);
1243
1244     $form['array_filter'] = array('#type' => 'value', '#value' => TRUE);
1245     return system_settings_form($type .'_node_settings', $form);
1246   }
1247   else {
1248     $header = array(t('Type'), t('Operations'));
1249
1250     $rows = array();
1251     foreach (node_get_types() as $type => $name) {
1252       $rows[] = array($name, l(t('configure'), 'admin/settings/content-types/'. $type));
1253     }
1254
1255     return theme('table', $header, $rows);
1256   }
1257 }
1258
1259 /**
1260  * Generate an overview table of older revisions of a node.
1261  */
1262 function node_revision_overview($node) {
1263   drupal_set_title(t('Revisions for %title', array('%title' => check_plain($node->title))));
1264
1265   $header = array(t('Revision'), array('data' => t('Operations'), 'colspan' => 2));
1266
1267   $revisions = node_revision_list($node);
1268
1269   $rows = array();
1270   $revert_permission = FALSE;
1271   if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) {
1272     $revert_permission = TRUE;
1273   }
1274   $delete_permission = FALSE;
1275   if (user_access('administer nodes')) {
1276     $delete_permission = TRUE;
1277   }
1278   foreach ($revisions as $revision) {
1279     $row = array();
1280     $operations = array();
1281
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);
1287     }
1288     else {
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");
1293       }
1294       if ($delete_permission) {
1295         $operations[] = l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete");
1296       }
1297     }
1298     $rows[] = array_merge($row, $operations);
1299   }
1300   $output .= theme('table', $header, $rows);
1301
1302   return $output;
1303 }
1304
1305 /**
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.
1308  */
1309 function node_revision_revert($nid, $revision) {
1310   global $user;
1311
1312   $node = node_load($nid, $revision);
1313   if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) {
1314     if ($node->vid) {
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);
1318
1319       node_save($node);
1320
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))));
1323     }
1324     else {
1325       drupal_set_message(t('You tried to revert to an invalid revision.'), 'error');
1326     }
1327     drupal_goto('node/'. $nid .'/revisions');
1328   }
1329   drupal_access_denied();
1330 }
1331
1332 /**
1333  * Delete the revision with specified revision number. A "delete revision" nodeapi event is invoked when a
1334  * revision is deleted.
1335  */
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);
1343
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))));
1348       }
1349
1350       else {
1351         drupal_set_message(t('Deletion failed. You tried to delete the current revision.'));
1352       }
1353       if (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $nid)) > 1) {
1354         drupal_goto("node/$nid/revisions");
1355       }
1356       else {
1357         drupal_goto("node/$nid");
1358       }
1359     }
1360   }
1361
1362   drupal_access_denied();
1363 }
1364
1365 /**
1366  * Return a list of all the existing revision numbers.
1367  */
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;
1373   }
1374
1375   return $revisions;
1376 }
1377
1378 function node_admin_search() {
1379   $output = search_form(url('admin/node/search'), $_POST['edit']['keys'], 'node') . search_data($_POST['edit']['keys'], 'node');
1380   return $output;
1381 }
1382
1383 /**
1384  * Implementation of hook_block().
1385  */
1386 function node_block($op = 'list', $delta = 0) {
1387   if ($op == 'list') {
1388     $blocks[0]['info'] = t('Syndicate');
1389     return $blocks;
1390   }
1391   else if ($op == 'view') {
1392     $block['subject'] = t('Syndicate');
1393     $block['content'] = theme('feed_icon', url('rss.xml'));
1394
1395     return $block;
1396   }
1397 }
1398
1399 /**
1400  * A generic function for generating RSS feeds from a set of nodes.
1401  *
1402  * @param $nodes
1403  *   An object as returned by db_query() which contains the nid field.
1404  * @param $channel
1405  *   An associative array containing title, link, description and other keys.
1406  *   The link should be an absolute URL.
1407  */
1408 function node_feed($nodes = 0, $channel = array()) {
1409   global $base_url, $locale;
1410
1411   if (!$nodes) {
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));
1413   }
1414
1415   $item_length = variable_get('feed_item_length', 'teaser');
1416   $namespaces = array('xmlns:dc="http://purl.org/dc/elements/1.1/"');
1417
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);
1422
1423     if ($item_length != 'title') {
1424       $teaser = ($item_length == 'teaser') ? TRUE : FALSE;
1425
1426       // Filter and prepare node teaser
1427       if (node_hook($item, 'view')) {
1428         node_invoke($item, 'view', $teaser, FALSE);
1429       }
1430       else {
1431         $item = node_prepare($item, $teaser);
1432       }
1433
1434       // Allow modules to change $node->teaser before viewing.
1435       node_invoke_nodeapi($item, 'view', $teaser, FALSE);
1436     }
1437
1438     // Prepare the item description
1439     switch ($item_length) {
1440       case 'fulltext':
1441         $item_text = $item->body;
1442         break;
1443       case 'teaser':
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>';
1447         }
1448         break;
1449       case 'title':
1450         $item_text = '';
1451         break;
1452     }
1453
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']);
1460       }
1461     }
1462     $items .= format_rss_item($item->title, $link, $item_text, $extra);
1463   }
1464
1465   $channel_defaults = array(
1466     'version'     => '2.0',
1467     'title'       => variable_get('site_name', 'drupal') .' - '. variable_get('site_slogan', ''),
1468     'link'        => $base_url,
1469     'description' => variable_get('site_mission', ''),
1470     'language'    => $locale
1471   );
1472   $channel = array_merge($channel_defaults, $channel);
1473
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";
1478
1479   drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
1480   print $output;
1481 }
1482
1483 /**
1484  * Prepare node for save and allow modules to make changes.
1485  */
1486 function node_submit($node) {
1487   global $user;
1488
1489   // Convert the node to an object, if necessary.
1490   $node = (object)$node;
1491
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) : '';
1496   }
1497
1498   $access = user_access('administer nodes');
1499   if ($access) {
1500     // Populate the "authored by" field.
1501     if ($account = user_load(array('name' => $node->name))) {
1502       $node->uid = $account->uid;
1503     }
1504     else {
1505       $node->uid = 0;
1506     }
1507
1508     $node->created = $node->date ? strtotime($node->date) : NULL;
1509   }
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);
1515     }
1516   }
1517
1518   // Do node-type-specific validation checks.
1519   node_invoke($node, 'submit');
1520   node_invoke_nodeapi($node, 'submit');
1521
1522   $node->validated = TRUE;
1523
1524   return $node;
1525 }
1526
1527 /**
1528  * Perform validation checks on the given node.
1529  */
1530 function node_validate($node, $form = array()) {
1531   // Convert the node to an object, if necessary.
1532   $node = (object)$node;
1533
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))));
1538   }
1539
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.'));
1542   }
1543
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))));
1551     }
1552
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.'));
1556     }
1557   }
1558
1559   // Do node-type-specific validation checks.
1560   node_invoke($node, 'validate', $form);
1561   node_invoke_nodeapi($node, 'validate', $form);
1562 }
1563
1564 function node_form_validate($form_id, $form_values, $form) {
1565   node_validate($form_values, $form);
1566 }
1567
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();
1573     }
1574
1575     if (!isset($node->date)) {
1576       $node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O');
1577     }
1578   }
1579   node_invoke($node, 'prepare');
1580   node_invoke_nodeapi($node, 'prepare');
1581 }
1582
1583 /**
1584  * Generate the node editing form.
1585  */
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');
1590 }
1591
1592 /**
1593 * Generate the node editing form array.
1594 */
1595 function node_form_array($node) {
1596   node_object_prepare($node);
1597
1598   // Set the id of the top-level form tag
1599   $form['#id'] = 'node-form';
1600
1601   /**
1602    * Basic node information.
1603    * These elements are just values so they are not even sent to the client.
1604    */
1605   foreach (array('nid', 'vid', 'uid', 'created', 'type') as $key) {
1606     $form[$key] = array('#type' => 'value', '#value' => $node->$key);
1607   }
1608
1609   // Changed must be sent to the client, for later overwrite error checking.
1610   $form['changed'] = array('#type' => 'hidden', '#value' => $node->changed);
1611
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;
1616   }
1617
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);
1623     }
1624     global $user;
1625     $node->uid = $user->uid;
1626   }
1627   else {
1628     // Nodes being edited should always be preset with the default revision setting.
1629     $node->revision = in_array('revision', $node_options);
1630   }
1631   $form['#node'] = $node;
1632
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)));
1638
1639     if (isset($node->nid)) {
1640       $form['author']['date']['#default_value'] = $node->date;
1641     }
1642
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);
1650   }
1651   else {
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);
1655     }
1656   }
1657
1658   // Add the buttons.
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);
1663   }
1664
1665   $form['#after_build'] = array('node_form_add_preview');
1666
1667   return $form;
1668 }
1669
1670 function node_form_add_preview($form) {
1671   global $form_values;
1672
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);
1679     }
1680   }
1681   if (variable_get('node_preview', 0) && (form_get_errors() || $op != t('Preview'))) {
1682     unset($form['submit']);
1683   }
1684   return $form;
1685 }
1686
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']);
1691   }
1692
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().
1696   $admin = '';
1697   if (isset($form['author'])) {
1698     $admin .= "    <div class=\"authored\">\n";
1699     $admin .= form_render($form['author']);
1700     $admin .= "    </div>\n";
1701   }
1702   if (isset($form['options'])) {
1703     $admin .= "    <div class=\"options\">\n";
1704     $admin .= form_render($form['options']);
1705     $admin .= "    </div>\n";
1706   }
1707   $buttons = form_render($form['preview']);
1708   $buttons .= form_render($form['submit']);
1709   $buttons .= isset($form['delete']) ? form_render($form['delete']) : '';
1710
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";
1716
1717   if (!empty($admin)) {
1718     $output .= "  <div class=\"admin\">\n";
1719     $output .= $admin;
1720     $output .= "  </div>\n";
1721   }
1722   $output .= $buttons;
1723   $output .= "</div>\n";
1724
1725   return $output;
1726 }
1727
1728 /**
1729  * Present a node submission form or a set of links to such forms.
1730  */
1731 function node_add($type) {
1732   global $user;
1733
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);
1738
1739     $output = node_form($node);
1740     drupal_set_title(t('Submit %name', array('%name' => node_get_name($node))));
1741   }
1742   else {
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;
1749       }
1750     }
1751
1752     if (isset($item)) {
1753       uksort($item, 'strnatcasecmp');
1754       $output = t('Choose the appropriate item from the list:') .'<dl>'. implode('', $item) .'</dl>';
1755     }
1756     else {
1757       $output = t('You are not allowed to create content.');
1758     }
1759   }
1760
1761   return $output;
1762 }
1763
1764 /**
1765  * Generate a node preview.
1766  */
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;
1775       }
1776       else {
1777         $node->uid = 0; // anonymous user
1778       }
1779     }
1780     else if ($node->uid) {
1781       $user = user_load(array('uid' => $node->uid));
1782       $node->name = $user->name;
1783     }
1784
1785     // Set the timestamps when needed:
1786     if ($node->date) {
1787       $node->created = strtotime($node->date);
1788     }
1789     $node->changed = time();
1790
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);
1795     }
1796
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);
1803     }
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)));
1806
1807     return $output;
1808   }
1809 }
1810
1811 /**
1812  * Display a node preview for display during node creation and editing.
1813  *
1814  * @param $node
1815  *   The node object which is being previewed.
1816  */
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 "&lt;!--break--&gt;" (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);
1825   }
1826   else {
1827     $output .= node_view($node, 0, FALSE, 0);
1828   }
1829   $output .= "</div>\n";
1830
1831   return $output;
1832 }
1833
1834 function node_form_submit($form_id, $edit) {
1835   global $user;
1836
1837   // Fix up the node when required:
1838   $node = node_submit($edit);
1839
1840   // Prepare the node's body:
1841   if ($node->nid) {
1842     // Check whether the current user has the proper access rights to
1843     // perform this operation:
1844     if (node_access('update', $node)) {
1845       node_save($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))));
1848     }
1849   }
1850   else {
1851     // Check whether the current user has the proper access rights to
1852     // perform this operation:
1853     if (node_access('create', $node)) {
1854       node_save($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))));
1857     }
1858   }
1859   if ($node->nid) {
1860     if (node_access('view', $node)) {
1861       return 'node/'. $node->nid;
1862     }
1863     else {
1864       return '';
1865     }
1866   }
1867   // it is very unlikely we get here
1868   return FALSE;
1869 }
1870
1871 /**
1872  * Menu callback -- ask for confirmation of node deletion
1873  */
1874 function node_delete_confirm() {
1875   $edit = $_POST['edit'];
1876   $edit['nid'] = $edit['nid'] ? $edit['nid'] : arg(1);
1877   $node = node_load($edit['nid']);
1878
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')  );
1885   }
1886
1887   return $output;
1888 }
1889
1890 /**
1891  * Execute node deletion
1892  */
1893 function node_delete_confirm_submit($form_id, $form_values) {
1894   if ($form_values['confirm']) {
1895     node_delete($form_values['nid']);
1896   }
1897
1898   return '';
1899 }
1900
1901 /**
1902  * Delete a node.
1903  */
1904 function node_delete($nid) {
1905
1906   $node = node_load($nid);
1907
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);
1911
1912     // Call the node-specific callback (if any):
1913     node_invoke($node, 'delete');
1914     node_invoke_nodeapi($node, 'delete');
1915
1916     // Clear the cache so an anonymous poster can see the node being deleted.
1917     cache_clear_all();
1918
1919     // Remove this node from the search index if needed.
1920     if (function_exists('search_wipe')) {
1921       search_wipe($node->nid, 'node');
1922     }
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))));
1925   }
1926 }
1927
1928 /**
1929  * Menu callback for revisions related activities.
1930  */
1931 function node_revisions() {
1932   if (is_numeric(arg(1)) && arg(2) == 'revisions') {
1933     $op = arg(4) ? arg(4) : 'overview';
1934     switch ($op) {
1935       case '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);
1939         }
1940         drupal_access_denied();
1941         return;
1942       case 'view':
1943         if (is_numeric(arg(3))) {
1944           $node = node_load(arg(1), arg(3));
1945           if ($node->nid) {
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));
1949             }
1950             drupal_access_denied();
1951             return;
1952           }
1953         }
1954         break;
1955       case 'revert':
1956         node_revision_revert(arg(1), arg(3));
1957         break;
1958       case 'delete':
1959         node_revision_delete(arg(1), arg(3));
1960         break;
1961     }
1962   }
1963   drupal_not_found();
1964 }
1965
1966 /**
1967  * Generate a listing of promoted nodes.
1968  */
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));
1971
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)));
1977
1978     $output = '';
1979     while ($node = db_fetch_object($result)) {
1980       $output .= node_view(node_load($node->nid), 1);
1981     }
1982     $output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
1983   }
1984   else {
1985     $output = t('
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>
1988       <ol>
1989         <li>
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.
1992         </li>
1993         <li>
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.
1996         </li>
1997         <li>
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>.
2000         </li>
2001         <li>
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>.
2004         </li>
2005         <li>
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.
2008         </li>
2009       </ol>
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')
2013     );
2014     $output = '<div id="first-time">'. $output .'</div>';
2015   }
2016
2017   return $output;
2018 }
2019
2020 /**
2021  * Menu callback; dispatches control to the appropriate operation handler.
2022  */
2023 function node_page() {
2024   $op = arg(1);
2025
2026   if (is_numeric($op)) {
2027     $op = (arg(2) && !is_numeric(arg(2))) ? arg(2) : 'view';
2028   }
2029
2030   switch ($op) {
2031     case 'view':
2032       if (is_numeric(arg(1))) {
2033         $node = node_load(arg(1));
2034         if ($node->nid) {
2035           drupal_set_title(check_plain($node->title));
2036           return node_show($node, arg(2));
2037         }
2038         else if (db_result(db_query('SELECT nid FROM {node} WHERE nid = %d', arg(1)))) {
2039           drupal_access_denied();
2040         }
2041         else {
2042           drupal_not_found();
2043         }
2044       }
2045       break;
2046     case 'add':
2047       return node_add(arg(2));
2048       break;
2049     case 'edit':
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']);
2055         }
2056         drupal_goto('node/'. arg(1) .'/delete', $destination);
2057       }
2058
2059       if (is_numeric(arg(1))) {
2060         $node = node_load(arg(1));
2061         if ($node->nid) {
2062           drupal_set_title(check_plain($node->title));
2063           return node_form($node);
2064         }
2065         else if (db_result(db_query('SELECT nid FROM {node} WHERE nid = %d', arg(1)))) {
2066           drupal_access_denied();
2067         }
2068         else {
2069           drupal_not_found();
2070         }
2071       }
2072       break;
2073     default:
2074       drupal_set_title('');
2075       return node_page_default();
2076   }
2077 }
2078
2079 /**
2080  * shutdown function to make sure we always mark the last node processed.
2081  */
2082 function node_update_shutdown() {
2083   global $last_change, $last_nid;
2084
2085   if ($last_change && $last_nid) {
2086     variable_set('node_cron_last', $last_change);
2087     variable_set('node_cron_last_nid', $last_nid);
2088   }
2089 }
2090
2091 /**
2092  * Implementation of hook_update_index().
2093  */
2094 function node_update_index() {
2095   global $last_change, $last_nid;
2096
2097   register_shutdown_function('node_update_shutdown');
2098
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);
2102
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}'))));
2106
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);
2108
2109   while ($node = db_fetch_object($result)) {
2110     $last_change = $node->last_change;
2111     $last_nid = $node->nid;
2112     $node = node_load($node->nid);
2113
2114     // Get node output (filtered and with module-specific fields).
2115     if (node_hook($node, 'view')) {
2116       node_invoke($node, 'view', false, false);
2117     }
2118     else {
2119       $node = node_prepare($node, false);
2120     }
2121     // Allow modules to change $node->body before viewing.
2122     node_invoke_nodeapi($node, 'view', false, false);
2123
2124     $text = '<h1>'. check_plain($node->title) .'</h1>'. $node->body;
2125
2126     // Fetch extra data normally not visible
2127     $extra = node_invoke_nodeapi($node, 'update index');
2128     foreach ($extra as $t) {
2129       $text .= $t;
2130     }
2131
2132     // Update index
2133     search_index($node->nid, 'node', $text);
2134   }
2135 }
2136
2137 /**
2138  * Implementation of hook_form_alter().
2139  */
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'),
2152       ),
2153       '#description' => t('Users with the <em>administer nodes</em> permission will be able to override these options.'),
2154     );
2155   }
2156
2157   // Advanced node search form
2158   elseif ($form_id == 'search_form' && arg(1) == 'node') {
2159     // Keyword boxes:
2160     $form['advanced'] = array(
2161       '#type' => 'fieldset',
2162       '#title' => t('Advanced search'),
2163       '#collapsible' => TRUE,
2164       '#collapsed' => TRUE,
2165       '#attributes' => array('class' => 'search-advanced'),
2166     );
2167     $form['advanced']['keywords'] = array(
2168       '#prefix' => '<div class="criterion">',
2169       '#suffix' => '</div>',
2170     );
2171     $form['advanced']['keywords']['or'] = array(
2172       '#type' => 'textfield',
2173       '#title' => t('Containing any of the words'),
2174       '#size' => 30,
2175       '#maxlength' => 255,
2176     );
2177     $form['advanced']['keywords']['phrase'] = array(
2178       '#type' => 'textfield',
2179       '#title' => t('Containing the phrase'),
2180       '#size' => 30,
2181       '#maxlength' => 255,
2182     );
2183     $form['advanced']['keywords']['negative'] = array(
2184       '#type' => 'textfield',
2185       '#title' => t('Containing none of the words'),
2186       '#size' => 30,
2187       '#maxlength' => 255,
2188     );
2189
2190     // Taxonomy box:
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">',
2196         '#size' => 10,
2197         '#suffix' => '</div>',
2198         '#options' => $taxonomy,
2199         '#multiple' => TRUE,
2200       );
2201     }
2202
2203     // Node types:
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,
2211     );
2212     $form['advanced']['submit'] = array(
2213       '#type' => 'submit',
2214       '#value' => t('Advanced search'),
2215       '#prefix' => '<div class="action">',
2216       '#suffix' => '</div><br class="clear" />',
2217     );
2218
2219     $form['#validate']['node_search_validate'] = array();
2220   }
2221 }
2222
2223 /**
2224  * Form API callback for the search form. Registered in node_form_alter().
2225  */
2226 function node_search_validate($form_id, $form_values, $form) {
2227   // Initialise using any existing basic search keywords.
2228   $keys = $form_values['processed_keys'];
2229
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'])));
2236     }
2237   }
2238
2239   if (isset($form_values['category']) && is_array($form_values['category'])) {
2240     $keys = search_query_insert($keys, 'category', implode(',', $form_values['category']));
2241   }
2242   if ($form_values['or'] != '') {
2243     if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $form_values['or'], $matches)) {
2244       $keys .= ' '. implode(' OR ', $matches[1]);
2245     }
2246   }
2247   if ($form_values['negative'] != '') {
2248     if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $form_values['negative'], $matches)) {
2249       $keys .= ' -'. implode(' -', $matches[1]);
2250     }
2251   }
2252   if ($form_values['phrase'] != '') {
2253     $keys .= ' "'. str_replace('"', ' ', $form_values['phrase']) .'"';
2254   }
2255   if (!empty($keys)) {
2256     form_set_value($form['basic']['inline']['processed_keys'], trim($keys));
2257   }
2258 }
2259
2260 /**
2261  * @defgroup node_access Node access rights
2262  * @{
2263  * The node access system determines who can do what to which nodes.
2264  *
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
2271  * PHP pages.
2272  *
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
2281  * node.
2282  *
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.
2288  *
2289  * To see how to write a node access module of your own, see
2290  * node_access_example.module.
2291  */
2292
2293 /**
2294  * Determine whether the current user may perform the given operation on the
2295  * specified node.
2296  *
2297  * @param $op
2298  *   The operation to be performed on the node. Possible values are:
2299  *   - "view"
2300  *   - "update"
2301  *   - "delete"
2302  *   - "create"
2303  * @param $node
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.
2306  * @param $uid
2307  *   The user ID on which the operation is to be performed.
2308  * @return
2309  *   TRUE if the operation may be performed.
2310  */
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;
2315   }
2316   // If the node is in a restricted format, disallow editing.
2317   if ($op == 'update' && !filter_access($node->format)) {
2318     return FALSE;
2319   }
2320
2321   if (user_access('administer nodes')) {
2322     return TRUE;
2323   }
2324
2325   if (!user_access('access content')) {
2326     return FALSE;
2327   }
2328
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)) {
2333     return $access;
2334   }
2335
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) {
2339     $grants = array();
2340     foreach (node_access_grants($op, $uid) as $realm => $gids) {
2341       foreach ($gids as $gid) {
2342         $grants[] = "(gid = $gid AND realm = '$realm')";
2343       }
2344     }
2345
2346     $grants_sql = '';
2347     if (count($grants)) {
2348       $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
2349     }
2350
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));
2354   }
2355   return FALSE;
2356 }
2357
2358 /**
2359  * Generate an SQL join clause for use in fetching a node listing.
2360  *
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.
2367  * @return
2368  *   An SQL join clause.
2369  */
2370 function _node_access_join_sql($node_alias = 'n', $node_access_alias = 'na') {
2371   if (user_access('administer nodes')) {
2372     return '';
2373   }
2374
2375   return 'INNER JOIN {node_access} '. $node_access_alias .' ON '. $node_access_alias .'.nid = '. $node_alias .'.nid';
2376 }
2377
2378 /**
2379  * Generate an SQL where clause for use in fetching a node listing.
2380  *
2381  * @param $op
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.
2386  * @return
2387  *   An SQL where clause.
2388  */
2389 function _node_access_where_sql($op = 'view', $node_access_alias = 'na', $uid = NULL) {
2390   if (user_access('administer nodes')) {
2391     return;
2392   }
2393
2394   $grants = array();
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')";
2398     }
2399   }
2400
2401   $grants_sql = '';
2402   if (count($grants)) {
2403     $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
2404   }
2405
2406   $sql = "$node_access_alias.grant_$op >= 1 $grants_sql";
2407   return $sql;
2408 }
2409
2410 /**
2411  * Fetch an array of permission IDs granted to the given user ID.
2412  *
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.
2416  *
2417  * @param $op
2418  *   The operation that the user is trying to perform.
2419  * @param $uid
2420  *   The user ID performing the operation. If omitted, the current user is used.
2421  * @return
2422  *   An associative array in which the keys are realms, and the values are
2423  *   arrays of grants for those realms.
2424  */
2425 function node_access_grants($op, $uid = NULL) {
2426   global $user;
2427
2428   if (isset($uid)) {
2429     $user_object = user_load(array('uid' => $uid));
2430   }
2431   else {
2432     $user_object = $user;
2433   }
2434
2435   return array_merge(array('all' => array(0)), module_invoke_all('node_grants', $user_object, $op));
2436 }
2437
2438 /**
2439  * Determine whether the user has a global viewing grant for all nodes.
2440  */
2441 function node_access_view_all_nodes() {
2442   static $access;
2443
2444   if (!isset($access)) {
2445     $grants = array();
2446     foreach (node_access_grants('view') as $realm => $gids) {
2447       foreach ($gids as $gid) {
2448         $grants[] = "(gid = $gid AND realm = '$realm')";
2449       }
2450     }
2451
2452     $grants_sql = '';
2453     if (count($grants)) {
2454       $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
2455     }
2456
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);
2460   }
2461
2462   return $access;
2463 }
2464
2465 /**
2466  * Implementation of hook_db_rewrite_sql
2467  */
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;
2473     return $return;
2474   }
2475 }
2476
2477 /**
2478  * @} End of "defgroup node_access".
2479  */
2480
2481