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

Contents of /drupal/modules/node.module

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


Revision 1.641.2.29 - (hide annotations) (download) (as text)
Mon Jan 1 22:16:11 2007 UTC (2 years, 10 months ago) by killes
Branch: DRUPAL-4-7
Changes since 1.641.2.28: +1 -3 lines
File MIME type: text/x-php
#100399, remove phpdoc that does not apply, patch by Robert Douglass
1 dries 1.1 <?php
2 killes 1.641.2.29 // $Id: node.module,v 1.641.2.28 2007/01/01 18:28:34 killes Exp $
3 dries 1.389
4     /**
5     * @file
6     * The core that allows content to be submitted to the site.
7     */
8 kjartan 1.321
9     define('NODE_NEW_LIMIT', time() - 30 * 24 * 60 * 60);
10 dries 1.1
11 dries 1.362 /**
12     * Implementation of hook_help().
13     */
14     function node_help($section) {
15 dries 1.248 switch ($section) {
16 dries 1.277 case 'admin/help#node':
17 dries 1.544 $output = '<p>'. t('All content in a website is stored and treated as <b>nodes</b>. Therefore nodes are any postings such as blogs, stories, polls and forums. The node module manages these content types and is one of the strengths of Drupal over other content management systems.') .'</p>';
18     $output .= '<p>'. t('Treating all content as nodes allows the flexibility of creating new types of content. It also allows you to painlessly apply new features or changes to all content. Comments are not stored as nodes but are always associated with a node.') .'</p>';
19     $output .= t('<p>Node module features</p>
20     <ul>
21     <li>The list tab provides an interface to search and sort all content on your site.</li>
22     <li>The configure settings tab has basic settings for content on your site.</li>
23     <li>The configure content types tab lists all content types for your site and lets you configure their default workflow.</li>
24     <li>The search tab lets you search all content on your site</li>
25     </ul>
26     ');
27     $output .= t('<p>You can</p>
28     <ul>
29     <li>search for content at <a href="%search">search</a>.</li>
30 dries 1.593 <li>administer nodes at <a href="%admin-settings-content-types">administer &gt;&gt; settings &gt;&gt; content types</a>.</li>
31 dries 1.544 </ul>
32 dries 1.593 ', array('%search' => url('search'), '%admin-settings-content-types' => url('admin/settings/content-types')));
33 dries 1.606 $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>';
34 dries 1.362 return $output;
35 dries 1.355 case 'admin/modules#description':
36 dries 1.484 return t('Allows content to be submitted to the site and displayed on pages.');
37 dries 1.359 case 'admin/node/configure':
38     case 'admin/node/configure/settings':
39 dries 1.424 return t('<p>Settings for the core of Drupal. Almost everything is a node so these settings will affect most of the site.</p>');
40 dries 1.248 case 'admin/node':
41 unconed 1.457 return t('<p>Below is a list of all of the posts on your site. Other forms of content are listed elsewhere (e.g. <a href="%comments">comments</a>).</p><p>Clicking a title views the post, while clicking an author\'s name views their user information.</p>', array('%comments' => url('admin/comment')));
42 dries 1.248 case 'admin/node/search':
43 dries 1.424 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>');
44 dries 1.426 }
45    
46 killes 1.641.2.17 if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == 'revisions' && !arg(3)) {
47 dries 1.426 return t('The revisions let you track differences between multiple versions of a post.');
48 dries 1.28 }
49 dries 1.451
50     if (arg(0) == 'node' && arg(1) == 'add' && $type = arg(2)) {
51 unconed 1.626 return filter_xss_admin(variable_get($type .'_help', ''));
52 dries 1.451 }
53 kjartan 1.138 }
54    
55 dries 1.362 /**
56     * Implementation of hook_cron().
57     */
58 kjartan 1.321 function node_cron() {
59 dries 1.324 db_query('DELETE FROM {history} WHERE timestamp < %d', NODE_NEW_LIMIT);
60 dries 1.293 }
61    
62 dries 1.362 /**
63     * Gather a listing of links to nodes.
64     *
65     * @param $result
66 dries 1.418 * 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.
67 dries 1.362 * @param $title
68     * A heading for the resulting list.
69     *
70     * @return
71     * An HTML list suitable as content for a block.
72     */
73 dries 1.155 function node_title_list($result, $title = NULL) {
74     while ($node = db_fetch_object($result)) {
75 dries 1.417 $items[] = l($node->title, 'node/'. $node->nid, $node->comment_count ? array('title' => format_plural($node->comment_count, '1 comment', '%count comments')) : '');
76 dries 1.155 }
77 dries 1.158
78 dries 1.324 return theme('node_list', $items, $title);
79 dries 1.206 }
80    
81 dries 1.362 /**
82     * Format a listing of links to nodes.
83     */
84 dries 1.207 function theme_node_list($items, $title = NULL) {
85 dries 1.324 return theme('item_list', $items, $title);
86 dries 1.102 }
87    
88 dries 1.362 /**
89     * Update the 'last viewed' timestamp of the specified node for current user.
90     */
91 dries 1.198 function node_tag_new($nid) {
92     global $user;
93    
94     if ($user->uid) {
95 dries 1.369 if (node_last_viewed($nid)) {
96 dries 1.324 db_query('UPDATE {history} SET timestamp = %d WHERE uid = %d AND nid = %d', time(), $user->uid, $nid);
97 dries 1.198 }
98     else {
99 dries 1.406 @db_query('INSERT INTO {history} (uid, nid, timestamp) VALUES (%d, %d, %d)', $user->uid, $nid, time());
100 dries 1.198 }
101     }
102     }
103    
104 dries 1.362 /**
105     * Retrieves the timestamp at which the current user last viewed the
106     * specified node.
107     */
108 dries 1.198 function node_last_viewed($nid) {
109     global $user;
110 dries 1.369 static $history;
111 dries 1.198
112 dries 1.369 if (!isset($history[$nid])) {
113     $history[$nid] = db_fetch_object(db_query("SELECT timestamp FROM {history} WHERE uid = '$user->uid' AND nid = %d", $nid));
114     }
115    
116 dries 1.538 return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp : 0);
117 dries 1.198 }
118    
119     /**
120 dries 1.454 * Decide on the type of marker to be displayed for a given node.
121 dries 1.198 *
122 dries 1.362 * @param $nid
123     * Node ID whose history supplies the "last viewed" timestamp.
124     * @param $timestamp
125     * Time which is compared against node's "last viewed" timestamp.
126 dries 1.454 * @return
127     * One of the MARK constants.
128 dries 1.299 */
129 dries 1.454 function node_mark($nid, $timestamp) {
130 dries 1.198 global $user;
131     static $cache;
132    
133 dries 1.454 if (!$user->uid) {
134     return MARK_READ;
135     }
136 dries 1.202 if (!isset($cache[$nid])) {
137 dries 1.454 $cache[$nid] = node_last_viewed($nid);
138     }
139     if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) {
140     return MARK_NEW;
141     }
142     elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) {
143     return MARK_UPDATED;
144 dries 1.198 }
145 dries 1.454 return MARK_READ;
146 dries 1.198 }
147    
148 dries 1.362 /**
149 unconed 1.494 * Automatically generate a teaser for a node body in a given format.
150 dries 1.362 */
151 unconed 1.494 function node_teaser($body, $format = NULL) {
152 dries 1.102
153 dries 1.324 $size = variable_get('teaser_length', 600);
154 dries 1.160
155 dries 1.373 // find where the delimiter is in the body
156     $delimiter = strpos($body, '<!--break-->');
157    
158 unconed 1.494 // If the size is zero, and there is no delimiter, the entire body is the teaser.
159 drumm 1.615 if ($size == 0 && $delimiter === FALSE) {
160 dries 1.160 return $body;
161     }
162 dries 1.102
163 killes 1.641.2.24 // If a valid delimiter has been specified, use it to chop off the teaser.
164     if ($delimiter !== FALSE) {
165     return substr($body, 0, $delimiter);
166     }
167    
168 unconed 1.494 // We check for the presence of the PHP evaluator filter in the current
169     // format. If the body contains PHP code, we do not split it up to prevent
170     // parse errors.
171     if (isset($format)) {
172     $filters = filter_list_format($format);
173 killes 1.641.2.24 if (isset($filters['filter/1']) && strpos($body, '<?') !== FALSE) {
174 unconed 1.494 return $body;
175     }
176 unconed 1.377 }
177    
178 unconed 1.494 // If we have a short body, the entire body is the teaser.
179 dries 1.238 if (strlen($body) < $size) {
180     return $body;
181     }
182    
183 dries 1.362 // In some cases, no delimiter has been specified (e.g. when posting using
184     // the Blogger API). In this case, we try to split at paragraph boundaries.
185 unconed 1.494 // When even the first paragraph is too long, we try to split at the end of
186 dries 1.362 // the next sentence.
187 unconed 1.639 $breakpoints = array('</p>' => 4, '<br />' => 0, '<br>' => 0, "\n" => 0, '. ' => 1, '! ' => 1, '? ' => 1, '。' => 3, '؟ ' => 1);
188 unconed 1.494 foreach ($breakpoints as $point => $charnum) {
189     if ($length = strpos($body, $point, $size)) {
190     return substr($body, 0, $length + $charnum);
191     }
192 dries 1.160 }
193 dries 1.496
194 unconed 1.494 // If all else fails, we simply truncate the string.
195 unconed 1.346 return truncate_utf8($body, $size);
196 dries 1.102 }
197    
198 dries 1.524 function _node_names($op = '', $node = NULL) {
199 dries 1.563 static $node_names = array();
200     static $node_list = array();
201 dries 1.524
202 dries 1.563 if (empty($node_names)) {
203 dries 1.525 $node_names = module_invoke_all('node_info');
204 dries 1.524 foreach ($node_names as $type => $value) {
205     $node_list[$type] = $value['name'];
206     }
207     }
208     if ($node) {
209     if (is_array($node)) {
210     $type = $node['type'];
211     }
212     elseif (is_object($node)) {
213     $type = $node->type;
214     }
215     elseif (is_string($node)) {
216     $type = $node;
217     }
218     if (!isset($node_names[$type])) {
219     return FALSE;
220     }
221     }
222     switch ($op) {
223     case 'base':
224     return $node_names[$type]['base'];
225     case 'list':
226     return $node_list;
227     case 'name':
228     return $node_list[$type];
229     }
230     }
231    
232 kjartan 1.327 /**
233 dries 1.524 * Determine the basename for hook_load etc.
234 dries 1.318 *
235 dries 1.524 * @param $node
236 dries 1.362 * Either a node object, a node array, or a string containing the node type.
237 dries 1.318 * @return
238 dries 1.524 * The basename for hook_load, hook_nodeapi etc.
239 dries 1.318 */
240 dries 1.524 function node_get_base($node) {
241     return _node_names('base', $node);
242     }
243 dries 1.505
244 dries 1.524 /**
245     * Determine the human readable name for a given type.
246     *
247     * @param $node
248     * Either a node object, a node array, or a string containing the node type.
249     * @return
250     * The human readable name of the node type.
251     */
252     function node_get_name($node) {
253     return _node_names('name', $node);
254 dries 1.320 }
255 dries 1.318
256 kjartan 1.327 /**
257 dries 1.524 * Return the list of available node types.
258 dries 1.318 *
259     * @return
260 dries 1.535 * An array consisting ('#type' => name) pairs.
261 dries 1.318 */
262 dries 1.524 function node_get_types() {
263     return _node_names('list');
264 dries 1.320 }
265 dries 1.318
266 kjartan 1.327 /**
267 dries 1.318 * Determine whether a node hook exists.
268     *
269     * @param &$node
270     * Either a node object, node array, or a string containing the node type.
271     * @param $hook
272     * A string containing the name of the hook.
273     * @return
274     * TRUE iff the $hook exists in the node type of $node.
275     */
276     function node_hook(&$node, $hook) {
277 dries 1.524 return module_hook(node_get_base($node), $hook);
278 dries 1.318 }
279    
280 kjartan 1.327 /**
281 dries 1.318 * Invoke a node hook.
282     *
283     * @param &$node
284     * Either a node object, node array, or a string containing the node type.
285     * @param $hook
286     * A string containing the name of the hook.
287     * @param $a2, $a3, $a4
288     * Arguments to pass on to the hook, after the $node argument.
289     * @return
290 dries 1.362 * The returned value of the invoked hook.
291 dries 1.318 */
292     function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
293 dries 1.524 if (node_hook($node, $hook)) {
294     $function = node_get_base($node) ."_$hook";
295 dries 1.258 return ($function($node, $a2, $a3, $a4));
296 dries 1.102 }
297     }
298    
299 dries 1.362 /**
300     * Invoke a hook_nodeapi() operation in all modules.
301     *
302     * @param &$node
303 dries 1.476 * A node object.
304 dries 1.362 * @param $op
305     * A string containing the name of the nodeapi operation.
306     * @param $a3, $a4
307     * Arguments to pass on to the hook, after the $node and $op arguments.
308     * @return
309     * The returned value of the invoked hooks.
310     */
311 dries 1.350 function node_invoke_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
312 dries 1.264 $return = array();
313 dries 1.524 foreach (module_implements('nodeapi') as $name) {
314 dries 1.324 $function = $name .'_nodeapi';
315 dries 1.524 $result = $function($node, $op, $a3, $a4);
316 dries 1.565 if (isset($result) && is_array($result)) {
317 dries 1.524 $return = array_merge($return, $result);
318     }
319     else if (isset($result)) {
320     $return[] = $result;
321 dries 1.264 }
322     }
323     return $return;
324     }
325    
326 dries 1.362 /**
327     * Load a node object from the database.
328     *
329 dries 1.504 * @param $param
330     * Either the nid of the node or an array of conditions to match against in the database query
331 dries 1.362 * @param $revision
332     * Which numbered revision to load. Defaults to the current version.
333 dries 1.420 * @param $reset
334     * Whether to reset the internal node_load cache.
335 dries 1.362 *
336     * @return
337     * A fully-populated node object.
338     */
339 dries 1.504 function node_load($param = array(), $revision = NULL, $reset = NULL) {
340 dries 1.420 static $nodes = array();
341    
342     if ($reset) {
343     $nodes = array();
344     }
345    
346 killes 1.641.2.27 $cachable = ($revision == NULL);
347 unconed 1.579 $arguments = array();
348 dries 1.504 if (is_numeric($param)) {
349 dries 1.538 if ($cachable && isset($nodes[$param])) {
350 killes 1.641.2.23 return is_object($nodes[$param]) ? drupal_clone($nodes[$param]) : $nodes[$param];
351 dries 1.504 }
352 unconed 1.579 $cond = 'n.nid = %d';
353     $arguments[] = $param;
354 dries 1.420 }
355 dries 1.504 else {
356     // Turn the conditions into a query.
357 unconed 1.508 foreach ($param as $key => $value) {
358 unconed 1.579 $cond[] = 'n.'. db_escape_string($key) ." = '%s'";
359     $arguments[] = $value;
360 dries 1.504 }
361     $cond = implode(' AND ', $cond);
362 dries 1.102 }
363    
364 dries 1.362 // Retrieve the node.
365 killes 1.641.2.11 // No db_rewrite_sql is applied so as to get complete indexing for search.
366 dries 1.526 if ($revision) {
367 unconed 1.579 array_unshift($arguments, $revision);
368 killes 1.641.2.11 $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));
369 dries 1.526 }
370     else {
371 killes 1.641.2.11 $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));
372 dries 1.102 }
373    
374 dries 1.526 if ($node->nid) {
375     // Call the node specific callback (if any) and piggy-back the
376     // results to the node or overwrite some values.
377     if ($extra = node_invoke($node, 'load')) {
378     foreach ($extra as $key => $value) {
379     $node->$key = $value;
380     }
381 dries 1.102 }
382    
383 dries 1.526 if ($extra = node_invoke_nodeapi($node, 'load')) {
384     foreach ($extra as $key => $value) {
385     $node->$key = $value;
386     }
387 dries 1.350 }
388 killes 1.641.2.27 if ($cachable) {
389     $nodes[$node->nid] = is_object($node) ? drupal_clone($node) : $node;
390     }
391 dries 1.220 }
392    
393 dries 1.102 return $node;
394     }
395    
396 dries 1.362 /**
397     * Save a node object into the database.
398     */
399 unconed 1.527 function node_save(&$node) {
400 dries 1.526 global $user;
401 dries 1.102
402 dries 1.526 $node->is_new = false;
403 dries 1.102
404 dries 1.362 // Apply filters to some default node fields:
405 dries 1.102 if (empty($node->nid)) {
406 dries 1.362 // Insert a new node.
407 dries 1.526 $node->is_new = true;
408 dries 1.102
409 dries 1.526 $node->nid = db_next_id('{node}_nid');
410     $node->vid = db_next_id('{node_revisions}_vid');;
411     }
412     else {
413     // We need to ensure that all node fields are filled.
414     $node_current = node_load($node->nid);
415     foreach ($node as $field => $data) {
416     $node_current->$field = $data;
417     }
418     $node = $node_current;
419    
420     if ($node->revision) {
421     $node->old_vid = $node->vid;
422     $node->vid = db_next_id('{node_revisions}_vid');
423 dries 1.349 }
424 dries 1.526 }
425    
426 unconed 1.629 // Set some required fields:
427     if (empty($node->created)) {
428     $node->created = time();
429     }
430 unconed 1.569 // The changed timestamp is always updated for bookkeeping purposes (revisions, searching, ...)
431 unconed 1.629 $node->changed = time();
432 dries 1.102
433 dries 1.526 // Split off revisions data to another structure
434     $revisions_table_values = array('nid' => $node->nid, 'vid' => $node->vid,
435     'title' => $node->title, 'body' => $node->body,
436     'teaser' => $node->teaser, 'log' => $node->log, 'timestamp' => $node->changed,
437     'uid' => $user->uid, 'format' => $node->format);
438     $revisions_table_types = array('nid' => '%d', 'vid' => '%d',
439     'title' => "'%s'", 'body' => "'%s'",
440     'teaser' => "'%s'", 'log' => "'%s'", 'timestamp' => '%d',
441     'uid' => '%d', 'format' => '%d');
442     $node_table_values = array('nid' => $node->nid, 'vid' => $node->vid,
443     'title' => $node->title, 'type' => $node->type, 'uid' => $node->uid,
444     'status' => $node->status, 'created' => $node->created,
445     'changed' => $node->changed, 'comment' => $node->comment,
446     'promote' => $node->promote, 'moderate' => $node->moderate,
447     'sticky' => $node->sticky);
448     $node_table_types = array('nid' => '%d', 'vid' => '%d',
449     'title' => "'%s'", 'type' => "'%s'", 'uid' => '%d',
450     'status' => '%d', 'created' => '%d',
451     'changed' => '%d', 'comment' => '%d',
452     'promote' => '%d', 'moderate' => '%d',
453     'sticky' => '%d');
454    
455     //Generate the node table query and the
456     //the node_revisions table query
457     if ($node->is_new) {
458     $node_query = 'INSERT INTO {node} ('. implode(', ', array_keys($node_table_types)) .') VALUES ('. implode(', ', $node_table_types) .')';
459     $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')';
460     }
461     else {
462     $arr = array();
463     foreach ($node_table_types as $key => $value) {
464     $arr[] = $key .' = '. $value;
465     }
466     $node_table_values[] = $node->nid;
467     $node_query = 'UPDATE {node} SET '. implode(', ', $arr) .' WHERE nid = %d';
468     if ($node->revision) {
469     $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')';
470     }
471     else {
472     $arr = array();
473     foreach ($revisions_table_types as $key => $value) {
474     $arr[] = $key .' = '. $value;
475 dries 1.102 }
476 dries 1.526 $revisions_table_values[] = $node->vid;
477     $revisions_query = 'UPDATE {node_revisions} SET '. implode(', ', $arr) .' WHERE vid = %d';
478 dries 1.102 }
479 dries 1.526 }
480 dries 1.102
481 dries 1.526 // Insert the node into the database:
482     db_query($node_query, $node_table_values);
483     db_query($revisions_query, $revisions_table_values);
484 dries 1.102
485 dries 1.526 // Call the node specific callback (if any):
486     if ($node->is_new) {
487 dries 1.324 node_invoke($node, 'insert');
488     node_invoke_nodeapi($node, 'insert');
489 dries 1.102 }
490     else {
491 dries 1.324 node_invoke($node, 'update');
492     node_invoke_nodeapi($node, 'update');
493 dries 1.102 }
494 dries 1.156
495 dries 1.362 // Clear the cache so an anonymous poster can see the node being added or updated.
496 dries 1.156 cache_clear_all();
497 dries 1.102 }
498    
499 dries 1.362 /**
500     * Generate a display of the given node.
501     *
502     * @param $node
503     * A node array or node object.
504     * @param $teaser
505 dries 1.517 * Whether to display the teaser only, as on the main page.
506 dries 1.362 * @param $page
507     * Whether the node is being displayed by itself as a page.
508 dries 1.425 * @param $links
509     * Whether or not to display node links. Links are omitted for node previews.
510 dries 1.362 *
511     * @return
512     * An HTML representation of the themed node.
513     */
514 dries 1.425 function node_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) {
515 dries 1.576 $node = (object)$node;
516 dries 1.102
517 dries 1.411 // Remove the delimiter (if any) that separates the teaser from the body.
518 dries 1.362 // TODO: this strips legitimate uses of '<!--break-->' also.
519 dries 1.324 $node->body = str_replace('<!--break-->', '', $node->body);
520 dries 1.160
521 dries 1.526 if ($node->log != '' && !$teaser && $node->moderate) {
522 dries 1.585 $node->body .= '<div class="log"><div class="title">'. t('Log') .':</div>'. filter_xss($node->log) .'</div>';
523 dries 1.526 }
524    
525 dries 1.362 // The 'view' hook can be implemented to overwrite the default function
526     // to display nodes.
527 dries 1.324 if (node_hook($node, 'view')) {
528 dries 1.370 node_invoke($node, 'view', $teaser, $page);
529 dries 1.102 }
530     else {
531 dries 1.370 $node = node_prepare($node, $teaser);
532 dries 1.309 }
533 dries 1.370 // Allow modules to change $node->body before viewing.
534     node_invoke_nodeapi($node, 'view', $teaser, $page);
535 dries 1.425 if ($links) {
536     $node->links = module_invoke_all('link', 'node', $node, !$page);
537     }
538 dries 1.501 // unset unused $node part so that a bad theme can not open a security hole
539     if ($teaser) {
540     unset($node->body);
541     }
542     else {
543     unset($node->teaser);
544     }
545 dries 1.370
546     return theme('node', $node, $teaser, $page);
547 dries 1.309 }
548 dries 1.102
549 dries 1.362 /**
550     * Apply filters to a node in preparation for theming.
551     */
552 dries 1.355 function node_prepare($node, $teaser = FALSE) {
553 dries 1.341 $node->readmore = (strlen($node->teaser) < strlen($node->body));
554 dries 1.355 if ($teaser == FALSE) {
555 unconed 1.512 $node->body = check_markup($node->body, $node->format, FALSE);
556 dries 1.309 }
557     else {
558 unconed 1.512 $node->teaser = check_markup($node->teaser, $node->format, FALSE);
559 dries 1.259 }
560 dries 1.309 return $node;
561 dries 1.102 }
562    
563 dries 1.362 /**
564     * Generate a page displaying a single node, along with its comments.
565     */
566 dries 1.219 function node_show($node, $cid) {
567 dries 1.371 $output = node_view($node, FALSE, TRUE);
568 dries 1.174
569 dries 1.371 if (function_exists('comment_render') && $node->comment) {
570     $output .= comment_render($node, $cid);
571 dries 1.300 }
572 dries 1.174
573 dries 1.371 // Update the history table, stating that this user viewed this node.
574     node_tag_new($node->nid);
575 dries 1.76
576 dries 1.371 return $output;
577 dries 1.74 }
578    
579 dries 1.362 /**
580     * Implementation of hook_perm().
581     */
582 dries 1.39 function node_perm() {
583 dries 1.584 return array('administer nodes', 'access content', 'view revisions', 'revert revisions');
584 dries 1.39 }
585    
586 dries 1.362 /**
587     * Implementation of hook_search().
588     */
589 dries 1.413 function node_search($op = 'search', $keys = null) {
590     switch ($op) {
591     case 'name':
592     return t('content');
593 unconed 1.537
594 dries 1.415 case 'reset':
595     variable_del('node_cron_last');
596 unconed 1.588 variable_del('node_cron_last_nid');
597 dries 1.415 return;
598 unconed 1.537
599 unconed 1.439 case 'status':
600     $last = variable_get('node_cron_last', 0);
601 unconed 1.588 $last_nid = variable_get('node_cron_last_nid', 0);
602 dries 1.594 $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1'));
603     $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));
604 unconed 1.439 return array('remaining' => $remaining, 'total' => $total);
605 unconed 1.537
606     case 'admin':
607     $form = array();
608     // Output form for defining rank factor weights.
609     $form['content_ranking'] = array('#type' => 'fieldset', '#title' => t('Content ranking'));
610     $form['content_ranking']['#theme'] = 'node_search_admin';
611 dries 1.582 $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>');
612 unconed 1.537
613     $ranking = array('node_rank_relevance' => t('Keyword relevance'),
614     'node_rank_recent' => t('Recently posted'));
615     if (module_exist('comment')) {
616     $ranking['node_rank_comments'] = t('Number of comments');
617     }
618     if (module_exist('statistics') && variable_get('statistics_count_content_views', 0)) {
619     $ranking['node_rank_views'] = t('Number of views');
620     }
621    
622     // Note: reversed to reflect that higher number = higher ranking.
623     $options = drupal_map_assoc(range(0, 10));
624     foreach ($ranking as $var => $title) {
625     $form['content_ranking']['factors'][$var] = array('#title' => $title, '#type' => 'select', '#options' => $options, '#default_value' => variable_get($var, 5));
626     }
627     return $form;
628    
629 dries 1.413 case 'search':
630 unconed 1.537 // Build matching conditions
631     list($join1, $where1) = _db_rewrite_sql();
632     $arguments1 = array();
633     $conditions1 = 'n.status = 1';
634    
635     if ($type = search_query_extract($keys, 'type')) {
636     $types = array();
637     foreach (explode(',', $type) as $t) {
638     $types[] = "n.type = '%s'";
639     $arguments1[] = $t;
640     }
641     $conditions1 .= ' AND ('. implode(' OR ', $types) .')';
642     $keys = search_query_insert($keys, 'type');
643     }
644    
645     if ($category = search_query_extract($keys, 'category')) {
646     $categories = array();
647     foreach (explode(',', $category) as $c) {
648     $categories[] = "tn.tid = %d";
649     $arguments1[] = $c;
650     }
651     $conditions1 .= ' AND ('. implode(' OR ', $categories) .')';
652     $join1 .= ' INNER JOIN {term_node} tn ON n.nid = tn.nid';
653     $keys = search_query_insert($keys, 'category');
654     }
655    
656     // Build ranking expression (we try to map each parameter to a
657     // uniform distribution in the range 0..1).
658     $ranking = array();
659     $arguments2 = array();
660     $join2 = '';
661 killes 1.641.2.25 $total = 0;
662 unconed 1.537 // Used to avoid joining on node_comment_statistics twice
663     $stats_join = false;
664     if ($weight = (int)variable_get('node_rank_relevance', 5)) {
665     // Average relevance values hover around 0.15
666     $ranking[] = '%d * i.relevance';
667     $arguments2[] = $weight;
668 killes 1.641.2.25 $total += $weight;
669 unconed 1.537 }
670     if ($weight = (int)variable_get('node_rank_recent', 5)) {
671     // Exponential decay with half-life of 6 months, starting at last indexed node
672     $ranking[] = '%d * POW(2, (GREATEST(n.created, n.changed, c.last_comment_timestamp) - %d) * 6.43e-8)';
673     $arguments2[] = $weight;
674     $arguments2[] = (int)variable_get('node_cron_last', 0);
675     $join2 .= ' INNER JOIN {node} n ON n.nid = i.sid LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
676     $stats_join = true;
677 killes 1.641.2.25 $total += $weight;
678 unconed 1.537 }
679     if (module_exist('comment') && $weight = (int)variable_get('node_rank_comments', 5)) {
680     // Inverse law that maps the highest reply count on the site to 1 and 0 to 0.
681     $scale = variable_get('node_cron_comments_scale', 0.0);
682     $ranking[] = '%d * (2.0 - 2.0 / (1.0 + c.comment_count * %f))';
683     $arguments2[] = $weight;
684     $arguments2[] = $scale;
685     if (!$stats_join) {
686     $join2 .= ' LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
687     }
688 killes 1.641.2.25 $total += $weight;
689 unconed 1.537 }
690     if (module_exist('statistics') && variable_get('statistics_count_content_views', 0) &&
691     $weight = (int)variable_get('node_rank_views', 5)) {
692     // Inverse law that maps the highest view count on the site to 1 and 0 to 0.
693     $scale = variable_get('node_cron_views_scale', 0.0);
694     $ranking[] = '%d * (2.0 - 2.0 / (1.0 + nc.totalcount * %f))';
695     $arguments2[] = $weight;
696     $arguments2[] = $scale;
697 unconed 1.638 $join2 .= ' LEFT JOIN {node_counter} nc ON nc.nid = i.sid';
698 killes 1.641.2.25 $total += $weight;
699 unconed 1.537 }
700     $select2 = (count($ranking) ? implode(' + ', $ranking) : 'i.relevance') . ' AS score';
701    
702     // Do search
703     $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);
704    
705     // Load results
706 dries 1.413 $results = array();
707     foreach ($find as $item) {
708 unconed 1.609 $node = node_load($item->sid);
709 unconed 1.510
710     // Get node output (filtered and with module-specific fields).
711     if (node_hook($node, 'view')) {
712     node_invoke($node, 'view', false, false);
713     }
714     else {
715     $node = node_prepare($node, false);
716     }
717     // Allow modules to change $node->body before viewing.
718     node_invoke_nodeapi($node, 'view', false, false);
719    
720 unconed 1.537 // Fetch comments for snippet
721     $node->body .= module_invoke('comment', 'nodeapi', $node, 'update index');
722 unconed 1.641 // Fetch terms for snippet
723     $node->body .= module_invoke('taxonomy', 'nodeapi', $node, 'update index');
724 unconed 1.537
725 dries 1.436 $extra = node_invoke_nodeapi($node, 'search result');
726 unconed 1.609 $results[] = array('link' => url('node/'. $item->sid),
727 dries 1.524 'type' => node_get_name($node),
728 dries 1.413 'title' => $node->title,
729 dries 1.514 'user' => theme('username', $node),
730 dries 1.413 'date' => $node->changed,
731 unconed 1.537 'node' => $node,
732 dries 1.436 'extra' => $extra,
733 killes 1.641.2.25 'score' => $item->score / $total,
734 unconed 1.511 'snippet' => search_excerpt($keys, $node->body));
735 dries 1.413 }
736     return $results;
737     }
738 dries 1.73 }
739    
740 killes 1.622 /**
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 unconed 1.537 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 dries 1.355 /**
768 dries 1.362 * Menu callback; presents general node configuration options.
769 dries 1.355 */
770     function node_configure() {
771    
772 dries 1.532 $form['default_nodes_main'] = array(
773 dries 1.535 '#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 dries 1.532 );
777    
778     $form['teaser_length'] = array(
779 dries 1.535 '#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 dries 1.532 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 dries 1.535 '#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 dries 1.532 );
785    
786     $form['node_preview'] = array(
787 dries 1.535 '#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 dries 1.532 );
790 dries 1.237
791 dries 1.532 return system_settings_form('node_configure', $form);
792 dries 1.37 }
793    
794 dries 1.362 /**
795     * Retrieve the comment mode for the given node ID (none, read, or read/write).
796     */
797 dries 1.120 function node_comment_mode($nid) {
798 dries 1.142 static $comment_mode;
799     if (!isset($comment_mode[$nid])) {
800 dries 1.324 $comment_mode[$nid] = db_result(db_query('SELECT comment FROM {node} WHERE nid = %d', $nid));
801 dries 1.142 }
802     return $comment_mode[$nid];
803 dries 1.120 }
804    
805 dries 1.348 /**
806     * Implementation of hook_link().
807     */
808 kjartan 1.89 function node_link($type, $node = 0, $main = 0) {
809 dries 1.205 $links = array();
810    
811 dries 1.324 if ($type == 'node') {
812 dries 1.341 if ($main == 1 && $node->teaser && $node->readmore) {
813 dries 1.355 $links[] = l(t('read more'), "node/$node->nid", array('title' => t('Read the rest of this posting.'),