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