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