/[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.31 - (hide annotations) (download) (as text)
Thu Jan 4 20:50:02 2007 UTC (2 years, 10 months ago) by killes
Branch: DRUPAL-4-7
CVS Tags: DRUPAL-4-7-5
Changes since 1.641.2.30: +3 -1 lines
File MIME type: text/x-php
#106604, no user pictures in previews, patch by prahlad
1 dries 1.1 <?php
2 killes 1.641.2.31 // $Id: node.module,v 1.641.2.30 2007/01/04 19:50:28 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 killes 1.641.2.30 // The teaser may not be longer than maximum length specified. Initial slice.
184     $teaser = truncate_utf8($body, $size);
185     $position = 0;
186     // Cache the reverse of the teaser.
187     $reversed = strrev($teaser);
188    
189     // In some cases, no delimiter has been specified. In this case, we try to
190     // split at paragraph boundaries.
191     $breakpoints = array('</p>' => 0, '<br />' => 6, '<br>' => 4, "\n" => 1);
192     // We use strpos on the reversed needle and haystack for speed.
193     foreach ($breakpoints as $point => $offset) {
194     $length = strpos($reversed, strrev($point));
195     if ($length !== FALSE) {
196     $position = - $length - $offset;
197     return ($position == 0) ? $teaser : substr($teaser, 0, $position);
198 unconed 1.494 }
199 dries 1.160 }
200 dries 1.496
201 killes 1.641.2.30 // When even the first paragraph is too long, we try to split at the end of
202     // the last full sentence.
203     $breakpoints = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, 'ØŸ ' => 1);
204     $min_length = strlen($reversed);
205     foreach ($breakpoints as $point => $offset) {
206     $length = strpos($reversed, strrev($point));
207     if ($length !== FALSE) {
208     $min_length = min($length, $min_length);
209     $position = 0 - $length - $offset;
210     }
211     }
212     return ($position == 0) ? $teaser : substr($teaser, 0, $position);
213 dries 1.102 }
214    
215 dries 1.524 function _node_names($op = '', $node = NULL) {
216 dries 1.563 static $node_names = array();
217     static $node_list = array();
218 dries 1.524
219 dries 1.563 if (empty($node_names)) {
220 dries 1.525 $node_names = module_invoke_all('node_info');
221 dries 1.524 foreach ($node_names as $type => $value) {
222     $node_list[$type] = $value['name'];
223     }
224     }
225     if ($node) {
226     if (is_array($node)) {
227     $type = $node['type'];
228     }
229     elseif (is_object($node)) {
230     $type = $node->type;
231     }
232     elseif (is_string($node)) {
233     $type = $node;
234     }
235     if (!isset($node_names[$type])) {
236     return FALSE;
237     }
238     }
239     switch ($op) {
240     case 'base':
241     return $node_names[$type]['base'];
242     case 'list':
243     return $node_list;
244     case 'name':
245     return $node_list[$type];
246     }
247     }
248    
249 kjartan 1.327 /**
250 dries 1.524 * Determine the basename for hook_load etc.
251 dries 1.318 *
252 dries 1.524 * @param $node
253 dries 1.362 * Either a node object, a node array, or a string containing the node type.
254 dries 1.318 * @return
255 dries 1.524 * The basename for hook_load, hook_nodeapi etc.
256 dries 1.318 */
257 dries 1.524 function node_get_base($node) {
258     return _node_names('base', $node);
259     }
260 dries 1.505
261 dries 1.524 /**
262     * Determine the human readable name for a given type.
263     *
264     * @param $node
265     * Either a node object, a node array, or a string containing the node type.
266     * @return
267     * The human readable name of the node type.
268     */
269     function node_get_name($node) {
270     return _node_names('name', $node);
271 dries 1.320 }
272 dries 1.318
273 kjartan 1.327 /**
274 dries 1.524 * Return the list of available node types.
275 dries 1.318 *
276     * @return
277 dries 1.535 * An array consisting ('#type' => name) pairs.
278 dries 1.318 */
279 dries 1.524 function node_get_types() {
280     return _node_names('list');
281 dries 1.320 }
282 dries 1.318
283 kjartan 1.327 /**
284 dries 1.318 * Determine whether a node hook exists.
285     *
286     * @param &$node
287     * Either a node object, node array, or a string containing the node type.
288     * @param $hook
289     * A string containing the name of the hook.
290     * @return
291     * TRUE iff the $hook exists in the node type of $node.
292     */
293     function node_hook(&$node, $hook) {
294 dries 1.524 return module_hook(node_get_base($node), $hook);
295 dries 1.318 }
296    
297 kjartan 1.327 /**
298 dries 1.318 * Invoke a node hook.
299     *
300     * @param &$node
301     * Either a node object, node array, or a string containing the node type.
302     * @param $hook
303     * A string containing the name of the hook.
304     * @param $a2, $a3, $a4
305     * Arguments to pass on to the hook, after the $node argument.
306     * @return
307 dries 1.362 * The returned value of the invoked hook.
308 dries 1.318 */
309     function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
310 dries 1.524 if (node_hook($node, $hook)) {
311     $function = node_get_base($node) ."_$hook";
312 dries 1.258 return ($function($node, $a2, $a3, $a4));
313 dries 1.102 }
314     }
315    
316 dries 1.362 /**
317     * Invoke a hook_nodeapi() operation in all modules.
318     *
319     * @param &$node
320 dries 1.476 * A node object.
321 dries 1.362 * @param $op
322     * A string containing the name of the nodeapi operation.
323     * @param $a3, $a4
324     * Arguments to pass on to the hook, after the $node and $op arguments.
325     * @return
326     * The returned value of the invoked hooks.
327     */
328 dries 1.350 function node_invoke_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
329 dries 1.264 $return = array();
330 dries 1.524 foreach (module_implements('nodeapi') as $name) {
331 dries 1.324 $function = $name .'_nodeapi';
332 dries 1.524 $result = $function($node, $op, $a3, $a4);
333 dries 1.565 if (isset($result) && is_array($result)) {
334 dries 1.524 $return = array_merge($return, $result);
335     }
336     else if (isset($result)) {
337     $return[] = $result;
338 dries 1.264 }
339     }
340     return $return;
341     }
342    
343 dries 1.362 /**
344     * Load a node object from the database.
345     *
346 dries 1.504 * @param $param
347     * Either the nid of the node or an array of conditions to match against in the database query
348 dries 1.362 * @param $revision
349     * Which numbered revision to load. Defaults to the current version.
350 dries 1.420 * @param $reset
351     * Whether to reset the internal node_load cache.
352 dries 1.362 *
353     * @return
354     * A fully-populated node object.
355     */
356 dries 1.504 function node_load($param = array(), $revision = NULL, $reset = NULL) {
357 dries 1.420 static $nodes = array();
358    
359     if ($reset) {
360     $nodes = array();
361     }
362    
363 killes 1.641.2.27 $cachable = ($revision == NULL);
364 unconed 1.579 $arguments = array();
365 dries 1.504 if (is_numeric($param)) {
366 dries 1.538 if ($cachable && isset($nodes[$param])) {
367 killes 1.641.2.23 return is_object($nodes[$param]) ? drupal_clone($nodes[$param]) : $nodes[$param];
368 dries 1.504 }
369 unconed 1.579 $cond = 'n.nid = %d';
370     $arguments[] = $param;
371 dries 1.420 }
372 dries 1.504 else {
373     // Turn the conditions into a query.
374 unconed 1.508 foreach ($param as $key => $value) {
375 unconed 1.579 $cond[] = 'n.'. db_escape_string($key) ." = '%s'";
376     $arguments[] = $value;
377 dries 1.504 }
378     $cond = implode(' AND ', $cond);
379 dries 1.102 }
380    
381 dries 1.362 // Retrieve the node.
382 killes 1.641.2.11 // No db_rewrite_sql is applied so as to get complete indexing for search.
383 dries 1.526 if ($revision) {
384 unconed 1.579 array_unshift($arguments, $revision);
385 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));
386 dries 1.526 }
387     else {
388 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));
389 dries 1.102 }
390    
391 dries 1.526 if ($node->nid) {
392     // Call the node specific callback (if any) and piggy-back the
393     // results to the node or overwrite some values.
394     if ($extra = node_invoke($node, 'load')) {
395     foreach ($extra as $key => $value) {
396     $node->$key = $value;
397     }
398 dries 1.102 }
399    
400 dries 1.526 if ($extra = node_invoke_nodeapi($node, 'load')) {
401     foreach ($extra as $key => $value) {
402     $node->$key = $value;
403     }
404 dries 1.350 }
405 killes 1.641.2.27 if ($cachable) {
406     $nodes[$node->nid] = is_object($node) ? drupal_clone($node) : $node;
407     }
408 dries 1.220 }
409    
410 dries 1.102 return $node;
411     }
412    
413 dries 1.362 /**
414     * Save a node object into the database.
415     */
416 unconed 1.527 function node_save(&$node) {
417 dries 1.526 global $user;
418 dries 1.102
419 dries 1.526 $node->is_new = false;
420 dries 1.102
421 dries 1.362 // Apply filters to some default node fields:
422 dries 1.102 if (empty($node->nid)) {
423 dries 1.362 // Insert a new node.
424 dries 1.526 $node->is_new = true;
425 dries 1.102
426 dries 1.526 $node->nid = db_next_id('{node}_nid');
427     $node->vid = db_next_id('{node_revisions}_vid');;
428     }
429     else {
430     // We need to ensure that all node fields are filled.
431     $node_current = node_load($node->nid);
432     foreach ($node as $field => $data) {
433     $node_current->$field = $data;
434     }
435     $node = $node_current;
436    
437     if ($node->revision) {
438     $node->old_vid = $node->vid;
439     $node->vid = db_next_id('{node_revisions}_vid');
440 dries 1.349 }
441 dries 1.526 }
442    
443 unconed 1.629 // Set some required fields:
444     if (empty($node->created)) {
445     $node->created = time();
446     }
447 unconed 1.569 // The changed timestamp is always updated for bookkeeping purposes (revisions, searching, ...)
448 unconed 1.629 $node->changed = time();
449 dries 1.102
450 dries 1.526 // Split off revisions data to another structure
451     $revisions_table_values = array('nid' => $node->nid, 'vid' => $node->vid,
452     'title' => $node->title, 'body' => $node->body,
453     'teaser' => $node->teaser, 'log' => $node->log, 'timestamp' => $node->changed,
454     'uid' => $user->uid, 'format' => $node->format);
455     $revisions_table_types = array('nid' => '%d', 'vid' => '%d',
456     'title' => "'%s'", 'body' => "'%s'",
457     'teaser' => "'%s'", 'log' => "'%s'", 'timestamp' => '%d',
458     'uid' => '%d', 'format' => '%d');
459     $node_table_values = array('nid' => $node->nid, 'vid' => $node->vid,
460     'title' => $node->title, 'type' => $node->type, 'uid' => $node->uid,
461     'status' => $node->status, 'created' => $node->created,
462     'changed' => $node->changed, 'comment' => $node->comment,
463     'promote' => $node->promote, 'moderate' => $node->moderate,
464     'sticky' => $node->sticky);
465     $node_table_types = array('nid' => '%d', 'vid' => '%d',
466     'title' => "'%s'", 'type' => "'%s'", 'uid' => '%d',
467     'status' => '%d', 'created' => '%d',
468     'changed' => '%d', 'comment' => '%d',
469     'promote' => '%d', 'moderate' => '%d',
470     'sticky' => '%d');
471    
472     //Generate the node table query and the
473     //the node_revisions table query
474     if ($node->is_new) {
475     $node_query = 'INSERT INTO {node} ('. implode(', ', array_keys($node_table_types)) .') VALUES ('. implode(', ', $node_table_types) .')';
476     $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')';
477     }
478     else {
479     $arr = array();
480     foreach ($node_table_types as $key => $value) {
481     $arr[] = $key .' = '. $value;
482     }
483     $node_table_values[] = $node->nid;
484     $node_query = 'UPDATE {node} SET '. implode(', ', $arr) .' WHERE nid = %d';
485     if ($node->revision) {
486     $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')';
487     }
488     else {
489     $arr = array();
490     foreach ($revisions_table_types as $key => $value) {
491     $arr[] = $key .' = '. $value;
492 dries 1.102 }
493 dries 1.526 $revisions_table_values[] = $node->vid;
494     $revisions_query = 'UPDATE {node_revisions} SET '. implode(', ', $arr) .' WHERE vid = %d';
495 dries 1.102 }
496 dries 1.526 }
497 dries 1.102
498 dries 1.526 // Insert the node into the database:
499     db_query($node_query, $node_table_values);
500     db_query($revisions_query, $revisions_table_values);
501 dries 1.102
502 dries 1.526 // Call the node specific callback (if any):
503     if ($node->is_new) {
504 dries 1.324 node_invoke($node, 'insert');
505     node_invoke_nodeapi($node, 'insert');
506 dries 1.102 }
507     else {
508 dries 1.324 node_invoke($node, 'update');
509     node_invoke_nodeapi($node, 'update');
510 dries 1.102 }
511 dries 1.156
512 dries 1.362 // Clear the cache so an anonymous poster can see the node being added or updated.
513 dries 1.156 cache_clear_all();
514 dries 1.102 }
515    
516 dries 1.362 /**
517     * Generate a display of the given node.
518     *
519     * @param $node
520     * A node array or node object.
521     * @param $teaser
522 dries 1.517 * Whether to display the teaser only, as on the main page.
523 dries 1.362 * @param $page
524     * Whether the node is being displayed by itself as a page.
525 dries 1.425 * @param $links
526     * Whether or not to display node links. Links are omitted for node previews.
527 dries 1.362 *
528     * @return
529     * An HTML representation of the themed node.
530     */
531 dries 1.425 function node_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) {
532 dries 1.576 $node = (object)$node;
533 dries 1.102
534 dries 1.411 // Remove the delimiter (if any) that separates the teaser from the body.
535 dries 1.362 // TODO: this strips legitimate uses of '<!--break-->' also.
536 dries 1.324 $node->body = str_replace('<!--break-->', '', $node->body);
537 dries 1.160
538 dries 1.526 if ($node->log != '' && !$teaser && $node->moderate) {
539 dries 1.585 $node->body .= '<div class="log"><div class="title">'. t('Log') .':</div>'. filter_xss($node->log) .'</div>';
540 dries 1.526 }
541    
542 dries 1.362 // The 'view' hook can be implemented to overwrite the default function
543     // to display nodes.
544 dries 1.324 if (node_hook($node, 'view')) {
545 dries 1.370 node_invoke($node, 'view', $teaser, $page);
546 dries 1.102 }
547     else {
548 dries 1.370 $node = node_prepare($node, $teaser);
549 dries 1.309 }
550 dries 1.370 // Allow modules to change $node->body before viewing.
551     node_invoke_nodeapi($node, 'view', $teaser, $page);
552 dries 1.425 if ($links) {
553     $node->links = module_invoke_all('link', 'node', $node, !$page);
554     }
555 dries 1.501 // unset unused $node part so that a bad theme can not open a security hole
556     if ($teaser) {
557     unset($node->body);
558     }
559     else {
560     unset($node->teaser);
561     }
562 dries 1.370
563     return theme('node', $node, $teaser, $page);
564 dries 1.309 }
565 dries 1.102
566 dries 1.362 /**
567     * Apply filters to a node in preparation for theming.
568     */
569 dries 1.355 function node_prepare($node, $teaser = FALSE) {
570 dries 1.341 $node->readmore = (strlen($node->teaser) < strlen($node->body));
571 dries 1.355 if ($teaser == FALSE) {
572 unconed 1.512 $node->body = check_markup($node->body, $node->format, FALSE);
573 dries 1.309 }
574     else {
575 unconed 1.512 $node->teaser = check_markup($node->teaser, $node->format, FALSE);
576 dries 1.259 }
577 dries 1.309 return $node;
578 dries 1.102 }
579    
580 dries 1.362 /**
581     * Generate a page displaying a single node, along with its comments.
582     */
583 dries 1.219 function node_show($node, $cid) {
584 dries 1.371 $output = node_view($node, FALSE, TRUE);
585 dries 1.174
586 dries 1.371 if (function_exists('comment_render') && $node->comment) {
587     $output .= comment_render($node, $cid);
588 dries 1.300 }
589 dries 1.174
590 dries 1.371 // Update the history table, stating that this user viewed this node.
591     node_tag_new($node->nid);
592 dries 1.76
593 dries 1.371 return $output;
594 dries 1.74 }
595    
596 dries 1.362 /**
597     * Implementation of hook_perm().
598     */
599 dries 1.39 function node_perm() {
600 dries 1.584 return array('administer nodes', 'access content', 'view revisions', 'revert revisions');
601 dries 1.39 }
602    
603 dries 1.362 /**
604     * Implementation of hook_search().
605     */
606 dries 1.413 function node_search($op = 'search', $keys = null) {
607     switch ($op) {
608     case 'name':
609     return t('content');
610 unconed 1.537
611 dries 1.415 case 'reset':
612     variable_del('node_cron_last');
613 unconed 1.588 variable_del('node_cron_last_nid');
614 dries 1.415 return;
615 unconed 1.537
616 unconed 1.439 case 'status':
617     $last = variable_get('node_cron_last', 0);
618 unconed 1.588 $last_nid = variable_get('node_cron_last_nid', 0);
619 dries 1.594 $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1'));
620     $remaining = db_result(db_query('SELECT COUNT(*) FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND ((GREATEST(n.created, n.changed, c.last_comment_timestamp) = %d AND n.nid > %d ) OR (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d))', $last, $last_nid, $last, $last, $last));
621 unconed 1.439 return array('remaining' => $remaining, 'total' => $total);
622 unconed 1.537
623     case 'admin':
624     $form = array();
625     // Output form for defining rank factor weights.
626     $form['content_ranking'] = array('#type' => 'fieldset', '#title' => t('Content ranking'));
627     $form['content_ranking']['#theme'] = 'node_search_admin';
628 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>');
629 unconed 1.537
630     $ranking = array('node_rank_relevance' => t('Keyword relevance'),
631     'node_rank_recent' => t('Recently posted'));
632     if (module_exist('comment')) {
633     $ranking['node_rank_comments'] = t('Number of comments');
634     }
635     if (module_exist('statistics') && variable_get('statistics_count_content_views', 0)) {
636     $ranking['node_rank_views'] = t('Number of views');
637     }
638    
639     // Note: reversed to reflect that higher number = higher ranking.
640     $options = drupal_map_assoc(range(0, 10));
641     foreach ($ranking as $var => $title) {
642     $form['content_ranking']['factors'][$var] = array('#title' => $title, '#type' => 'select', '#options' => $options, '#default_value' => variable_get($var, 5));
643     }
644     return $form;
645    
646 dries 1.413 case 'search':
647 unconed 1.537 // Build matching conditions
648     list($join1, $where1) = _db_rewrite_sql();
649     $arguments1 = array();
650     $conditions1 = 'n.status = 1';
651    
652     if ($type = search_query_extract($keys, 'type')) {
653     $types = array();
654     foreach (explode(',', $type) as $t) {
655     $types[] = "n.type = '%s'";
656     $arguments1[] = $t;
657     }
658     $conditions1 .= ' AND ('. implode(' OR ', $types) .')';
659     $keys = search_query_insert($keys, 'type');
660     }
661    
662     if ($category = search_query_extract($keys, 'category')) {
663     $categories = array();
664     foreach (explode(',', $category) as $c) {
665     $categories[] = "tn.tid = %d";
666     $arguments1[] = $c;
667     }
668     $conditions1 .= ' AND ('. implode(' OR ', $categories) .')';
669     $join1 .= ' INNER JOIN {term_node} tn ON n.nid = tn.nid';
670     $keys = search_query_insert($keys, 'category');
671     }
672    
673     // Build ranking expression (we try to map each parameter to a
674     // uniform distribution in the range 0..1).
675     $ranking = array();
676     $arguments2 = array();
677     $join2 = '';
678 killes 1.641.2.25 $total = 0;
679 unconed 1.537 // Used to avoid joining on node_comment_statistics twice
680     $stats_join = false;
681     if ($weight = (int)variable_get('node_rank_relevance', 5)) {
682     // Average relevance values hover around 0.15
683     $ranking[] = '%d * i.relevance';
684     $arguments2[] = $weight;
685 killes 1.641.2.25 $total += $weight;
686 unconed 1.537 }
687     if ($weight = (int)variable_get('node_rank_recent', 5)) {
688     // Exponential decay with half-life of 6 months, starting at last indexed node
689     $ranking[] = '%d * POW(2, (GREATEST(n.created, n.changed, c.last_comment_timestamp) - %d) * 6.43e-8)';
690     $arguments2[] = $weight;
691     $arguments2[] = (int)variable_get('node_cron_last', 0);
692     $join2 .= ' INNER JOIN {node} n ON n.nid = i.sid LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
693     $stats_join = true;
694 killes 1.641.2.25 $total += $weight;
695 unconed 1.537 }
696     if (module_exist('comment') && $weight = (int)variable_get('node_rank_comments', 5)) {
697     // Inverse law that maps the highest reply count on the site to 1 and 0 to 0.
698     $scale = variable_get('node_cron_comments_scale', 0.0);
699     $ranking[] = '%d * (2.0 - 2.0 / (1.0 + c.comment_count * %f))';
700     $arguments2[] = $weight;
701     $arguments2[] = $scale;
702     if (!$stats_join) {
703     $join2 .= ' LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
704     }
705 killes 1.641.2.25 $total += $weight;
706 unconed 1.537 }
707     if (module_exist('statistics') && variable_get('statistics_count_content_views', 0) &&
708     $weight = (int)variable_get('node_rank_views', 5)) {
709     // Inverse law that maps the highest view count on the site to 1 and 0 to 0.
710     $scale = variable_get('node_cron_views_scale', 0.0);
711     $ranking[] = '%d * (2.0 - 2.0 / (1.0 + nc.totalcount * %f))';
712     $arguments2[] = $weight;
713     $arguments2[] = $scale;
714 unconed 1.638 $join2 .= ' LEFT JOIN {node_counter} nc ON nc.nid = i.sid';
715 killes 1.641.2.25 $total += $weight;
716 unconed 1.537 }
717     $select2 = (count($ranking) ? implode(' + ', $ranking) : 'i.relevance') . ' AS score';
718    
719     // Do search
720     $find = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid = i.sid '. $join1 .' INNER JOIN {users} u ON n.uid = u.uid', $conditions1 . (empty($where1) ? '' : ' AND '. $where1), $arguments1, $select2, $join2, $arguments2);
721    
722     // Load results
723 dries 1.413 $results = array();
724     foreach ($find as $item) {
725 unconed 1.609 $node = node_load($item->sid);
726 unconed 1.510
727     // Get node output (filtered and with module-specific fields).
728     if (node_hook($node, 'view')) {
729     node_invoke($node, 'view', false, false);
730     }
731     else {
732     $node = node_prepare($node, false);
733     }
734     // Allow modules to change $node->body before viewing.
735     node_invoke_nodeapi($node, 'view', false, false);
736    
737 unconed 1.537 // Fetch comments for snippet
738     $node->body .= module_invoke('comment', 'nodeapi', $node, 'update index');
739 unconed 1.641 // Fetch terms for snippet
740     $node->body .= module_invoke('taxonomy', 'nodeapi', $node, 'update index');
741 unconed 1.537
742 dries 1.436 $extra = node_invoke_nodeapi($node, 'search result');
743 unconed 1.609 $results[] = array('link' => url('node/'. $item->sid),
744 dries 1.524 'type' => node_get_name($node),
745 dries 1.413 'title' => $node->title,
746 dries 1.514 'user' => theme('username', $node),
747 dries 1.413 'date' => $node->changed,
748 unconed 1.537 'node' => $node,
749 dries 1.436 'extra' => $extra,
750 killes 1.641.2.25 'score' => $item->score / $total,
751 unconed 1.511 'snippet' => search_excerpt($keys, $node->body));
752 dries 1.413 }
753     return $results;
754     }
755 dries 1.73 }
756    
757 killes 1.622 /**
758     * Implementation of hook_user().
759     */
760     function node_user($op, &$edit, &$user) {
761     if ($op == 'delete') {
762     db_query('UPDATE {node} SET uid = 0 WHERE uid = %d', $user->uid);
763     db_query('UPDATE {node_revisions} SET uid = 0 WHERE uid = %d', $user->uid);
764     }
765     }
766    
767 unconed 1.537 function theme_node_search_admin($form) {
768     $output = form_render($form['info']);
769    
770     $header = array(t('Factor'), t('Weight'));
771     foreach (element_children($form['factors']) as $key) {
772     $row = array();
773     $row[] = $form['factors'][$key]['#title'];
774     unset($form['factors'][$key]['#title']);
775     $row[] = form_render($form['factors'][$key]);
776     $rows[] = $row;
777     }
778     $output .= theme('table', $header, $rows);
779    
780     $output .= form_render($form);
781     return $output;
782     }
783    
784 dries 1.355 /**
785 dries 1.362 * Menu callback; presents general node configuration options.
786 dries 1.355 */
787     function node_configure() {
788    
789 dries 1.532 $form['default_nodes_main'] = array(
790 dries 1.535 '#type' => 'select', '#title' => t('Number of posts on main page'), '#default_value' => variable_get('default_nodes_main', 10),
791     '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30)),
792     '#description' => t('The default maximum number of posts to display per page on overview pages such as the main page.')
793 dries 1.532 );
794    
795     $form['teaser_length'] = array(
796 dries 1.535 '#type' => 'select', '#title' => t('Length of trimmed posts'), '#default_value' => variable_get('teaser_length', 600),
797     '#options' => array(0 => t('Unlimited'), 200 => t('200 characters'), 400 => t('400 characters'), 600 => t('600 characters'),
798 dries 1.532 800 => t('800 characters'), 1000 => t('1000 characters'), 1200 => t('1200 characters'), 1400 => t('1400 characters'),
799     1600 => t('1600 characters'), 1800 => t('1800 characters'), 2000 => t('2000 characters')),
800 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.")
801 dries 1.532 );
802    
803     $form['node_preview'] = array(
804 dries 1.535 '#type' => 'radios', '#title' => t('Preview post'), '#default_value' => variable_get('node_preview', 0),
805     '#options' => array(t('Optional'), t('Required')), '#description' => t('Must users preview posts before submitting?')
806 dries 1.532 );
807 dries 1.237
808 dries 1.532 return system_settings_form('node_configure', $form);
809 dries 1.37 }
810    
811 dries 1.362 /**
812     * Retrieve the comment mode for the given node ID (none, read, or read/write).
813     */
814 dries 1.120 function node_comment_mode($nid) {
815 dries 1.142 static $comment_mode