/[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.21 - (hide annotations) (download) (as text)
Thu Nov 23 21:47:01 2006 UTC (3 years ago) by killes
Branch: DRUPAL-4-7
Changes since 1.641.2.20: +2 -2 lines
File MIME type: text/x-php
#98686, Nodes not indexed when passing directly from Moderation to Promoted, patch by Robert Douglass
1 dries 1.1 <?php
2 killes 1.641.2.21 // $Id: node.module,v 1.641.2.20 2006/11/14 10:34:08 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 unconed 1.494 // We check for the presence of the PHP evaluator filter in the current
164     // format. If the body contains PHP code, we do not split it up to prevent
165     // parse errors.
166     if (isset($format)) {
167     $filters = filter_list_format($format);
168 killes 1.641.2.16 if (isset($filters['filter/1']) && (strpos($body, '<?') !== FALSE) && (strpos($body, '<?') < $delimiter)) {
169 unconed 1.494 return $body;
170     }
171 unconed 1.377 }
172    
173 dries 1.362 // If a valid delimiter has been specified, use it to chop of the teaser.
174 drumm 1.615 if ($delimiter !== FALSE) {
175 dries 1.160 return substr($body, 0, $delimiter);
176     }
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 unconed 1.579 $arguments = array();
349 dries 1.504 if (is_numeric($param)) {
350     $cachable = $revision == NULL;
351 dries 1.538 if ($cachable && isset($nodes[$param])) {
352 killes 1.641.2.20 return drupal_clone($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     }
391    
392 dries 1.420 if ($cachable) {
393 killes 1.641.2.20 $nodes[$param] = drupal_clone($node);
394 dries 1.220 }
395    
396 dries 1.102 return $node;
397     }
398    
399 dries 1.362 /**
400     * Save a node object into the database.
401     */
402 unconed 1.527 function node_save(&$node) {
403 dries 1.526 global $user;
404 dries 1.102
405 dries 1.526 $node->is_new = false;
406 dries 1.102
407 dries 1.362 // Apply filters to some default node fields:
408 dries 1.102 if (empty($node->nid)) {
409 dries 1.362 // Insert a new node.
410 dries 1.526 $node->is_new = true;
411 dries 1.102
412 dries 1.526 $node->nid = db_next_id('{node}_nid');
413     $node->vid = db_next_id('{node_revisions}_vid');;
414     }
415     else {
416     // We need to ensure that all node fields are filled.
417     $node_current = node_load($node->nid);
418     foreach ($node as $field => $data) {
419     $node_current->$field = $data;
420     }
421     $node = $node_current;
422    
423     if ($node->revision) {
424     $node->old_vid = $node->vid;
425     $node->vid = db_next_id('{node_revisions}_vid');
426 dries 1.349 }
427 dries 1.526 }
428    
429 unconed 1.629 // Set some required fields:
430     if (empty($node->created)) {
431     $node->created = time();
432     }
433 unconed 1.569 // The changed timestamp is always updated for bookkeeping purposes (revisions, searching, ...)
434 unconed 1.629 $node->changed = time();
435 dries 1.102
436 dries 1.526 // Split off revisions data to another structure
437     $revisions_table_values = array('nid' => $node->nid, 'vid' => $node->vid,
438     'title' => $node->title, 'body' => $node->body,
439     'teaser' => $node->teaser, 'log' => $node->log, 'timestamp' => $node->changed,
440     'uid' => $user->uid, 'format' => $node->format);
441     $revisions_table_types = array('nid' => '%d', 'vid' => '%d',
442     'title' => "'%s'", 'body' => "'%s'",
443     'teaser' => "'%s'", 'log' => "'%s'", 'timestamp' => '%d',
444     'uid' => '%d', 'format' => '%d');
445     $node_table_values = array('nid' => $node->nid, 'vid' => $node->vid,
446     'title' => $node->title, 'type' => $node->type, 'uid' => $node->uid,
447     'status' => $node->status, 'created' => $node->created,
448     'changed' => $node->changed, 'comment' => $node->comment,
449     'promote' => $node->promote, 'moderate' => $node->moderate,
450     'sticky' => $node->sticky);
451     $node_table_types = array('nid' => '%d', 'vid' => '%d',
452     'title' => "'%s'", 'type' => "'%s'", 'uid' => '%d',
453     'status' => '%d', 'created' => '%d',
454     'changed' => '%d', 'comment' => '%d',
455     'promote' => '%d', 'moderate' => '%d',
456     'sticky' => '%d');
457    
458     //Generate the node table query and the
459     //the node_revisions table query
460     if ($node->is_new) {
461     $node_query = 'INSERT INTO {node} ('. implode(', ', array_keys($node_table_types)) .') VALUES ('. implode(', ', $node_table_types) .')';
462     $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')';
463     }
464     else {
465     $arr = array();
466     foreach ($node_table_types as $key => $value) {
467     $arr[] = $key .' = '. $value;
468     }
469     $node_table_values[] = $node->nid;
470     $node_query = 'UPDATE {node} SET '. implode(', ', $arr) .' WHERE nid = %d';
471     if ($node->revision) {
472     $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')';
473     }
474     else {
475     $arr = array();
476     foreach ($revisions_table_types as $key => $value) {
477     $arr[] = $key .' = '. $value;
478 dries 1.102 }
479 dries 1.526 $revisions_table_values[] = $node->vid;
480     $revisions_query = 'UPDATE {node_revisions} SET '. implode(', ', $arr) .' WHERE vid = %d';
481 dries 1.102 }
482 dries 1.526 }
483 dries 1.102
484 dries 1.526 // Insert the node into the database:
485     db_query($node_query, $node_table_values);
486     db_query($revisions_query, $revisions_table_values);
487 dries 1.102
488 dries 1.526 // Call the node specific callback (if any):
489     if ($node->is_new) {
490 dries 1.324 node_invoke($node, 'insert');
491     node_invoke_nodeapi($node, 'insert');
492 dries 1.102 }
493     else {
494 dries 1.324 node_invoke($node, 'update');
495     node_invoke_nodeapi($node, 'update');
496 dries 1.102 }
497 dries 1.156
498 dries 1.362 // Clear the cache so an anonymous poster can see the node being added or updated.
499 dries 1.156 cache_clear_all();
500 dries 1.102 }
501    
502 dries 1.362 /**
503     * Generate a display of the given node.
504     *
505     * @param $node
506     * A node array or node object.
507     * @param $teaser
508 dries 1.517 * Whether to display the teaser only, as on the main page.
509 dries 1.362 * @param $page
510     * Whether the node is being displayed by itself as a page.
511 dries 1.425 * @param $links
512     * Whether or not to display node links. Links are omitted for node previews.
513 dries 1.362 *
514     * @return
515     * An HTML representation of the themed node.
516     */
517 dries 1.425 function node_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) {
518 dries 1.576 $node = (object)$node;
519 dries 1.102
520 dries 1.411 // Remove the delimiter (if any) that separates the teaser from the body.
521 dries 1.362 // TODO: this strips legitimate uses of '<!--break-->' also.
522 dries 1.324 $node->body = str_replace('<!--break-->', '', $node->body);
523 dries 1.160
524 dries 1.526 if ($node->log != '' && !$teaser && $node->moderate) {
525 dries 1.585 $node->body .= '<div class="log"><div class="title">'. t('Log') .':</div>'. filter_xss($node->log) .'</div>';
526 dries 1.526 }
527    
528 dries 1.362 // The 'view' hook can be implemented to overwrite the default function
529     // to display nodes.
530 dries 1.324 if (node_hook($node, 'view')) {
531 dries 1.370 node_invoke($node, 'view', $teaser, $page);
532 dries 1.102 }
533     else {
534 dries 1.370 $node = node_prepare($node, $teaser);
535 dries 1.309 }
536 dries 1.370 // Allow modules to change $node->body before viewing.
537     node_invoke_nodeapi($node, 'view', $teaser, $page);
538 dries 1.425 if ($links) {
539     $node->links = module_invoke_all('link', 'node', $node, !$page);
540     }
541 dries 1.501 // unset unused $node part so that a bad theme can not open a security hole
542     if ($teaser) {
543     unset($node->body);
544     }
545     else {
546     unset($node->teaser);
547     }
548 dries 1.370
549     return theme('node', $node, $teaser, $page);
550 dries 1.309 }
551 dries 1.102
552 dries 1.362 /**
553     * Apply filters to a node in preparation for theming.
554     */
555 dries 1.355 function node_prepare($node, $teaser = FALSE) {
556 dries 1.341 $node->readmore = (strlen($node->teaser) < strlen($node->body));
557 dries 1.355 if ($teaser == FALSE) {
558 unconed 1.512 $node->body = check_markup($node->body, $node->format, FALSE);
559 dries 1.309 }
560     else {
561 unconed 1.512 $node->teaser = check_markup($node->teaser, $node->format, FALSE);
562 dries 1.259 }
563 dries 1.309 return $node;
564 dries 1.102 }
565    
566 dries 1.362 /**
567     * Generate a page displaying a single node, along with its comments.
568     */
569 dries 1.219 function node_show($node, $cid) {
570 dries 1.371 $output = node_view($node, FALSE, TRUE);
571 dries 1.174
572 dries 1.371 if (function_exists('comment_render') && $node->comment) {
573     $output .= comment_render($node, $cid);
574 dries 1.300 }
575 dries 1.174
576 dries 1.371 // Update the history table, stating that this user viewed this node.
577     node_tag_new($node->nid);
578 dries 1.76
579 dries 1.371 return $output;
580 dries 1.74 }
581    
582 dries 1.362 /**
583     * Implementation of hook_perm().
584     */
585 dries 1.39 function node_perm() {
586 dries 1.584 return array('administer nodes', 'access content', 'view revisions', 'revert revisions');
587 dries 1.39 }
588    
589 dries 1.362 /**
590     * Implementation of hook_search().
591     */
592 dries 1.413 function node_search($op = 'search', $keys = null) {
593     switch ($op) {
594     case 'name':
595     return t('content');
596 unconed 1.537
597 dries 1.415 case 'reset':
598     variable_del('node_cron_last');
599 unconed 1.588 variable_del('node_cron_last_nid');
600 dries 1.415 return;
601 unconed 1.537
602 unconed 1.439 case 'status':
603     $last = variable_get('node_cron_last', 0);
604 unconed 1.588 $last_nid = variable_get('node_cron_last_nid', 0);
605 dries 1.594 $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1'));
606     $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));
607 unconed 1.439 return array('remaining' => $remaining, 'total' => $total);
608 unconed 1.537
609     case 'admin':
610     $form = array();
611     // Output form for defining rank factor weights.
612     $form['content_ranking'] = array('#type' => 'fieldset', '#title' => t('Content ranking'));
613     $form['content_ranking']['#theme'] = 'node_search_admin';
614 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>');
615 unconed 1.537
616     $ranking = array('node_rank_relevance' => t('Keyword relevance'),
617     'node_rank_recent' => t('Recently posted'));
618     if (module_exist('comment')) {
619     $ranking['node_rank_comments'] = t('Number of comments');
620     }
621     if (module_exist('statistics') && variable_get('statistics_count_content_views', 0)) {
622     $ranking['node_rank_views'] = t('Number of views');
623     }
624    
625     // Note: reversed to reflect that higher number = higher ranking.
626     $options = drupal_map_assoc(range(0, 10));
627     foreach ($ranking as $var => $title) {
628     $form['content_ranking']['factors'][$var] = array('#title' => $title, '#type' => 'select', '#options' => $options, '#default_value' => variable_get($var, 5));
629     }
630     return $form;
631    
632 dries 1.413 case 'search':
633 unconed 1.537 // Build matching conditions
634     list($join1, $where1) = _db_rewrite_sql();
635     $arguments1 = array();
636     $conditions1 = 'n.status = 1';
637    
638     if ($type = search_query_extract($keys, 'type')) {
639     $types = array();
640     foreach (explode(',', $type) as $t) {
641     $types[] = "n.type = '%s'";
642     $arguments1[] = $t;
643     }
644     $conditions1 .= ' AND ('. implode(' OR ', $types) .')';
645     $keys = search_query_insert($keys, 'type');
646     }
647    
648     if ($category = search_query_extract($keys, 'category')) {
649     $categories = array();
650     foreach (explode(',', $category) as $c) {
651     $categories[] = "tn.tid = %d";
652     $arguments1[] = $c;
653     }
654     $conditions1 .= ' AND ('. implode(' OR ', $categories) .')';
655     $join1 .= ' INNER JOIN {term_node} tn ON n.nid = tn.nid';
656     $keys = search_query_insert($keys, 'category');
657     }
658    
659     // Build ranking expression (we try to map each parameter to a
660     // uniform distribution in the range 0..1).
661     $ranking = array();
662     $arguments2 = array();
663     $join2 = '';
664     // 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     }
671     if ($weight = (int)variable_get('node_rank_recent', 5)) {
672     // Exponential decay with half-life of 6 months, starting at last indexed node
673     $ranking[] = '%d * POW(2, (GREATEST(n.created, n.changed, c.last_comment_timestamp) - %d) * 6.43e-8)';
674     $arguments2[] = $weight;
675     $arguments2[] = (int)variable_get('node_cron_last', 0);
676     $join2 .= ' INNER JOIN {node} n ON n.nid = i.sid LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
677     $stats_join = true;
678     }
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     }
689     if (module_exist('statistics') && variable_get('statistics_count_content_views', 0) &&
690     $weight = (int)variable_get('node_rank_views', 5)) {
691     // Inverse law that maps the highest view count on the site to 1 and 0 to 0.
692     $scale = variable_get('node_cron_views_scale', 0.0);
693     $ranking[] = '%d * (2.0 - 2.0 / (1.0 + nc.totalcount * %f))';
694     $arguments2[] = $weight;
695     $arguments2[] = $scale;
696 unconed 1.638 $join2 .= ' LEFT JOIN {node_counter} nc ON nc.nid = i.sid';
697 unconed 1.537 }
698     $select2 = (count($ranking) ? implode(' + ', $ranking) : 'i.relevance') . ' AS score';
699    
700     // Do search
701     $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);
702    
703     // Load results
704 dries 1.413 $results = array();
705     foreach ($find as $item) {
706 unconed 1.609 $node = node_load($item->sid);
707 unconed 1.510
708     // Get node output (filtered and with module-specific fields).
709     if (node_hook($node, 'view')) {
710     node_invoke($node, 'view', false, false);
711     }
712     else {
713     $node = node_prepare($node, false);
714     }
715     // Allow modules to change $node->body before viewing.
716     node_invoke_nodeapi($node, 'view', false, false);
717    
718 unconed 1.537 // Fetch comments for snippet
719     $node->body .= module_invoke('comment', 'nodeapi', $node, 'update index');
720 unconed 1.641 // Fetch terms for snippet
721     $node->body .= module_invoke('taxonomy', 'nodeapi', $node, 'update index');
722 unconed 1.537
723 dries 1.436 $extra = node_invoke_nodeapi($node, 'search result');
724 unconed 1.609 $results[] = array('link' => url('node/'. $item->sid),
725 dries 1.524 'type' => node_get_name($node),
726 dries 1.413 'title' => $node->title,
727 dries 1.514 'user' => theme('username', $node),
728 dries 1.413 'date' => $node->changed,
729 unconed 1.537 'node' => $node,
730 dries 1.436 'extra' => $extra,
731 unconed 1.511 'snippet' => search_excerpt($keys, $node->body));
732 dries 1.413 }
733     return $results;
734     }
735 dries 1.73 }
736    
737 killes 1.622 /**
738     * Implementation of hook_user().
739     */
740     function node_user($op, &$edit, &$user) {
741     if ($op == 'delete') {
742     db_query('UPDATE {node} SET uid = 0 WHERE uid = %d', $user->uid);
743     db_query('UPDATE {node_revisions} SET uid = 0 WHERE uid = %d', $user->uid);
744     }
745     }
746    
747 unconed 1.537 function theme_node_search_admin($form) {
748     $output = form_render($form['info']);
749    
750     $header = array(t('Factor'), t('Weight'));
751     foreach (element_children($form['factors']) as $key) {
752     $row = array();
753     $row[] = $form['factors'][$key]['#title'];
754     unset($form['factors'][$key]['#title']);
755     $row[] = form_render($form['factors'][$key]);
756     $rows[] = $row;
757     }
758     $output .= theme('table', $header, $rows);
759    
760     $output .= form_render($form);
761     return $output;
762     }
763    
764 dries 1.355 /**
765 dries 1.362 * Menu callback; presents general node configuration options.
766 dries 1.355 */
767     function node_configure() {
768    
769 dries 1.532 $form['default_nodes_main'] = array(
770 dries 1.535 '#type' => 'select', '#title' => t('Number of posts on main page'), '#default_value' => variable_get('default_nodes_main', 10),
771     '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30)),
772     '#description' => t('The default maximum number of posts to display per page on overview pages such as the main page.')
773 dries 1.532 );
774    
775     $form['teaser_length'] = array(
776 dries 1.535 '#type' => 'select', '#title' => t('Length of trimmed posts'), '#default_value' => variable_get('teaser_length', 600),
777     '#options' => array(0 => t('Unlimited'), 200 => t('200 characters'), 400 => t('400 characters'), 600 => t('600 characters'),
778 dries 1.532 800 => t('800 characters'), 1000 => t('1000 characters'), 1200 => t('1200 characters'), 1400 => t('1400 characters'),
779     1600 => t('1600 characters'), 1800 => t('1800 characters'), 2000 => t('2000 characters')),
780 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.")
781 dries 1.532 );
782    
783     $form['node_preview'] = array(
784 dries 1.535 '#type' => 'radios', '#title' => t('Preview post'), '#default_value' => variable_get('node_preview', 0),
785     '#options' => array(t('Optional'), t('Required')), '#description' => t('Must users preview posts before submitting?')
786 dries 1.532 );
787 dries 1.237
788 dries 1.532 return system_settings_form('node_configure', $form);
789 dries 1.37 }
790    
791 dries 1.362 /**
792     * Retrieve the comment mode for the given node ID (none, read, or read/write).
793     */
794 dries 1.120 function node_comment_mode($nid) {
795 dries 1.142 static $comment_mode;
796     if (!isset($comment_mode[$nid])) {
797 dries 1.324 $comment_mode[$nid] = db_result(db_query('SELECT comment FROM {node} WHERE nid = %d', $nid));
798 dries 1.142 }
799     return $comment_mode[$nid];
800 dries 1.120 }
801    
802 dries 1.348 /**
803     * Implementation of hook_link().
804     */
805 kjartan 1.89 function node_link($type, $node = 0, $main = 0) {
806 dries 1.205 $links = array();
807    
808 dries 1.324 if ($type == 'node') {
809 dries 1.341 if ($main == 1 && $node->teaser && $node->readmore) {
810 dries 1.355 $links[] = l(t('read more'), "node/$node->nid", array('title' => t('Read the rest of this posting.'), 'class' => 'read-more'));
811 dries 1.291 }
812 dries 1.42 }
813    
814 dries 1.355 return $links;
815     }
816 dries 1.271
817 dries 1.355 /**
818     * Implementation of hook_menu().