Code coverage for /20080809/modules/node/node.module

Line #Times calledCode
1
<?php
2
// $Id: node.module,v 1.971 2008/08/03 19:02:06 dries Exp $
3
4
/**
5
 * @file
6
 * The core that allows content to be submitted to the site. Modules and
7
 * scripts may programmatically submit nodes using the usual form API
pattern.
8
 */
9
10
/**
11
 * Nodes changed before this time are always marked as read.
12
 *
13
 * Nodes changed after this time may be marked new, updated, or read,
depending
14
 * on their state for the current user. Defaults to 30 days ago.
15
 */
162027
define('NODE_NEW_LIMIT', time() - 30 * 24 * 60 * 60);
17
18
/**
19
 * Node is being built before being viewed normally.
20
 */
212027
define('NODE_BUILD_NORMAL', 0);
22
23
/**
24
 * Node is being built before being previewed.
25
 */
262027
define('NODE_BUILD_PREVIEW', 1);
27
28
/**
29
 * Node is being built before being indexed by search module.
30
 */
312027
define('NODE_BUILD_SEARCH_INDEX', 2);
32
33
/**
34
 * Node is being built before being displayed as a search result.
35
 */
362027
define('NODE_BUILD_SEARCH_RESULT', 3);
37
38
/**
39
 * Node is being built before being displayed as part of an RSS feed.
40
 */
412027
define('NODE_BUILD_RSS', 4);
42
43
/**
44
 * Node is being built before being printed.
45
 */
462027
define('NODE_BUILD_PRINT', 5);
47
48
/**
49
 * Implementation of hook_help().
50
 */
512027
function node_help($path, $arg) {
52
  // Remind site administrators about the {node_access} table being
flagged
53
  // for rebuild. We don't need to issue the message on the confirm form,
or
54
  // while the rebuild is being processed.
551491
  if ($path != 'admin/content/node-settings/rebuild' && $path != 'batch' &&
strpos($path, '#') === FALSE
561491
      && user_access('access administration pages') &&
node_access_needs_rebuild()) {
570
    if ($path == 'admin/content/node-settings') {
580
      $message = t('The content access permissions need to be rebuilt.');
590
    }
60
    else {
610
      $message = t('The content access permissions need to be rebuilt.
Please visit <a href="@node_access_rebuild">this page</a>.',
array('@node_access_rebuild' =>
url('admin/content/node-settings/rebuild')));
62
    }
630
    drupal_set_message($message, 'error');
640
  }
65
66
  switch ($path) {
671491
    case 'admin/help#node':
6811
      $output = '<p>' . t('The node module manages content on your site,
and stores all posts (regardless of type) as a "node" . In addition to
basic publishing settings, including whether the post has been published,
promoted to the site front page, or should remain present (or sticky) at
the top of lists, the node module also records basic information about the
author of a post. Optional revision control over edits is available. For
additional functionality, the node module is often extended by other
modules.') . '</p>';
6911
      $output .= '<p>' . t('Though each post on your site is a node, each
post is also of a particular <a href="@content-type">content type</a>. <a
href="@content-type">Content types</a> are used to define the
characteristics of a post, including the title and description of the
fields displayed on its add and edit pages. Each content type may have
different default settings for <em>Publishing options</em> and other
workflow controls. By default, the two content types in a standard Drupal
installation are <em>Page</em> and <em>Story</em>. Use the <a
href="@content-type">content types page</a> to add new or edit existing
content types. Additional content types also become available as you enable
additional core, contributed and custom modules.', array('@content-type' =>
url('admin/build/types'))) . '</p>';
7011
      $output .= '<p>' . t('The administrative <a href="@content">content
page</a> allows you to review and manage your site content. The <a
href="@post-settings">post settings page</a> sets certain options for the
display of posts. The node module makes a number of permissions available
for each content type, which may be set by role on the <a
href="@permissions">permissions page</a>.', array('@content' =>
url('admin/content/node'), '@post-settings' =>
url('admin/content/node-settings'), '@permissions' =>
url('admin/user/permissions'))) . '</p>';
7111
      $output .= '<p>' . t('For more information, see the online handbook
entry for <a href="@node">Node module</a>.', array('@node' =>
'http://drupal.org/handbook/modules/node/')) . '</p>';
7211
      return $output;
731489
    case 'admin/content/node':
740
      return ' '; // Return a non-null value so that the 'more help' link
is shown.
751489
    case 'admin/build/types':
761
      return '<p>' . t('Below is a list of all the content types on your
site. All posts that exist on your site are instances of one of these
content types.') . '</p>';
771489
    case 'admin/build/types/add':
780
      return '<p>' . t('To create a new content type, enter the
human-readable name, the machine-readable name, and all other relevant
fields that are on this page. Once created, users of your site will be able
to create posts that are instances of this content type.') . '</p>';
791489
    case 'node/%/revisions':
803
      return '<p>' . t('The revisions let you track differences between
multiple versions of a post.') . '</p>';
811486
    case 'node/%/edit':
8237
      $node = node_load($arg[1]);
8337
      $type = node_get_types('type', $node->type);
8437
      return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) .
'</p>' : '');
850
  }
86
871449
  if ($arg[0] == 'node' && $arg[1] == 'add' && $arg[2]) {
8849
    $type = node_get_types('type', str_replace('-', '_', $arg[2]));
8949
    return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) .
'</p>' : '');
900
  }
911400
}
92
93
/**
94
 * Implementation of hook_theme().
95
 */
962027
function node_theme() {
97
  return array(
98
    'node' => array(
9991
      'arguments' => array('node' => NULL, 'teaser' => FALSE, 'page' =>
FALSE),
10091
      'template' => 'node',
10191
    ),
102
    'node_list' => array(
10391
      'arguments' => array('items' => NULL, 'title' => NULL),
10491
    ),
105
    'node_search_admin' => array(
10691
      'arguments' => array('form' => NULL),
10791
    ),
108
    'node_filter_form' => array(
10991
      'arguments' => array('form' => NULL),
11091
      'file' => 'node.admin.inc',
11191
    ),
112
    'node_filters' => array(
11391
      'arguments' => array('form' => NULL),
11491
      'file' => 'node.admin.inc',
11591
    ),
116
    'node_admin_nodes' => array(
11791
      'arguments' => array('form' => NULL),
11891
      'file' => 'node.admin.inc',
11991
    ),
120
    'node_add_list' => array(
12191
      'arguments' => array('content' => NULL),
12291
      'file' => 'node.pages.inc',
12391
    ),
124
    'node_form' => array(
12591
      'arguments' => array('form' => NULL),
12691
      'file' => 'node.pages.inc',
12791
    ),
128
    'node_preview' => array(
12991
      'arguments' => array('node' => NULL),
13091
      'file' => 'node.pages.inc',
13191
    ),
132
    'node_log_message' => array(
13391
      'arguments' => array('log' => NULL),
13491
    ),
135
    'node_submitted' => array(
13691
      'arguments' => array('node' => NULL),
13791
    ),
13891
  );
1390
}
140
141
/**
142
 * Implementation of hook_cron().
143
 */
1442027
function node_cron() {
1452
  db_query('DELETE FROM {history} WHERE timestamp < %d', NODE_NEW_LIMIT);
1462
}
147
148
/**
149
 * Gather a listing of links to nodes.
150
 *
151
 * @param $result
152
 *   A DB result object from a query to fetch node objects. If your query
153
 *   joins the <code>node_comment_statistics</code> table so that the
154
 *   <code>comment_count</code> field is available, a title attribute will
155
 *   be added to show the number of comments.
156
 * @param $title
157
 *   A heading for the resulting list.
158
 *
159
 * @return
160
 *   An HTML list suitable as content for a block, or FALSE if no result
can
161
 *   fetch from DB result object.
162
 */
1632027
function node_title_list($result, $title = NULL) {
164181
  $items = array();
165181
  $num_rows = FALSE;
166181
  while ($node = db_fetch_object($result)) {
167146
    $items[] = l($node->title, 'node/' . $node->nid,
!empty($node->comment_count) ? array('attributes' => array('title' =>
format_plural($node->comment_count, '1 comment', '@count comments'))) :
array());
168146
    $num_rows = TRUE;
169146
  }
170
171181
  return $num_rows ? theme('node_list', $items, $title) : FALSE;
1720
}
173
174
/**
175
 * Format a listing of links to nodes.
176
 *
177
 * @ingroup themeable
178
 */
1792027
function theme_node_list($items, $title = NULL) {
180146
  return theme('item_list', $items, $title);
1810
}
182
183
/**
184
 * Update the 'last viewed' timestamp of the specified node for current
user.
185
 */
1862027
function node_tag_new($nid) {
187168
  global $user;
188
189168
  if ($user->uid) {
190161
    if (node_last_viewed($nid)) {
19181
      db_query('UPDATE {history} SET timestamp = %d WHERE uid = %d AND nid
= %d', time(), $user->uid, $nid);
19281
    }
193
    else {
19480
      @db_query('INSERT INTO {history} (uid, nid, timestamp) VALUES (%d,
%d, %d)', $user->uid, $nid, time());
195
    }
196161
  }
197168
}
198
199
/**
200
 * Retrieves the timestamp at which the current user last viewed the
201
 * specified node.
202
 */
2032027
function node_last_viewed($nid) {
204178
  global $user;
205178
  static $history;
206
207178
  if (!isset($history[$nid])) {
208178
    $history[$nid] = db_fetch_object(db_query("SELECT timestamp FROM
{history} WHERE uid = %d AND nid = %d", $user->uid, $nid));
209178
  }
210
211178
  return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp :
0);
2120
}
213
214
/**
215
 * Decide on the type of marker to be displayed for a given node.
216
 *
217
 * @param $nid
218
 *   Node ID whose history supplies the "last viewed" timestamp.
219
 * @param $timestamp
220
 *   Time which is compared against node's "last viewed" timestamp.
221
 * @return
222
 *   One of the MARK constants.
223
 */
2242027
function node_mark($nid, $timestamp) {
22540
  global $user;
22640
  static $cache;
227
22840
  if (!$user->uid) {
22910
    return MARK_READ;
2300
  }
23130
  if (!isset($cache[$nid])) {
23230
    $cache[$nid] = node_last_viewed($nid);
23330
  }
23430
  if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) {
23513
    return MARK_NEW;
2360
  }
23717
  elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) {
2389
    return MARK_UPDATED;
2390
  }
24014
  return MARK_READ;
2410
}
242
243
/**
244
 * See if the user used JS to submit a teaser.
245
 */
2462027
function node_teaser_js(&$form, &$form_state) {
247139
  if (isset($form['#post']['teaser_js'])) {
248
    // Glue the teaser to the body.
24966
    if (trim($form_state['values']['teaser_js'])) {
250
      // Space the teaser from the body
2510
      $body = trim($form_state['values']['teaser_js']) .
"\r\n<!--break-->\r\n" . trim($form_state['values']['body']);
2520
    }
253
    else {
254
      // Empty teaser, no spaces.
25566
      $body = '<!--break-->' . $form_state['values']['body'];
256
    }
257
    // Pass updated body value on to preview/submit form processing.
25866
    form_set_value($form['body'], $body, $form_state);
259
    // Pass updated body value back onto form for those cases
260
    // in which the form is redisplayed.
26166
    $form['body']['#value'] = $body;
26266
  }
263139
  return $form;
2640
}
265
266
/**
267
 * Ensure value of "teaser_include" checkbox is consistent with other form
data.
268
 *
269
 * This handles two situations in which an unchecked checkbox is rejected:
270
 *
271
 *   1. The user defines a teaser (summary) but it is empty;
272
 *   2. The user does not define a teaser (summary) (in this case an
273
 *      unchecked checkbox would cause the body to be empty, or missing
274
 *      the auto-generated teaser).
275
 *
276
 * If JavaScript is active then it is used to force the checkbox to be
277
 * checked when hidden, and so the second case will not arise.
278
 *
279
 * In either case a warning message is output.
280
 */
2812027
function node_teaser_include_verify(&$form, &$form_state) {
282139
  $message = '';
283
284
  // $form['#post'] is set only when the form is built for preview/submit.
285139
  if (isset($form['#post']['body']) &&
isset($form_state['values']['teaser_include']) &&
!$form_state['values']['teaser_include']) {
286
    // "teaser_include" checkbox is present and unchecked.
2870
    if (strpos($form_state['values']['body'], '<!--break-->') === 0) {
288
      // Teaser is empty string.
2890
      $message = t('You specified that the summary should not be shown when
this post is displayed in full view. This setting is ignored when the
summary is empty.');
2900
    }
2910
    elseif (strpos($form_state['values']['body'], '<!--break-->') ===
FALSE) {
292
      // Teaser delimiter is not present in the body.
2930
      $message = t('You specified that the summary should not be shown when
this post is displayed in full view. This setting has been ignored since
you have not defined a summary for the post. (To define a summary, insert
the delimiter "&lt;!--break--&gt;" (without the quotes) in the Body of the
post to indicate the end of the summary and the start of the main
content.)');
2940
    }
295
2960
    if (!empty($message)) {
2970
      drupal_set_message($message, 'warning');
298
      // Pass new checkbox value on to preview/submit form processing.
2990
      form_set_value($form['teaser_include'], 1, $form_state);
300
      // Pass new checkbox value back onto form for those cases
301
      // in which form is redisplayed.
3020
      $form['teaser_include']['#value'] = 1;
3030
    }
3040
  }
305
306139
  return $form;
3070
}
308
309
/**
310
 * Generate a teaser for a node body.
311
 *
312
 * If the end of the teaser is not indicated using the <!--break-->
delimiter
313
 * then we generate the teaser automatically, trying to end it at a
sensible
314
 * place such as the end of a paragraph, a line break, or the end of a
315
 * sentence (in that order of preference).
316
 *
317
 * @param $body
318
 *   The content for which a teaser will be generated.
319
 * @param $format
320
 *   The format of the content. If the content contains PHP code, we do
not
321
 *   split it up to prevent parse errors. If the line break filter is
present
322
 *   then we treat newlines embedded in $body as line breaks.
323
 * @param $size
324
 *   The desired character length of the teaser. If omitted, the default
325
 *   value will be used. Ignored if the special delimiter is present
326
 *   in $body.
327
 * @return
328
 *   The generated teaser.
329
 */
3302027
function node_teaser($body, $format = NULL, $size = NULL) {
331
33264
  if (!isset($size)) {
33363
    $size = variable_get('teaser_length', 600);
33463
  }
335
336
  // Find where the delimiter is in the body
33764
  $delimiter = strpos($body, '<!--break-->');
338
339
  // If the size is zero, and there is no delimiter, the entire body is the
teaser.
34064
  if ($size == 0 && $delimiter === FALSE) {
3411
    return $body;
3420
  }
343
344
  // If a valid delimiter has been specified, use it to chop off the
teaser.
34564
  if ($delimiter !== FALSE) {
34663
    return substr($body, 0, $delimiter);
3470
  }
348
349
  // We check for the presence of the PHP evaluator filter in the current
350
  // format. If the body contains PHP code, we do not split it up to
prevent
351
  // parse errors.
3521
  if (isset($format)) {
3531
    $filters = filter_list_format($format);
3541
    if (isset($filters['php/0']) && strpos($body, '<?') !== FALSE) {
3550
      return $body;
3560
    }
3571
  }
358
359
  // If we have a short body, the entire body is the teaser.
3601
  if (drupal_strlen($body) <= $size) {
3611
    return $body;
3620
  }
363
364
  // If the delimiter has not been specified, try to split at paragraph or
365
  // sentence boundaries.
366
367
  // The teaser may not be longer than maximum length specified. Initial
slice.
3681
  $teaser = truncate_utf8($body, $size);
369
370
  // Store the actual length of the UTF8 string -- which might not be the
same
371
  // as $size.
3721
  $max_rpos = strlen($teaser);
373
374
  // How much to cut off the end of the teaser so that it doesn't end in
the
375
  // middle of a paragraph, sentence, or word.
376
  // Initialize it to maximum in order to find the minimum.
3771
  $min_rpos = $max_rpos;
378
379
  // Store the reverse of the teaser.  We use strpos on the reversed needle
and
380
  // haystack for speed and convenience.
3811
  $reversed = strrev($teaser);
382
383
  // Build an array of arrays of break points grouped by preference.
3841
  $break_points = array();
385
386
  // A paragraph near the end of sliced teaser is most preferable.
3871
  $break_points[] = array('</p>' => 0);
388
389
  // If no complete paragraph then treat line breaks as paragraphs.
3901
  $line_breaks = array('<br />' => 6, '<br>' => 4);
391
  // Newline only indicates a line break if line break converter
392
  // filter is present.
3931
  if (isset($filters['filter/1'])) {
3941
    $line_breaks["\n"] = 1;
3951
  }
3961
  $break_points[] = $line_breaks;
397
398
  // If the first paragraph is too long, split at the end of a sentence.
3991
  $break_points[] = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟
' => 1);
400
401
  // Iterate over the groups of break points until a break point is found.
4021
  foreach ($break_points as $points) {
403
    // Look for each break point, starting at the end of the teaser.
4041
    foreach ($points as $point => $offset) {
405
      // The teaser is already reversed, but the break point isn't.
4061
      $rpos = strpos($reversed, strrev($point));
4071
      if ($rpos !== FALSE) {
4081
        $min_rpos = min($rpos + $offset, $min_rpos);
4091
      }
4101
    }
411
412
    // If a break point was found in this group, slice and return the
teaser.
4131
    if ($min_rpos !== $max_rpos) {
414
      // Don't slice with length 0.  Length must be <0 to slice from RHS.
4151
      return ($min_rpos === 0) ? $teaser : substr($teaser, 0, 0 -
$min_rpos);
4160
    }
4171
  }
418
419
  // If a break point was not found, still return a teaser.
4201
  return $teaser;
4210
}
422
423
/**
424
 * Builds a list of available node types, and returns all of part of this
list
425
 * in the specified format.
426
 *
427
 * @param $op
428
 *   The format in which to return the list. When this is set to 'type',
429
 *   'module', or 'name', only the specified node type is returned. When
set to
430
 *   'types' or 'names', all node types are returned.
431
 * @param $node
432
 *   A node object, array, or string that indicates the node type to
return.
433
 *   Leave at default value (NULL) to return a list of all node types.
434
 * @param $reset
435
 *   Whether or not to reset this function's internal cache (defaults to
436
 *   FALSE).
437
 *
438
 * @return
439
 *   Either an array of all available node types, or a single node type, in
a
440
 *   variable format. Returns FALSE if the node type is not found.
441
 */
4422027
function node_get_types($op = 'types', $node = NULL, $reset = FALSE) {
4431710
  static $_node_types, $_node_names;
444
4451710
  if ($reset || !isset($_node_types)) {
4461710
    list($_node_types, $_node_names) = _node_types_build();
4471710
  }
448
4491710
  if ($node) {
4501678
    if (is_array($node)) {
4510
      $type = $node['type'];
4520
    }
4531678
    elseif (is_object($node)) {
454561
      $type = $node->type;
455561
    }
4561538
    elseif (is_string($node)) {
4571538
      $type = $node;
4581538
    }
4591678
    if (!isset($_node_types[$type])) {
4600
      return FALSE;
4610
    }
4621678
  }
463
  switch ($op) {
4641710
    case 'types':
4651649
      return $_node_types;
4661682
    case 'type':
467239
      return isset($_node_types[$type]) ? $_node_types[$type] : FALSE;
4681635
    case 'module':
4691631
      return isset($_node_types[$type]->module) ?
$_node_types[$type]->module : FALSE;
470113
    case 'names':
4718
      return $_node_names;
472105
    case 'name':
473105
      return isset($_node_names[$type]) ? $_node_names[$type] : FALSE;
4740
  }
4750
}
476
477
/**
478
 * Resets the database cache of node types, and saves all new or
non-modified
479
 * module-defined node types to the database.
480
 */
4812027
function node_types_rebuild() {
48210
  _node_types_build();
483
48410
  $node_types = node_get_types('types', NULL, TRUE);
485
48610
  foreach ($node_types as $type => $info) {
48710
    if (!empty($info->is_new)) {
4880
      node_type_save($info);
4890
    }
49010
    if (!empty($info->disabled)) {
4910
      node_type_delete($info->type);
4920
    }
49310
  }
494
49510
  _node_types_build();
49610
}
497
498
/**
499
 * Saves a node type to the database.
500
 *
501
 * @param $info
502
 *   The node type to save, as an object.
503
 *
504
 * @return
505
 *   Status flag indicating outcome of the operation.
506
 */
5072027
function node_type_save($info) {
50863
  $is_existing = FALSE;
50963
  $existing_type = !empty($info->old_type) ? $info->old_type :
$info->type;
51063
  $is_existing = db_result(db_query("SELECT COUNT(*) FROM {node_type} WHERE
type = '%s'", $existing_type));
51163
  if (!isset($info->help)) {
5120
    $info->help = '';
5130
  }
51463
  if (!isset($info->min_word_count)) {
5150
    $info->min_word_count = 0;
5160
  }
51763
  if (!isset($info->body_label)) {
5180
    $info->body_label = '';
5190
  }
520
52163
  if ($is_existing) {
5221
    db_query("UPDATE {node_type} SET type = '%s', name = '%s', module =
'%s', has_title = %d, title_label = '%s', has_body = %d, body_label = '%s',
description = '%s', help = '%s', min_word_count = %d, custom = %d, modified
= %d, locked = %d WHERE type = '%s'", $info->type, $info->name,
$info->module, $info->has_title, $info->title_label, $info->has_body,
$info->body_label, $info->description, $info->help, $info->min_word_count,
$info->custom, $info->modified, $info->locked, $existing_type);
523
5241
    module_invoke_all('node_type', 'update', $info);
5251
    return SAVED_UPDATED;
5260
  }
527
  else {
52862
    db_query("INSERT INTO {node_type} (type, name, module, has_title,
title_label, has_body, body_label, description, help, min_word_count,
custom, modified, locked, orig_type) VALUES ('%s', '%s', '%s', %d, '%s',
%d, '%s', '%s', '%s', %d, %d, %d, %d, '%s')", $info->type, $info->name,
$info->module, $info->has_title, $info->title_label, $info->has_body,
$info->body_label, $info->description, $info->help, $info->min_word_count,
$info->custom, $info->modified, $info->locked, $info->orig_type);
529
53062
    module_invoke_all('node_type', 'insert', $info);
53162
    return SAVED_NEW;
532
  }
5330
}
534
535
/**
536
 * Deletes a node type from the database.
537
 *
538
 * @param $type
539
 *   The machine-readable name of the node type to be deleted.
540
 */
5412027
function node_type_delete($type) {
5420
  $info = node_get_types('type', $type);
5430
  db_query("DELETE FROM {node_type} WHERE type = '%s'", $type);
5440
  module_invoke_all('node_type', 'delete', $info);
5450
}
546
547
/**
548
 * Updates all nodes of one type to be of another type.
549
 *
550
 * @param $old_type
551
 *   The current node type of the nodes.
552
 * @param $type
553
 *   The new node type of the nodes.
554
 *
555
 * @return
556
 *   The number of nodes whose node type field was modified.
557
 */
5582027
function node_type_update_nodes($old_type, $type) {
5590
  db_query("UPDATE {node} SET type = '%s' WHERE type = '%s'", $type,
$old_type);
5600
  return db_affected_rows();
5610
}
562
563
/**
564
 * Builds and returns the list of available node types.
565
 *
566
 * The list of types is built by querying hook_node_info() in all modules,
and
567
 * by comparing this information with the node types in the {node_type}
table.
568
 *
569
 */
5702027
function _node_types_build() {
5711710
  $_node_types = array();
5721710
  $_node_names = array();
573
5741710
  $info_array = module_invoke_all('node_info');
5751710
  foreach ($info_array as $type => $info) {
576381
    $info['type'] = $type;
577381
    $_node_types[$type] = (object) _node_type_set_defaults($info);
578381
    $_node_names[$type] = $info['name'];
579381
  }
580
5811710
  $type_result = db_query(db_rewrite_sql('SELECT nt.type, nt.* FROM
{node_type} nt ORDER BY nt.type ASC', 'nt', 'type'));
5821710
  while ($type_object = db_fetch_object($type_result)) {
583
    // Check for node types from disabled modules and mark their types for
removal.
584
    // Types defined by the node module in the database (rather than by a
separate
585
    // module using hook_node_info) have a module value of 'node'.
5861710
    if ($type_object->module != 'node' &&
empty($info_array[$type_object->type])) {
5870
      $type_object->disabled = TRUE;
5880
    }
5891710
    if (!isset($_node_types[$type_object->type]) || $type_object->modified)
{
5901710
      $_node_types[$type_object->type] = $type_object;
5911710
      $_node_names[$type_object->type] = $type_object->name;
592
5931710
      if ($type_object->type != $type_object->orig_type) {
5940
        unset($_node_types[$type_object->orig_type]);
5950
        unset($_node_names[$type_object->orig_type]);
5960
      }
5971710
    }
5981710
  }
599
6001710
  asort($_node_names);
601
6021710
  return array($_node_types, $_node_names);
6030
}
604
605
/**
606
 * Set default values for a node type defined through hook_node_info().
607
 */
6082027
function _node_type_set_defaults($info) {
609437
  if (!isset($info['has_title'])) {
610437
    $info['has_title'] = TRUE;
611437
  }
612437
  if ($info['has_title'] && !isset($info['title_label'])) {
613259
    $info['title_label'] = t('Title');
614259
  }
615
616437
  if (!isset($info['has_body'])) {
617413
    $info['has_body'] = TRUE;
618413
  }
619437
  if ($info['has_body'] && !isset($info['body_label'])) {
620413
    $info['body_label'] = t('Body');
621413
  }
622
623437
  if (!isset($info['help'])) {
624382
    $info['help'] = '';
625382
  }
626437
  if (!isset($info['min_word_count'])) {
627382
    $info['min_word_count'] = 0;
628382
  }
629437
  if (!isset($info['custom'])) {
630381
    $info['custom'] = FALSE;
631381
  }
632437
  if (!isset($info['modified'])) {
633381
    $info['modified'] = FALSE;
634381
  }
635437
  if (!isset($info['locked'])) {
636381
    $info['locked'] = TRUE;
637381
  }
638
639437
  $info['orig_type'] = $info['type'];
640437
  $info['is_new'] = TRUE;
641
642437
  return $info;
6430
}
644
645
/**
646
 * Determine whether a node hook exists.
647
 *
648
 * @param &$node
649
 *   Either a node object, node array, or a string containing the node
type.
650
 * @param $hook
651
 *   A string containing the name of the hook.
652
 * @return
653
 *   TRUE iff the $hook exists in the node type of $node.
654
 */
6552027
function node_hook(&$node, $hook) {
6561629
  $module = node_get_types('module', $node);
6571629
  if ($module == 'node') {
658
    // Avoid function name collisions.
6591433
    $module = 'node_content';
6601433
  }
6611629
  return module_hook($module, $hook);
6620
}
663
664
/**
665
 * Invoke a node hook.
666
 *
667
 * @param &$node
668
 *   Either a node object, node array, or a string containing the node
type.
669
 * @param $hook
670
 *   A string containing the name of the hook.
671
 * @param $a2, $a3, $a4
672
 *   Arguments to pass on to the hook, after the $node argument.
673
 * @return
674
 *   The returned value of the invoked hook.
675
 */
6762027
function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
677496
  if (node_hook($node, $hook)) {
678176
    $module = node_get_types('module', $node);
679176
    if ($module == 'node') {
68051
      $module = 'node_content'; // Avoid function name collisions.
68151
    }
682176
    $function = $module . '_' . $hook;
683176
    return ($function($node, $a2, $a3, $a4));
6840
  }
685418
}
686
687
/**
688
 * Invoke a hook_nodeapi() operation in all modules.
689
 *
690
 * @param &$node
691
 *   A node object.
692
 * @param $op
693
 *   A string containing the name of the nodeapi operation.
694
 * @param $a3, $a4
695
 *   Arguments to pass on to the hook, after the $node and $op arguments.
696
 * @return
697
 *   The returned value of the invoked hooks.
698
 */
6992027
function node_invoke_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
700496
  $return = array();
701496
  foreach (module_implements('nodeapi') as $name) {
702496
    $function = $name . '_nodeapi';
703496
    $result = $function($node, $op, $a3, $a4);
704496
    if (isset($result) && is_array($result)) {
705403
      $return = array_merge($return, $result);
706403
    }
707496
    else if (isset($result)) {
7081
      $return[] = $result;
7091
    }
710496
  }
711496
  return $return;
7120
}
713
714
/**
715
 * Load a node object from the database.
716
 *
717
 * @param $param
718
 *   Either the nid of the node or an array of conditions to match against
in the database query
719
 * @param $revision
720
 *   Which numbered revision to load. Defaults to the current version.
721
 * @param $reset
722
 *   Whether to reset the internal node_load cache.
723
 *
724
 * @return
725
 *   A fully-populated node object.
726
 */
7272027
function node_load($param = array(), $revision = NULL, $reset = NULL) {
728408
  static $nodes = array();
729
730408
  if ($reset) {
7312
    $nodes = array();
7322
  }
733
734408
  $cachable = ($revision == NULL);
735408
  $arguments = array();
736408
  if (is_numeric($param)) {
737396
    if ($cachable) {
738
      // Is the node statically cached?
739396
      if (isset($nodes[$param])) {
740309
        return is_object($nodes[$param]) ? clone $nodes[$param] :
$nodes[$param];
7410
      }
742396
    }
743396
    $cond = 'n.nid = %d';
744396
    $arguments[] = $param;
745396
  }
74612
  elseif (is_array($param)) {
747
    // Turn the conditions into a query.
74812
    foreach ($param as $key => $value) {
74912
      $cond[] = 'n.' . db_escape_table($key) . " = '%s'";
75012
      $arguments[] = $value;
75112
    }
75212
    $cond = implode(' AND ', $cond);
75312
  }
754
  else {
7550
    return FALSE;
756
  }
757
758
  // Retrieve a field list based on the site's schema.
759408
  $fields = drupal_schema_fields_sql('node', 'n');
760408
  $fields = array_merge($fields, drupal_schema_fields_sql('node_revisions',
'r'));
761408
  $fields = array_merge($fields, array('u.name', 'u.picture', 'u.data'));
762
  // Remove fields not needed in the query: n.vid and r.nid are redundant,
763
  // n.title is unnecessary because the node title comes from the
764
  // node_revisions table.  We'll keep r.vid, r.title, and n.nid.
765408
  $fields = array_diff($fields, array('n.vid', 'n.title', 'r.nid'));
766408
  $fields = implode(', ', $fields);
767
  // Rename timestamp field for clarity.
768408
  $fields = str_replace('r.timestamp', 'r.timestamp AS revision_timestamp',
$fields);
769
  // Change name of revision uid so it doesn't conflict with n.uid.
770408
  $fields = str_replace('r.uid', 'r.uid AS revision_uid', $fields);
771
772
  // Retrieve the node.
773
  // No db_rewrite_sql is applied so as to get complete indexing for
search.
774408
  if ($revision) {
7755
    array_unshift($arguments, $revision);
7765
    $node = db_fetch_object(db_query('SELECT ' . $fields . ' 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));
7775
  }
778
  else {
779408
    $node = db_fetch_object(db_query('SELECT ' . $fields . ' 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));
780
  }
781
782408
  if ($node && $node->nid) {
783
    // Call the node specific callback (if any) and piggy-back the
784
    // results to the node or overwrite some values.
785403
    if ($extra = node_invoke($node, 'load')) {
78688
      foreach ($extra as $key => $value) {
78788
        $node->$key = $value;
78888
      }
78988
    }
790
791403
    if ($extra = node_invoke_nodeapi($node, 'load')) {
792403
      foreach ($extra as $key => $value) {
793403
        $node->$key = $value;
794403
      }
795403
    }
796403
    if ($cachable) {
797403
      $nodes[$node->nid] = is_object($node) ? clone $node : $node;
798403
    }
799403
  }
800
801408
  return $node;
8020
}
803
804
/**
805
 * Perform validation checks on the given node.
806
 */
8072027
function node_validate($node, $form = array()) {
808
  // Convert the node to an object, if necessary.
80975
  $node = (object)$node;
81075
  $type = node_get_types('type', $node);
811
812
  // Make sure the body has the minimum number of words.
813
  // TODO : use a better word counting algorithm that will work in other
languages
81475
  if (!empty($type->min_word_count) && isset($node->body) &&
count(explode(' ', $node->body)) < $type->min_word_count) {
8150
    form_set_error('body', t('The body of your @type is too short. You need
at least %words words.', array('%words' => $type->min_word_count, '@type'
=> $type->name)));
8160
  }
817
81875
  if (isset($node->nid) && (node_last_changed($node->nid) >
$node->changed)) {
8190
    form_set_error('changed', t('This content has been modified by another
user, changes cannot be saved.'));
8200
  }
821
82275
  if (user_access('administer nodes')) {
823
    // Validate the "authored by" field.
8246
    if (!empty($node->name) && !($account = user_load(array('name' =>
$node->name)))) {
825
      // The use of empty() is mandatory in the context of usernames
826
      // as the empty string denotes the anonymous user. In case we
827
      // are dealing with an anonymous user we set the user ID to 0.
8280
      form_set_error('name', t('The username %name does not exist.',
array('%name' => $node->name)));
8290
    }
830
831
    // Validate the "authored on" field. As of PHP 5.1.0, strtotime returns
FALSE instead of -1 upon failure.
8326
    if (!empty($node->date) && strtotime($node->date) <= 0) {
8330
      form_set_error('date', t('You have to specify a valid date.'));
8340
    }
8356
  }
836
837
  // Do node-type-specific validation checks.
83875
  node_invoke($node, 'validate', $form);
83975
  node_invoke_nodeapi($node, 'validate', $form);
84075
}
841
842
/**
843
 * Prepare node for save and allow modules to make changes.
844
 */
8452027
function node_submit($node) {
84672
  global $user;
847
848
  // Convert the node to an object, if necessary.
84972
  $node = (object)$node;
850
851
  // Generate the teaser, but only if it hasn't been set (e.g. by a
852
  // module-provided 'teaser' form item).
85372
  if (!isset($node->teaser)) {
85465
    if (isset($node->body)) {
85563
      $node->teaser = node_teaser($node->body, isset($node->format) ?
$node->format : NULL);
856
      // Chop off the teaser from the body if needed. The teaser_include
857
      // property might not be set (eg. in Blog API postings), so only act
on
858
      // it, if it was set with a given value.
85963
      if (isset($node->teaser_include) && !$node->teaser_include &&
$node->teaser == substr($node->body, 0, strlen($node->teaser))) {
8600
        $node->body = substr($node->body, strlen($node->teaser));
8610
      }
86263
    }
863
    else {
8642
      $node->teaser = '';
865
    }
86665
  }
867
86872
  if (user_access('administer nodes')) {
869
    // Populate the "authored by" field.
8706
    if ($account = user_load(array('name' => $node->name))) {
8716
      $node->uid = $account->uid;
8726
    }
873
    else {
8740
      $node->uid = 0;
875
    }
8766
  }
87772
  $node->created = !empty($node->date) ? strtotime($node->date) : time();
87872
  $node->validated = TRUE;
879
88072
  return $node;
8810
}
882
883
/**
884
 * Save a node object into the database.
885
 */
8862027
function node_save(&$node) {
887
  // Let modules modify the node before it is saved to the database.
88879
  node_invoke_nodeapi($node, 'presave');
88979
  global $user;
890
89179
  $node->is_new = FALSE;
892
893
  // Apply filters to some default node fields:
89479
  if (empty($node->nid)) {
895
    // Insert a new node.
89649
    $node->is_new = TRUE;
897
898
    // When inserting a node, $node->log must be set because
899
    // {node_revisions}.log does not (and cannot) have a default
900
    // value.  If the user does not have permission to create
901
    // revisions, however, the form will not contain an element for
902
    // log so $node->log will be unset at this point.
90349
    if (!isset($node->log)) {
90432
      $node->log = '';
90532
    }
906
907
    // For the same reasons, make sure we have $node->teaser and
908
    // $node->body.  We should consider making these fields nullable
909
    // in a future version since node types are not required to use them.
91049
    if (!isset($node->teaser)) {
9110
      $node->teaser = '';
9120
    }
91349
    if (!isset($node->body)) {
9144
      $node->body = '';
9154
    }
91649
  }
91731
  elseif (!empty($node->revision)) {
9182
    $node->old_vid = $node->vid;
9192
  }
920
  else {
921
    // When updating a node, avoid clobberring an existing log entry with
an empty one.
92229
    if (empty($node->log)) {
92329
      unset($node->log);
92429
    }
925
  }
926
927
  // Set some required fields:
92879
  if (empty($node->created)) {
92911
    $node->created = time();
93011
  }
931
  // The changed timestamp is always updated for bookkeeping purposes
(revisions, searching, ...)
93279
  $node->changed = time();
933
93479
  $node->timestamp = time();
93579
  $node->format = isset($node->format) ? $node->format :
FILTER_FORMAT_DEFAULT;
93679
  $update_node = TRUE;
937
938
  // Generate the node table query and the node_revisions table query.
93979
  if ($node->is_new) {
94049
    drupal_write_record('node', $node);
94149
    _node_save_revision($node, $user->uid);
94249
    $op = 'insert';
94349
  }
944
  else {
94531
    drupal_write_record('node', $node, 'nid');
94631
    if (!empty($node->revision)) {
9472
      _node_save_revision($node, $user->uid);
9482
    }
949
    else {
95029
      _node_save_revision($node, $user->uid, 'vid');
95129
      $update_node = FALSE;
952
    }
95331
    $op = 'update';
954
  }
95579
  if ($update_node) {
95650
    db_query('UPDATE {node} SET vid = %d WHERE nid = %d', $node->vid,
$node->nid);
95750
  }
958
959
  // Call the node specific callback (if any). This can be
960
  // node_invoke($node, 'insert') or
961
  // node_invoke($node, 'update').
96279
  node_invoke($node, $op);
96379
  node_invoke_nodeapi($node, $op);
964
965
  // Update the node access table for this node.
96679
  node_access_acquire_grants($node);
967
968
  // Clear the page and block caches.
96979
  cache_clear_all();
97079
}
971
972
/**
973
 * Helper function to save a revision with the uid of the current user.
974
 *
975
 * Node is taken by reference, becuse drupal_write_record() updates the
976
 * $node with the revision id, and we need to pass that back to the
caller.
977
 */
9782027
function _node_save_revision(&$node, $uid, $update = NULL) {
97979
  $temp_uid = $node->uid;
98079
  $node->uid = $uid;
98179
  if (isset($update)) {
98229
    drupal_write_record('node_revisions', $node, $update);
98329
  }
984
  else {
98550
    drupal_write_record('node_revisions', $node);
986
  }
98779
  $node->uid = $temp_uid;
98879
}
989
990
/**
991
 * Delete a node.
992
 */
9932027
function node_delete($nid) {
994
99513
  $node = node_load($nid);
996
99713
  if (node_access('delete', $node)) {
99813
    db_query('DELETE FROM {node} WHERE nid = %d', $node->nid);
99913
    db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);
1000
1001
    // Call the node-specific callback (if any):
100213
    node_invoke($node, 'delete');
100313
    node_invoke_nodeapi($node, 'delete');
1004
1005
    // Clear the page and block caches.
100613
    cache_clear_all();
1007
1008
    // Remove this node from the search index if needed.
1009
    // This code is implemented in node module rather than in search
module,
1010
    // because node module is implementing search module's API, not the
other
1011
    // way around.
101213
    if (module_exists('search')) {
10130
      search_wipe($node->nid, 'node');
10140
    }
101513
    watchdog('content', '@type: deleted %title.', array('@type' =>
$node->type, '%title' => $node->title));
101613
    drupal_set_message(t('@type %title has been deleted.', array('@type' =>
node_get_types('name', $node), '%title' => $node->title)));
101713
  }
101813
}
1019
1020
/**
1021
 * Generate a display of the given node.
1022
 *
1023
 * @param $node
1024
 *   A node array or node object.
1025
 * @param $teaser
1026
 *   Whether to display the teaser only or the full form.
1027
 * @param $page
1028
 *   Whether the node is being displayed by itself as a page.
1029
 * @param $links
1030
 *   Whether or not to display node links. Links are omitted for node
previews.
1031
 *
1032
 * @return
1033
 *   An HTML representation of the themed node.
1034
 */
10352027
function node_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) {
1036234
  $node = (object)$node;
1037
1038234
  $node = node_build_content($node, $teaser, $page);
1039
1040233
  if ($links) {
1041231
    $node->links = module_invoke_all('link', 'node', $node, $teaser);
1042231
    drupal_alter('link', $node->links, $node);
1043231
  }
1044
1045
  // Set the proper node part, then unset unused $node part so that a bad
1046
  // theme can not open a security hole.
1047233
  $content = drupal_render($node->content);
1048233
  if ($teaser) {
104928
    $node->teaser = $content;
105028
    unset($node->body);
105128
  }
1052
  else {
1053206
    $node->body = $content;
1054206
    unset($node->teaser);
1055
  }
1056
1057
  // Allow modules to modify the fully-built node.
1058233
  node_invoke_nodeapi($node, 'alter', $teaser, $page);
1059
1060233
  return theme('node', $node, $teaser, $page);
10610
}
1062
1063
/**
1064
 * Apply filters and build the node's standard elements.
1065
 */
10662027
function node_prepare($node, $teaser = FALSE) {
1067
  // First we'll overwrite the existing node teaser and body with
1068
  // the filtered copies! Then, we'll stick those into the content
1069
  // array and set the read more flag if appropriate.
1070232
  $node->readmore = (strlen($node->teaser) < strlen($node->body));
1071
1072232
  if ($teaser == FALSE) {
1073206
    $node->body = check_markup($node->body, $node->format, FALSE);
1074206
  }
1075
  else {
107627
    $node->teaser = check_markup($node->teaser, $node->format, FALSE);
1077
  }
1078
1079232
  $node->content['body'] = array(
1080232
    '#markup' => $teaser ? $node->teaser : $node->body,
1081232
    '#weight' => 0,
1082
  );
1083
1084232
  return $node;
10850
}
1086
1087
/**
1088
 * Builds a structured array representing the node's content.
1089
 *
1090
 * @param $node
1091
 *   A node object.
1092
 * @param $teaser
1093
 *   Whether to display the teaser only, as on the main page.
1094
 * @param $page
1095
 *   Whether the node is being displayed by itself as a page.
1096
 *
1097
 * @return
1098
 *   An structured array containing the individual elements
1099
 *   of the node's body.
1100
 */
11012027
function node_build_content($node, $teaser = FALSE, $page = FALSE) {
1102
1103
  // The build mode identifies the target for which the node is built.
1104241
  if (!isset($node->build_mode)) {
1105232
    $node->build_mode = NODE_BUILD_NORMAL;
1106232
  }
1107
1108
  // Remove the delimiter (if any) that separates the teaser from the
body.
1109241
  $node->body = isset($node->body) ? str_replace('<!--break-->', '',
$node->body) : '';
1110
1111
  // The 'view' hook can be implemented to overwrite the default function
1112
  // to display nodes.
1113241
  if (node_hook($node, 'view')) {
111418
    $node = node_invoke($node, 'view', $teaser, $page);
111517
  }
1116
  else {
1117223
    $node = node_prepare($node, $teaser);
1118
  }
1119
1120
  // Allow modules to make their own additions to the node.
1121240
  node_invoke_nodeapi($node, 'view', $teaser, $page);
1122
1123240
  return $node;
11240
}
1125
1126
/**
1127
 * Generate a page displaying a single node, along with its comments.
1128
 */
11292027
function node_show($node, $cid, $message = FALSE) {
1130169
  if ($message) {
11311
    drupal_set_title(t('Revision of %title from %date', array('%title' =>
$node->title, '%date' => format_date($node->revision_timestamp))));
11321
  }
1133169
  $output = node_view($node, FALSE, TRUE);
1134
1135168
  if (function_exists('comment_render') && $node->comment) {
1136140
    $output .= comment_render($node, $cid);
1137140
  }
1138
1139
  // Update the history table, stating that this user viewed this node.
1140168
  node_tag_new($node->nid);
1141
1142168
  return $output;
11430
}
1144
1145
/**
1146
 * Theme a log message.
1147
 *
1148
 * @ingroup themeable
1149
 */
11502027
function theme_node_log_message($log) {
11510
  return '<div class="log"><div class="title">' . t('Log') . ':</div>' .
$log . '</div>';
11520
}
1153
1154
/**
1155
 * Implementation of hook_perm().
1156
 */
11572027
function node_perm() {
1158
  $perms = array(
115987
    'administer content types' => t('Manage content types and content type
administration settings.'),
116087
    'administer nodes' => t('Manage all website content, and bypass any
content-related access control. %warning', array('%warning' => t('Warning:
Give to trusted roles only; this permission has security
implications.'))),
116187
    'access content' => t('View published content.'),
116287
    'view revisions' => t('View content revisions.'),
116387
    'revert revisions' => t('Replace content with an older revision.'),
116487
    'delete revisions' => t('Delete content revisions.'),
116587
  );
1166
116787
  foreach (node_get_types() as $type) {
116887
    if ($type->module == 'node') {
116987
      $perms += node_list_permissions($type);
117087
    }
117187
  }
1172
117387
  return $perms;
11740
}
1175
1176
/**
1177
 * Gather the rankings from the the hook_ranking implementations.
1178
 */
11792027
function _node_rankings() {
1180
  $rankings = array(
11812
    'total' => 0, 'join' => array(), 'score' => array(), 'args' =>
array(),
11822
  );
11832
  if ($ranking = module_invoke_all('ranking')) {
11842
    foreach ($ranking as $rank => $values) {
11852
      if ($node_rank = variable_get('node_rank_'. $rank, 0)) {
1186
        // If the table defined in the ranking isn't already joined, then
add it.
11871
        if (isset($values['join']) &&
!isset($rankings['join'][$values['join']])) {
11881
          $rankings['join'][$values['join']] = $values['join'];
11891
        }
1190
1191
        // Add the rankings weighted score multiplier value, handling NULL
gracefully.
11921
        $rankings['score'][] = '%f * COALESCE(('. $values['score'] .'),
0)';
1193
1194
        // Add the the administrator's weighted score multiplier value for
this ranking.
11951
        $rankings['total'] += $node_rank;
11961
        $rankings['arguments'][] = $node_rank;
1197
1198
        // Add any additional arguments used by this ranking.
11991
        if (isset($values['arguments'])) {
12001
          $rankings['arguments'] = array_merge($rankings['arguments'],
$values['arguments']);
12011
        }
12021
      }
12032
    }
12042
  }
12052
  return $rankings;
12060
}
1207
1208
1209
/**
1210
 * Implementation of hook_search().
1211
 */
12122027
function node_search($op = 'search', $keys = NULL) {
1213
  switch ($op) {
12144
    case 'name':
12153
      return t('Content');
1216
12172
    case 'reset':
12180
      db_query("UPDATE {search_dataset} SET reindex = %d WHERE type =
'node'", time());
12190
      return;
1220
12212
    case 'status':
12220
      $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status
= 1'));
12230
      $remaining = db_result(db_query("SELECT COUNT(*) FROM {node} n LEFT
JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE n.status
= 1 AND d.sid IS NULL OR d.reindex <> 0"));
12240
      return array('remaining' => $remaining, 'total' => $total);
1225
12262
    case 'admin':
12270
      $form = array();
1228
      // Output form for defining rank factor weights.
12290
      $form['content_ranking'] = array(
12300
        '#type' => 'fieldset',
12310
        '#title' => t('Content ranking'),
1232
      );
12330
      $form['content_ranking']['#theme'] = 'node_search_admin';
12340
      $form['content_ranking']['info'] = array(
12350
        '#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>'
12360
      );
1237
1238
      // Note: reversed to reflect that higher number = higher ranking.
12390
      $options = drupal_map_assoc(range(0, 10));
12400
      foreach (module_invoke_all('ranking') as $var => $values) {
12410
        $form['content_ranking']['factors']['node_rank_'. $var] = array(
12420
          '#title' => $values['title'],
12430
          '#type' => 'select',
12440
          '#options' => $options,
12450
          '#default_value' => variable_get('node_rank_'. $var, 0),
1246
        );
12470
      }
12480
      return $form;
1249
12502
    case 'search':
1251
      // Build matching conditions
12522
      list($join1, $where1) = _db_rewrite_sql();
12532
      $arguments1 = array();
12542
      $conditions1 = 'n.status = 1';
1255
12562
      if ($type = search_query_extract($keys, 'type')) {
12570
        $types = array();
12580
        foreach (explode(',', $type) as $t) {
12590
          $types[] = "n.type = '%s'";
12600
          $arguments1[] = $t;
12610
        }
12620
        $conditions1 .= ' AND (' . implode(' OR ', $types) . ')';
12630
        $keys = search_query_insert($keys, 'type');
12640
      }
1265
12662
      if ($category = search_query_extract($keys, 'category')) {
12670
        $categories = array();
12680
        foreach (explode(',', $category) as $c) {
12690
          $categories[] = "tn.tid = %d";
12700
          $arguments1[] = $c;
12710
        }
12720
        $conditions1 .= ' AND (' . implode(' OR ', $categories) . ')';
12730
        $join1 .= ' INNER JOIN {term_node} tn ON n.vid = tn.vid';
12740
        $keys = search_query_insert($keys, 'category');
12750
      }
1276
12772
      if ($languages = search_query_extract($keys, 'language')) {
12780
        $categories = array();
12790
        foreach (explode(',', $languages) as $l) {
12800
          $categories[] = "n.language = '%s'";
12810
          $arguments1[] = $l;
12820
        }
12830
        $conditions1 .= ' AND (' . implode(' OR ', $categories) . ')';
12840
        $keys = search_query_insert($keys, 'language');
12850
      }
1286
1287
      // Get the ranking expressions.
12882
      $rankings = _node_rankings();
1289
1290
      // When all search factors are disabled (ie they have a weight of
zero),
1291
      // The default score is based only on keyword relevance.
12922
      if ($rankings['total'] == 0) {
12931
        $total = 1;
12941
        $arguments2 = array();
12951
        $join2 = '';
12961
        $select2 = 'i.relevance AS score';
12971
      }
1298
      else {
12991
        $total = $rankings['total'];
13001
        $arguments2 = $rankings['arguments'];
13011
        $join2 = implode(' ', $rankings['join']);
13021
        $select2 = '('. implode(' + ', $rankings['score']) .') AS score';
1303
      }
1304
1305
      // Do search.
13062
      $find = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid =
i.sid ' . $join1, $conditions1 . (empty($where1) ? '' : ' AND ' . $where1),
$arguments1, $select2, $join2, $arguments2);
1307
1308
      // Load results.
13092
      $results = array();
13102
      foreach ($find as $item) {
1311
        // Build the node body.
13121
        $node = node_load($item->sid);
13131
        $node->build_mode = NODE_BUILD_SEARCH_RESULT;
13141
        $node = node_build_content($node, FALSE, FALSE);
13151
        $node->body = drupal_render($node->content);
1316
1317
        // Fetch comments for snippet.
13181
        $node->body .= module_invoke('comment', 'nodeapi', $node, 'update
index');
1319
        // Fetch terms for snippet.
13201
        $node->body .= module_invoke('taxonomy', 'nodeapi', $node, 'update
index');
1321
13221
        $extra = node_invoke_nodeapi($node, 'search result');
1323
13241
        $results[] = array(
13251
          'link' => url('node/' . $item->sid, array('absolute' => TRUE)),
13261
          'type' => check_plain(node_get_types('name', $node)),
13271
          'title' => $node->title,
13281
          'user' => theme('username', $node),
13291
          'date' => $node->changed,
13301
          'node' => $node,
13311
          'extra' => $extra,
13321
          'score' => $total ? ($item->score / $total) : 0,
13331
          'snippet' => search_excerpt($keys, $node->body),
1334
        );
13351
      }
13362
      return $results;
13370
  }
13380
}
1339
1340
/**
1341
 * Implementation of hook_ranking().
1342
 */
13432027
function node_ranking() {
1344
  // Create the ranking array and add the basic ranking options.
1345
  $ranking = array(
1346
    'relevance' => array(
13472
      'title' => t('Keyword relevance'),
1348
      // Average relevance values hover around 0.15
13492
      'score' => 'i.relevance',
13502
    ),
1351
    'sticky' => array(
13522
      'title' => t('Content is sticky at top of lists'),
1353
      // The sticky flag is either 0 or 1, which is automatically
normalized.
13542
      'score' => 'n.sticky',
13552
    ),
1356
    'promote' => array(
13572
      'title' => t('Content is promoted to the front page'),
1358
      // The promote flag is either 0 or 1, which is automatically
normalized.
13592
      'score' => 'n.promote',
13602
    ),
13612
  );
1362
1363
  // Add relevance based on creation or changed date.
13642
  if ($node_cron_last = variable_get('node_cron_last', 0)) {
13651
    $ranking['recent'] = array(
13661
      'title' => t('Recently posted'),
1367
      // Exponential decay with half-life of 6 months, starting at last
indexed node
13681
      'score' => '(POW(2, GREATEST(n.created, n.changed) - %d) *
6.43e-8)',
13691
      'arguments' => array($node_cron_last),
1370
    );
13711
  }
13722
  return $ranking;
13730
}
1374
1375
/**
1376
 * Implementation of hook_user().
1377
 */
13782027
function node_user($op, &$edit, &$user) {
13791848
  if ($op == 'delete') {
13802
    db_query('UPDATE {node} SET uid = 0 WHERE uid = %d', $user->uid);
13812
    db_query('UPDATE {node_revisions} SET uid = 0 WHERE uid = %d',
$user->uid);
13822
  }
13831848
}
1384
1385
/**
1386
 * Theme the content ranking part of the search settings admin page.
1387
 *
1388
 * @ingroup themeable
1389
 */
13902027
function theme_node_search_admin($form) {
13910
  $output = drupal_render($form['info']);
1392
13930
  $header = array(t('Factor'), t('Weight'));
13940
  foreach (element_children($form['factors']) as $key) {
13950
    $row = array();
13960
    $row[] = $form['factors'][$key]['#title'];
13970
    unset($form['factors'][$key]['#title']);
13980
    $row[] = drupal_render($form['factors'][$key]);
13990
    $rows[] = $row;
14000
  }
14010
  $output .= theme('table', $header, $rows);
1402
14030
  $output .= drupal_render($form);
14040
  return $output;
14050
}
1406
1407
/**
1408
 * Retrieve the comment mode for the given node ID (none, read, or
read/write).
1409
 */
14102027
function node_comment_mode($nid) {
1411169
  static $comment_mode;
1412169
  if (!isset($comment_mode[$nid])) {
1413169
    $comment_mode[$nid] = db_result(db_query('SELECT comment FROM {node}
WHERE nid = %d', $nid));
1414169
  }
1415169
  return $comment_mode[$nid];
14160
}
1417
1418
/**
1419
 * Implementation of hook_link().
1420
 */
14212027
function node_link($type, $node = NULL, $teaser = FALSE) {
1422231
  $links = array();
1423
1424231
  if ($type == 'node') {
1425231
    if ($teaser == 1 && $node->teaser && !empty($node->readmore)) {
14260
      $links['node_read_more'] = array(
14270
        'title' => t('Read more'),
14280
        'href' => "node/$node->nid",
1429
        // The title attribute gets escaped when the links are processed,
so
1430
        // there is no need to escape here.
14310
        'attributes' => array('title' => t('Read the rest of !title.',
array('!title' => $node->title)))
14320
      );
14330
    }
1434231
  }
1435
1436231
  return $links;
14370
}
1438
14392027
function _node_revision_access($node, $op = 'view') {
1440214
  static $access = array();
1441214
  if (!isset($access[$node->vid])) {
1442214
    $node_current_revision = node_load($node->nid);
1443214
    $is_current_revision = $node_current_revision->vid == $node->vid;
1444
    // There should be at least two revisions. If the vid of the given
node
1445
    // and the vid of the current revision differs, then we already have
two
1446
    // different revisions so there is no need for a separate database
check.
1447
    // Also, if you try to revert to or delete the current revision,
that's
1448
    // not good.
1449214
    if ($is_current_revision && (db_result(db_query('SELECT COUNT(vid) FROM
{node_revisions} WHERE nid = %d', $node->nid)) == 1 || $op == 'update' ||
$op == 'delete')) {
1450206
      $access[$node->vid] = FALSE;
1451206
    }
14528
    elseif (user_access('administer nodes')) {
14530
      $access[$node->vid] = TRUE;
14540
    }
1455
    else {
14568
      $map = array('view' => 'view revisions', 'update' => 'revert
revisions', 'delete' => 'delete revisions');
1457
      // First check the user permission, second check the access to the
1458
      // current revision and finally, if the node passed in is not the
current
1459
      // revision then access to that, too.
14608
      $access[$node->vid] = isset($map[$op]) && user_access($map[$op]) &&
node_access($op, $node_current_revision) && ($is_current_revision ||
node_access($op, $node));
1461
    }
1462214
  }
1463214
  return $access[$node->vid];
14640
}
1465
14662027
function _node_add_access() {
14671498
  $types = node_get_types();
14681498
  foreach ($types as $type) {
14691498
    if (node_hook($type->type, 'form') && node_access('create',
$type->type)) {
1470439
      return TRUE;
14710
    }
14721181
  }
14731059
  return FALSE;
14740
}
1475
1476
/**
1477
 * Implementation of hook_menu().
1478
 */
14792027
function node_menu() {
148085
  $items['admin/content/node'] = array(
148185
    'title' => 'Content',
148285
    'description' => "View, edit, and delete your site's content.",
148385
    'page callback' => 'drupal_get_form',
148485
    'page arguments' => array('node_admin_content'),
148585
    'access arguments' => array('administer nodes'),
1486
  );
1487
148885
  $items['admin/content/node/overview'] = array(
148985
    'title' => 'List',
149085
    'type' => MENU_DEFAULT_LOCAL_TASK,
149185
    'weight' => -10,
1492
  );
1493
149485
  $items['admin/content/node-settings'] = array(
149585
    'title' => 'Post settings',
149685
    'description' => 'Control posting behavior, such as teaser length,
requiring previews before posting, and the number of posts on the front
page.',
149785
    'page callback' => 'drupal_get_form',
149885
    'page arguments' => array('node_configure'),
149985
    'access arguments' => array('administer nodes'),
1500
  );
150185
  $items['admin/content/node-settings/rebuild'] = array(
150285
    'title' => 'Rebuild permissions',
150385
    'page arguments' => array('node_configure_rebuild_confirm'),
1504
    // Any user than can potentially trigger a
node_acess_needs_rebuild(TRUE)
1505
    // has to be allowed access to the 'node access rebuild' confirm form.
150685
    'access arguments' => array('access administration pages'),
150785
    'type' => MENU_CALLBACK,
1508
  );
1509
151085
  $items['admin/build/types'] = array(
151185
    'title' => 'Content types',
151285
    'description' => 'Manage posts by content type, including default
status, front page promotion, etc.',
151385
    'page callback' => 'node_overview_types',
151485
    'access arguments' => array('administer content types'),
1515
  );
151685
  $items['admin/build/types/list'] = array(
151785
    'title' => 'List',
151885
    'type' => MENU_DEFAULT_LOCAL_TASK,
151985
    'weight' => -10,
1520
  );
152185
  $items['admin/build/types/add'] = array(
152285
    'title' => 'Add content type',
152385
    'page callback' => 'drupal_get_form',
152485
    'page arguments' => array('node_type_form'),
152585
    'access arguments' => array('administer content types'),
152685
    'type' => MENU_LOCAL_TASK,
1527
  );
152885
  $items['node'] = array(
152985
    'title' => 'Content',
153085
    'page callback' => 'node_page_default',
153185
    'access arguments' => array('access content'),
153285
    'type' => MENU_CALLBACK,
1533
  );
153485
  $items['node/add'] = array(
153585
    'title' => 'Create content',
153685
    'page callback' => 'node_add_page',
153785
    'access callback' => '_node_add_access',
153885
    'weight' => 1,
1539
  );
154085
  $items['rss.xml'] = array(
154185
    'title' => 'RSS feed',
154285
    'page callback' => 'node_feed',
154385
    'access arguments' => array('access content'),
154485
    'type' => MENU_CALLBACK,
1545
  );
154685
  foreach (node_get_types('types', NULL, TRUE) as $type) {
154785
    $type_url_str = str_replace('_', '-', $type->type);
154885
    $items['node/add/' . $type_url_str] = array(
154985
      'title' => drupal_ucfirst($type->name),
155085
      'title callback' => 'check_plain',
155185
      'page callback' => 'node_add',
155285
      'page arguments' => array(2),
155385
      'access callback' => 'node_access',
155485
      'access arguments' => array('create', $type->type),
155585
      'description' => $type->description,
1556
    );
155785
    $items['admin/build/node-type/' . $type_url_str] = array(
155885
      'title' => $type->name,
155985
      'page callback' => 'drupal_get_form',
156085
      'page arguments' => array('node_type_form', $type),
156185
      'access arguments' => array('administer content types'),
156285
      'type' => MENU_CALLBACK,
1563
    );
156485
    $items['admin/build/node-type/' . $type_url_str . '/edit'] = array(
156585
      'title' => 'Edit',
156685
      'type' => MENU_DEFAULT_LOCAL_TASK,
1567
    );
156885
    $items['admin/build/node-type/' . $type_url_str . '/delete'] = array(
156985
      'title' => 'Delete',
157085
      'page arguments' => array('node_type_delete_confirm', $type),
157185
      'access arguments' => array('administer content types'),
157285
      'type' => MENU_CALLBACK,
1573
    );
157485
  }
157585
  $items['node/%node'] = array(
157685
    'title callback' => 'node_page_title',
157785
    'title arguments' => array(1),
157885
    'page callback' => 'node_page_view',
157985
    'page arguments' => array(1),
158085
    'access callback' => 'node_access',
158185
    'access arguments' => array('view', 1),
158285
    'type' => MENU_CALLBACK);
158385
  $items['node/%node/view'] = array(
158485
    'title' => 'View',
158585
    'type' => MENU_DEFAULT_LOCAL_TASK,
158685
    'weight' => -10);
158785
  $items['node/%node/edit'] = array(
158885
    'title' => 'Edit',
158985
    'page callback' => 'node_page_edit',
159085
    'page arguments' => array(1),
159185
    'access callback' => 'node_access',
159285
    'access arguments' => array('update', 1),
159385
    'weight' => 1,
159485
    'type' => MENU_LOCAL_TASK,
1595
  );
159685
  $items['node/%node/delete'] = array(
159785
    'title' => 'Delete',
159885
    'page callback' => 'drupal_get_form',
159985
    'page arguments' => array('node_delete_confirm', 1),
160085
    'access callback' => 'node_access',
160185
    'access arguments' => array('delete', 1),
160285
    'weight' => 1,
160385
    'type' => MENU_CALLBACK);
160485
  $items['node/%node/revisions'] = array(
160585
    'title' => 'Revisions',
160685
    'page callback' => 'node_revision_overview',
160785
    'page arguments' => array(1),
160885
    'access callback' => '_node_revision_access',
160985
    'access arguments' => array(1),
161085
    'weight' => 2,
161185
    'type' => MENU_LOCAL_TASK,
1612
  );
161385
  $items['node/%node/revisions/%/view'] = array(
161485
    'title' => 'Revisions',
161585
    'load arguments' => array(3),
161685
    'page callback' => 'node_show',
161785
    'page arguments' => array(1, NULL, TRUE),
161885
    'access callback' => '_node_revision_access',
161985
    'access arguments' => array(1),
162085
    'type' => MENU_CALLBACK,
1621
  );
162285
  $items['node/%node/revisions/%/revert'] = array(
162385
    'title' => 'Revert to earlier revision',
162485
    'load arguments' => array(3),
162585
    'page callback' => 'drupal_get_form',
162685
    'page arguments' => array('node_revision_revert_confirm', 1),
162785
    'access callback' => '_node_revision_access',
162885
    'access arguments' => array(1, 'update'),
162985
    'type' => MENU_CALLBACK,
1630
  );
163185
  $items['node/%node/revisions/%/delete'] = array(
163285
    'title' => 'Delete earlier revision',
163385
    'load arguments' => array(3),
163485
    'page callback' => 'drupal_get_form',
163585
    'page arguments' => array('node_revision_delete_confirm', 1),
163685
    'access callback' => '_node_revision_access',
163785
    'access arguments' => array(1, 'delete'),
163885
    'type' => MENU_CALLBACK,
1639
  );
164085
  return $items;
16410
}
1642
1643
/**
1644
 * Title callback.
1645
 */
16462027
function node_page_title($node) {
1647229
  return $node->title;
16480
}
1649
1650
/**
1651
 * Implementation of hook_init().
1652
 */
16532027
function node_init() {
16542027
  drupal_add_css(drupal_get_path('module', 'node') . '/node.css');
16552027
}
1656
16572027
function node_last_changed($nid) {
165829
  $node = db_fetch_object(db_query('SELECT changed FROM {node} WHERE nid =
%d', $nid));
165929
  return ($node->changed);
16600
}
1661
1662
/**
1663
 * Return a list of all the existing revision numbers.
1664
 */
16652027
function node_revision_list($node) {
16663
  $revisions = array();
16673
  $result = db_query('SELECT r.vid, r.title, r.log, r.uid, n.vid AS
current_vid, r.timestamp, u.name FROM {node_revisions} r LEFT JOIN {node} n
ON n.vid = r.vid INNER JOIN {users} u ON u.uid = r.uid WHERE r.nid = %d
ORDER BY r.timestamp DESC', $node->nid);
16683
  while ($revision = db_fetch_object($result)) {
16693
    $revisions[$revision->vid] = $revision;
16703
  }
1671
16723
  return $revisions;
16730
}
1674
1675
/**
1676
 * Implementation of hook_block().
1677
 */
16782027
function node_block($op = 'list', $delta = '') {
167928
  if ($op == 'list') {
168028
    $blocks['syndicate']['info'] = t('Syndicate');
1681
    // Not worth caching.
168228
    $blocks['syndicate']['cache'] = BLOCK_NO_CACHE;
168328
    return $blocks;
16840
  }
16850
  else if ($op == 'view') {
16860
    $block['subject'] = t('Syndicate');
16870
    $block['content'] = theme('feed_icon', url('rss.xml'),
t('Syndicate'));
1688
16890
    return $block;
16900
  }
16910
}
1692
1693
/**
1694
 * A generic function for generating RSS feeds from a set of nodes.
1695
 *
1696
 * @param $nids
1697
 *   An array of node IDs (nid). Defaults to FALSE so empty feeds can be
1698
 *   generated with passing an empty array, if no items are to be added
1699
 *   to the feed.
1700
 * @param $channel
1701
 *   An associative array containing title, link, description and other
keys.
1702
 *   The link should be an absolute URL.
1703
 */
17042027
function node_feed($nids = FALSE, $channel = array()) {
170510
  global $base_url, $language;
1706
170710
  if ($nids === FALSE) {
17086
    $nids = array();
17096
    $result = db_query_range(db_rewrite_sql('SELECT n.nid, n.created FROM
{node} n WHERE n.promote = 1 AND n.status = 1 ORDER BY n.created DESC'), 0,
variable_get('feed_default_items', 10));
17106
    while ($row = db_fetch_object($result)) {
17110
      $nids[] = $row->nid;
17120
    }
17136
  }
1714
171510
  $item_length = variable_get('feed_item_length', 'teaser');
171610
  $namespaces = array('xmlns:dc' => 'http://purl.org/dc/elements/1.1/');
1717
171810
  $items = '';
171910
  foreach ($nids as $nid) {
1720
    // Load the specified node:
17210
    $item = node_load($nid);
17220
    $item->build_mode = NODE_BUILD_RSS;
17230
    $item->link = url("node/$nid", array('absolute' => TRUE));
1724
17250
    if ($item_length != 'title') {
17260
      $teaser = ($item_length == 'teaser') ? TRUE : FALSE;
1727
1728
      // Filter and prepare node teaser
17290
      if (node_hook($item, 'view')) {
17300
        $item = node_invoke($item, 'view', $teaser, FALSE);
17310
      }
1732
      else {
17330
        $item = node_prepare($item, $teaser);
1734
      }
1735
1736
      // Allow modules to change $node->teaser before viewing.
17370
      node_invoke_nodeapi($item, 'view', $teaser, FALSE);
17380
    }
1739
1740
    // Allow modules to add additional item fields and/or modify $item
17410
    $extra = node_invoke_nodeapi($item, 'rss item');
17420
    $extra = array_merge($extra, array(array('key' => 'pubDate', 'value' =>
gmdate('r', $item->created)), array('key' => 'dc:creator', 'value' =>
$item->name), array('key' => 'guid', 'value' => $item->nid . ' at ' .
$base_url, 'attributes' => array('isPermaLink' => 'false'))));
17430
    foreach ($extra as $element) {
17440
      if (isset($element['namespace'])) {
17450
        $namespaces = array_merge($namespaces, $element['namespace']);
17460
      }
17470
    }
1748
1749
    // Prepare the item description
1750
    switch ($item_length) {
17510
      case 'fulltext':
17520
        $item_text = $item->body;
17530
        break;
17540
      case 'teaser':
17550
        $item_text = $item->teaser;
17560
        if (!empty($item->readmore)) {
17570
          $item_text .= '<p>' . l(t('read more'), 'node/' . $item->nid,
array('absolute' => TRUE, 'attributes' => array('target' => '_blank'))) .
'</p>';
17580
        }
17590
        break;
17600
      case 'title':
17610
        $item_text = '';
17620
        break;
17630
    }
1764
17650
    $items .= format_rss_item($item->title, $item->link, $item_text,
$extra);
17660
  }
1767
1768
  $channel_defaults = array(
176910
    'version'     => '2.0',
177010
    'title'       => variable_get('site_name', 'Drupal'),
177110
    'link'        => $base_url,
177210
    'description' => variable_get('site_mission', ''),
177310
    'language'    => $language->language
177410
  );
177510
  $channel = array_merge($channel_defaults, $channel);
1776
177710
  $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
177810
  $output .= "<rss version=\"" . $channel["version"] . "\" xml:base=\"" .
$base_url . "\" " . drupal_attributes($namespaces) . ">\n";
177910
  $output .= format_rss_channel($channel['title'], $channel['link'],
$channel['description'], $items, $channel['language']);
178010
  $output .= "</rss>\n";
1781
178210
  drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
178310
  print $output;
178410
}
1785
1786
/**
1787
 * Menu callback; Generate a listing of promoted nodes.
1788
 */
17892027
function node_page_default() {
1790204
  $result = pager_query(db_rewrite_sql('SELECT n.nid, n.sticky, n.created
FROM {node} n WHERE n.promote = 1 AND n.status = 1 ORDER BY n.sticky DESC,
n.created DESC'), variable_get('default_nodes_main', 10));
1791
1792204
  $output = '';
1793204
  $num_rows = FALSE;
1794204
  while ($node = db_fetch_object($result)) {
179527
    $output .= node_view(node_load($node->nid), 1);
179627
    $num_rows = TRUE;
179727
  }
1798
1799204
  if ($num_rows) {
180027
    $feed_url = url('rss.xml', array('absolute' => TRUE));
180127
    drupal_add_feed($feed_url, variable_get('site_name', 'Drupal') . ' ' .
t('RSS'));
180227
    $output .= theme('pager', NULL, variable_get('default_nodes_main',
10));
180327
  }
1804
  else {
1805177
    $default_message = '<h1 class="title">' . t('Welcome to your new Drupal
website!') . '</h1>';
1806177
    $default_message .= '<p>' . t('Please follow these steps to set up and
start using your website:') . '</p>';
1807177
    $default_message .= '<ol>';
1808177
    $default_message .= '<li>' . t('<strong>Configure your website</strong>
Once logged in, visit the <a href="@admin">administration section</a>,
where you can <a href="@config">customize and configure</a> all aspects of
your website.', array('@admin' => url('admin'), '@config' =>
url('admin/settings'))) . '</li>';
1809177
    $default_message .= '<li>' . t('<strong>Enable additional
functionality</strong> Next, visit the <a href="@modules">module list</a>
and enable features which suit your specific needs. You can find additional
modules in the <a href="@download_modules">Drupal modules download
section</a>.', array('@modules' => url('admin/build/modules'),
'@download_modules' => 'http://drupal.org/project/modules')) . '</li>';
1810177
    $default_message .= '<li>' . t('<strong>Customize your website
design</strong> To change the "look and feel" of your website, visit the <a
href="@themes">themes section</a>. You may choose from one of the included
themes or download additional themes from the <a
href="@download_themes">Drupal themes download section</a>.',
array('@themes' => url('admin/build/themes'), '@download_themes' =>
'http://drupal.org/project/themes')) . '</li>';
1811177
    $default_message .= '<li>' . t('<strong>Start posting content</strong>
Finally, you can <a href="@content">create content</a> for your website.
This message will disappear once you have promoted a post to the front
page.', array('@content' => url('node/add'))) . '</li>';
1812177
    $default_message .= '</ol>';
1813177
    $default_message .= '<p>' . t('For more information, please refer to
the <a href="@help">help section</a>, or the <a href="@handbook">online
Drupal handbooks</a>. You may also post at the <a href="@forum">Drupal
forum</a>, or view the wide range of <a href="@support">other support
options</a> available.', array('@help' => url('admin/help'), '@handbook' =>
'http://drupal.org/handbooks', '@forum' => 'http://drupal.org/forum',
'@support' => 'http://drupal.org/support')) . '</p>';
1814
1815177
    $output = '<div id="first-time">' . $default_message . '</div>';
1816
  }
1817204
  drupal_set_title('');
1818
1819204
  return $output;
18200
}
1821
1822
/**
1823
 * Menu callback; view a single node.
1824
 */
18252027
function node_page_view($node, $cid = NULL) {
1826168
  drupal_set_title(check_plain($node->title));
1827168
  return node_show($node, $cid);
18280
}
1829
1830
/**
1831
 * Implementation of hook_update_index().
1832
 */
18332027
function node_update_index() {
18341
  $limit = (int)variable_get('search_cron_limit', 100);
1835
1836
  // Store the maximum possible comments per thread (used for ranking by
reply count)
18371
  variable_set('node_cron_comments_scale', 1.0 / max(1,
db_result(db_query('SELECT MAX(comment_count) FROM
{node_comment_statistics}'))));
18381
  variable_set('node_cron_views_scale', 1.0 / max(1,
db_result(db_query('SELECT MAX(totalcount) FROM {node_counter}'))));
1839
18401
  $result = db_query_range("SELECT n.nid FROM {node} n LEFT JOIN
{search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL
OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, $limit);
1841
18421
  while ($node = db_fetch_object($result)) {
18431
    _node_index_node($node);
18441
  }
18451
}
1846
1847
/**
1848
 * Index a single node.
1849
 *
1850
 * @param $node
1851
 *   The node to index.
1852
 */
18532027
function _node_index_node($node) {
18541
  $node = node_load($node->nid);
1855
1856
  // save the changed time of the most recent indexed node, for the search
results half-life calculation
18571
  variable_set('node_cron_last', $node->changed);
1858
1859
  // Build the node body.
18601
  $node->build_mode = NODE_BUILD_SEARCH_INDEX;
18611
  $node = node_build_content($node, FALSE, FALSE);
18621
  $node->body = drupal_render($node->content);
1863
18641
  $text = '<h1>' . check_plain($node->title) . '</h1>' . $node->body;
1865
1866
  // Fetch extra data normally not visible
18671
  $extra = node_invoke_nodeapi($node, 'update index');
18681
  foreach ($extra as $t) {
18691
    $text .= $t;
18701
  }
1871
1872
  // Update index
18731
  search_index($node->nid, 'node', $text);
18741
}
1875
1876
/**
1877
 * Implementation of hook_form_alter().
1878
 */
18792027
function node_form_alter(&$form, $form_state, $form_id) {
1880
  // Advanced node search form
18811338
  if ($form_id == 'search_form' && $form['module']['#value'] == 'node' &&
user_access('use advanced search')) {
1882
    // Keyword boxes:
18830
    $form['advanced'] = array(
18840
      '#type' => 'fieldset',
18850
      '#title' => t('Advanced search'),
18860
      '#collapsible' => TRUE,
18870
      '#collapsed' => TRUE,
18880
      '#attributes' => array('class' => 'search-advanced'),
1889
    );
18900
    $form['advanced']['keywords'] = array(
18910
      '#prefix' => '<div class="criterion">',
18920
      '#suffix' => '</div>',
1893
    );
18940
    $form['advanced']['keywords']['or'] = array(
18950
      '#type' => 'textfield',
18960
      '#title' => t('Containing any of the words'),
18970
      '#size' => 30,
18980
      '#maxlength' => 255,
1899
    );
19000
    $form['advanced']['keywords']['phrase'] = array(
19010
      '#type' => 'textfield',
19020
      '#title' => t('Containing the phrase'),
19030
      '#size' => 30,
19040
      '#maxlength' => 255,
1905
    );
19060
    $form['advanced']['keywords']['negative'] = array(
19070
      '#type' => 'textfield',
19080
      '#title' => t('Containing none of the words'),
19090
      '#size' => 30,
19100
      '#maxlength' => 255,
1911
    );
1912
1913
    // Taxonomy box:
19140
    if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
19150
      $form['advanced']['category'] = array(
19160
        '#type' => 'select',
19170
        '#title' => t('Only in the category(s)'),
19180
        '#prefix' => '<div class="criterion">',
19190
        '#size' => 10,
19200
        '#suffix' => '</div>',
19210
        '#options' => $taxonomy,
19220
        '#multiple' => TRUE,
1923
      );
19240
    }
1925
1926
    // Node types:
19270
    $types = array_map('check_plain', node_get_types('names'));
19280
    $form['advanced']['type'] = array(
19290
      '#type' => 'checkboxes',
19300
      '#title' => t('Only of the type(s)'),
19310
      '#prefix' => '<div class="criterion">',
19320
      '#suffix' => '</div>',
19330
      '#options' => $types,
1934
    );
19350
    $form['advanced']['submit'] = array(
19360
      '#type' => 'submit',
19370
      '#value' => t('Advanced search'),
19380
      '#prefix' => '<div class="action">',
19390
      '#suffix' => '</div>',
1940
    );
1941
1942
    // Languages:
19430
    $language_options = array();
19440
    foreach (language_list('language') as $key => $object) {
19450
      $language_options[$key] = $object->name;
19460
    }
19470
    if (count($language_options) > 1) {
19480
      $form['advanced']['language'] = array(
19490
        '#type' => 'checkboxes',
19500
        '#title' => t('Languages'),
19510
        '#prefix' => '<div class="criterion">',
19520
        '#suffix' => '</div>',
19530
        '#options' => $language_options,
1954
      );
19550
    }
1956
1957
19580
    $form['#validate'][] = 'node_search_validate';
19590
  }
19601338
}
1961
1962
/**
1963
 * Form API callback for the search form. Registered in node_form_alter().
1964
 */
19652027
function node_search_validate($form, &$form_state) {
1966
  // Initialise using any existing basic search keywords.
19670
  $keys = $form_state['values']['processed_keys'];
1968
1969
  // Insert extra restrictions into the search keywords string.
19700
  if (isset($form_state['values']['type']) &&
is_array($form_state['values']['type'])) {
1971
    // Retrieve selected types - Forms API sets the value of unselected
checkboxes to 0.
19720
    $form_state['values']['type'] =
array_filter($form_state['values']['type']);
19730
    if (count($form_state['values']['type'])) {
19740
      $keys = search_query_insert($keys, 'type', implode(',',
array_keys($form_state['values']['type'])));
19750
    }
19760
  }
1977
19780
  if (isset($form_state['values']['category']) &&
is_array($form_state['values']['category'])) {
19790
    $keys = search_query_insert($keys, 'category', implode(',',
$form_state['values']['category']));
19800
  }
19810
  if (isset($form_state['values']['language']) &&
is_array($form_state['values']['language'])) {
19820
    $keys = search_query_insert($keys, 'language', implode(',',
array_filter($form_state['values']['language'])));
19830
  }
19840
  if ($form_state['values']['or'] != '') {
19850
    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' .
$form_state['values']['or'], $matches)) {
19860
      $keys .= ' ' . implode(' OR ', $matches[1]);
19870
    }
19880
  }
19890
  if ($form_state['values']['negative'] != '') {
19900
    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' .
$form_state['values']['negative'], $matches)) {
19910
      $keys .= ' -' . implode(' -', $matches[1]);
19920
    }
19930
  }
19940
  if ($form_state['values']['phrase'] != '') {
19950
    $keys .= ' "' . str_replace('"', ' ', $form_state['values']['phrase'])
. '"';
19960
  }
19970
  if (!empty($keys)) {
19980
    form_set_value($form['basic']['inline']['processed_keys'], trim($keys),
$form_state);
19990
  }
20000
}
2001
2002
/**
2003
 * @defgroup node_access Node access rights
2004
 * @{
2005
 * The node access system determines who can do what to which nodes.
2006
 *
2007
 * In determining access rights for a node, node_access() first checks
2008
 * whether the user has the "administer nodes" permission. Such users have
2009
 * unrestricted access to all nodes. Then the node module's hook_access()
2010
 * is called, and a TRUE or FALSE return value will grant or deny access.
2011
 * This allows, for example, the blog module to always grant access to the
2012
 * blog author, and for the book module to always deny editing access to
2013
 * PHP pages.
2014
 *
2015
 * If node module does not intervene (returns NULL), then the
2016
 * node_access table is used to determine access. All node access
2017
 * modules are queried using hook_node_grants() to assemble a list of
2018
 * "grant IDs" for the user. This list is compared against the table.
2019
 * If any row contains the node ID in question (or 0, which stands for
"all
2020
 * nodes"), one of the grant IDs returned, and a value of TRUE for the
2021
 * operation in question, then access is granted. Note that this table is
a
2022
 * list of grants; any matching row is sufficient to grant access to the
2023
 * node.
2024
 *
2025
 * In node listings, the process above is followed except that
2026
 * hook_access() is not called on each node for performance reasons and
for
2027
 * proper functioning of the pager system. When adding a node listing to
your
2028
 * module, be sure to use db_rewrite_sql() to add
2029
 * the appropriate clauses to your query for access checks.
2030
 *
2031
 * To see how to write a node access module of your own, see
2032
 * node_access_example.module.
2033
 */
2034
2035
/**
2036
 * Determine whether the current user may perform the given operation on
the
2037
 * specified node.
2038
 *
2039
 * @param $op
2040
 *   The operation to be performed on the node. Possible values are:
2041
 *   - "view"
2042
 *   - "update"
2043
 *   - "delete"
2044
 *   - "create"
2045
 * @param $node
2046
 *   The node object (or node array) on which the operation is to be
performed,
2047
 *   or node type (e.g. 'forum') for "create" operation.
2048
 * @param $account
2049
 *   Optional, a user object representing the user for whom the operation
is to
2050
 *   be performed. Determines access for a user other than the current
user.
2051
 * @return
2052
 *   TRUE if the operation may be performed.
2053
 */
20542027
function node_access($op, $node, $account = NULL) {
20551600
  global $user;
2056
20571600
  if (!$node) {
20580
    return FALSE;
20590
  }
2060
  // Convert the node to an object if necessary:
20611600
  if ($op != 'create') {
2062349
    $node = (object)$node;
2063349
  }
2064
  // If no user object is supplied, the access check is for the current
user.
20651600
  if (empty($account)) {
20661600
    $account = $user;
20671600
  }
2068
  // If the node is in a restricted format, disallow editing.
20691600
  if ($op == 'update' && !filter_access($node->format)) {
20700
    return FALSE;
20710
  }
2072
20731600
  if (user_access('administer nodes', $account)) {
207428
    return TRUE;
20750
  }
2076
20771572
  if (!user_access('access content', $account)) {
20780
    return FALSE;
20790
  }
2080
2081
  // Can't use node_invoke('access', $node), because the access hook takes
the
2082
  // $op parameter before the $node parameter.
20831572
  $module = node_get_types('module', $node);
20841572
  if ($module == 'node') {
20851420
    $module = 'node_content'; // Avoid function name collisions.
20861420
  }
20871572
  $access = module_invoke($module, 'access', $op, $node, $account);
20881572
  if (!is_null($access)) {
20891553
    return $access;
20900
  }
2091
2092
  // If the module did not override the access rights, use those set in
the
2093
  // node_access table.
2094285
  if ($op != 'create' && $node->nid && $node->status) {
2095285
    $grants = array();
2096285
    foreach (node_access_grants($op, $account) as $realm => $gids) {
2097285
      foreach ($gids as $gid) {
2098285
        $grants[] = "(gid = $gid AND realm = '$realm')";
2099285
      }
2100285
    }
2101
2102285
    $grants_sql = '';
2103285
    if (count($grants)) {
2104285
      $grants_sql = 'AND (' . implode(' OR ', $grants) . ')';
2105285
    }
2106
2107285
    $sql = "SELECT COUNT(*) FROM {node_access} WHERE (nid = 0 OR nid = %d)
$grants_sql AND grant_$op >= 1";
2108285
    $result = db_query($sql, $node->nid);
2109285
    return (db_result($result));
21100
  }
2111
2112
  // Let authors view their own nodes.
21130
  if ($op == 'view' && $account->uid == $node->uid && $account->uid != 0)
{
21140
    return TRUE;
21150
  }
2116
21170
  return FALSE;
21180
}
2119
2120
/**
2121
 * Generate an SQL join clause for use in fetching a node listing.
2122
 *
2123
 * @param $node_alias
2124
 *   If the node table has been given an SQL alias other than the default
2125
 *   "n", that must be passed here.
2126
 * @param $node_access_alias
2127
 *   If the node_access table has been given an SQL alias other than the
default
2128
 *   "na", that must be passed here.
2129
 * @return
2130
 *   An SQL join clause.
2131
 */
21322027
function _node_access_join_sql($node_alias = 'n', $node_access_alias =
'na') {
21330
  if (user_access('administer nodes')) {
21340
    return '';
21350
  }
2136
21370
  return 'INNER JOIN {node_access} ' . $node_access_alias . ' ON ' .
$node_access_alias . '.nid = ' . $node_alias . '.nid';
21380
}
2139
2140
/**
2141
 * Generate an SQL where clause for use in fetching a node listing.
2142
 *
2143
 * @param $op
2144
 *   The operation that must be allowed to return a node.
2145
 * @param $node_access_alias
2146
 *   If the node_access table has been given an SQL alias other than the
default
2147
 *   "na", that must be passed here.
2148
 * @param $account
2149
 *   The user object for the user performing the operation. If omitted,
the
2150
 *   current user is used.
2151
 * @return
2152
 *   An SQL where clause.
2153
 */
21542027
function _node_access_where_sql($op = 'view', $node_access_alias = 'na',
$account = NULL) {
21550
  if (user_access('administer nodes')) {
21560
    return;
21570
  }
2158
21590
  $grants = array();
21600
  foreach (node_access_grants($op, $account) as $realm => $gids) {
21610
    foreach ($gids as $gid) {
21620
      $grants[] = "($node_access_alias.gid = $gid AND
$node_access_alias.realm = '$realm')";
21630
    }
21640
  }
2165
21660
  $grants_sql = '';
21670
  if (count($grants)) {
21680
    $grants_sql = 'AND (' . implode(' OR ', $grants) . ')';
21690
  }
2170
21710
  $sql = "$node_access_alias.grant_$op >= 1 $grants_sql";
21720
  return $sql;
21730
}
2174
2175
/**
2176
 * Fetch an array of permission IDs granted to the given user ID.
2177
 *
2178
 * The implementation here provides only the universal "all" grant. A node
2179
 * access module should implement hook_node_grants() to provide a grant
2180
 * list for the user.
2181
 *
2182
 * @param $op
2183
 *   The operation that the user is trying to perform.
2184
 * @param $account
2185
 *   The user object for the user performing the operation. If omitted,
the
2186
 *   current user is used.
2187
 * @return
2188
 *   An associative array in which the keys are realms, and the values are
2189
 *   arrays of grants for those realms.
2190
 */
21912027
function node_access_grants($op, $account = NULL) {
2192
2193669
  if (!isset($account)) {
2194520
    $account = $GLOBALS['user'];
2195520
  }
2196
2197669
  return array_merge(array('all' => array(0)),
module_invoke_all('node_grants', $account, $op));
21980
}
2199
2200
/**
2201
 * Determine whether the user has a global viewing grant for all nodes.
2202
 */
22032027
function node_access_view_all_nodes() {
2204520
  static $access;
2205
2206520
  if (!isset($access)) {
2207520
    $grants = array();
2208520
    foreach (node_access_grants('view') as $realm => $gids) {
2209520
      foreach ($gids as $gid) {
2210520
        $grants[] = "(gid = $gid AND realm = '$realm')";
2211520
      }
2212520
    }
2213
2214520
    $grants_sql = '';
2215520
    if (count($grants)) {
2216520
      $grants_sql = 'AND (' . implode(' OR ', $grants) . ')';
2217520
    }
2218
2219520
    $sql = "SELECT COUNT(*) FROM {node_access} WHERE nid = 0 $grants_sql
AND grant_view >= 1";
2220520
    $result = db_query($sql);
2221520
    $access = db_result($result);
2222520
  }
2223
2224520
  return $access;
22250
}
2226
2227
/**
2228
 * Implementation of hook_db_rewrite_sql().
2229
 */
22302027
function node_db_rewrite_sql($query, $primary_table, $primary_field) {
22311724
  if ($primary_field == 'nid' && !node_access_view_all_nodes()) {
22320
    $return['join'] = _node_access_join_sql($primary_table);
22330
    $return['where'] = _node_access_where_sql();
22340
    $return['distinct'] = 1;
22350
    return $return;
22360
  }
22371724
}
2238
2239
/**
2240
 * This function will call module invoke to get a list of grants and then
2241
 * write them to the database. It is called at node save, and should be
2242
 * called by modules whenever something other than a node_save causes
2243
 * the permissions on a node to change.
2244
 *
2245
 * This function is the only function that should write to the node_access
2246
 * table.
2247
 *
2248
 * @param $node
2249
 *   The $node to acquire grants for.
2250
 */
22512027
function node_access_acquire_grants($node) {
225279
  $grants = module_invoke_all('node_access_records', $node);
225379
  if (empty($grants)) {
225479
    $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1,
'grant_update' => 0, 'grant_delete' => 0);
225579
  }
2256
  else {
2257
    // retain grants by highest priority
22580
    $grant_by_priority = array();
22590
    foreach ($grants as $g) {
22600
      $grant_by_priority[intval($g['priority'])][] = $g;
22610
    }
22620
    krsort($grant_by_priority);
22630
    $grants = array_shift($grant_by_priority);
2264
  }
2265
226679
  node_access_write_grants($node, $grants);
226779
}
2268
2269
/**
2270
 * This function will write a list of grants to the database, deleting
2271
 * any pre-existing grants. If a realm is provided, it will only
2272
 * delete grants from that realm, but it will always delete a grant
2273
 * from the 'all' realm. Modules which utilize node_access can
2274
 * use this function when doing mass updates due to widespread permission
2275
 * changes.
2276
 *
2277
 * @param $node
2278
 *   The $node being written to. All that is necessary is that it contain a
nid.
2279
 * @param $grants
2280
 *   A list of grants to write. Each grant is an array that must contain
the
2281
 *   following keys: realm, gid, grant_view, grant_update, grant_delete.
2282
 *   The realm is specified by a particular module; the gid is as well,
and
2283
 *   is a module-defined id to define grant privileges. each grant_* field
2284
 *   is a boolean value.
2285
 * @param $realm
2286
 *   If provided, only read/write grants for that realm.
2287
 * @param $delete
2288
 *   If false, do not delete records. This is only for optimization
purposes,
2289
 *   and assumes the caller has already performed a mass delete of some
form.
2290
 */
22912027
function node_access_write_grants($node, $grants, $realm = NULL, $delete =
TRUE) {
229279
  if ($delete) {
229379
    $query = 'DELETE FROM {node_access} WHERE nid = %d';
229479
    if ($realm) {
22950
      $query .= " AND realm in ('%s', 'all')";
22960
    }
229779
    db_query($query, $node->nid, $realm);
229879
  }
2299
2300
  // Only perform work when node_access modules are active.
230179
  if (count(module_implements('node_grants'))) {
23020
    foreach ($grants as $grant) {
23030
      if ($realm && $realm != $grant['realm']) {
23040
        continue;
23050
      }
2306
      // Only write grants; denies are implicit.
23070
      if ($grant['grant_view'] || $grant['grant_update'] ||
$grant['grant_delete']) {
23080
        db_query("INSERT INTO {node_access} (nid, realm, gid, grant_view,
grant_update, grant_delete) VALUES (%d, '%s', %d, %d, %d, %d)", $node->nid,
$grant['realm'], $grant['gid'], $grant['grant_view'],
$grant['grant_update'], $grant['grant_delete']);
23090
      }
23100
    }
23110
  }
231279
}
2313
2314
/**
2315
 * Flag / unflag the node access grants for rebuilding, or read the
current
2316
 * value of the flag.
2317
 *
2318
 * When the flag is set, a message is displayed to users with 'access
2319
 * administration pages' permission, pointing to the 'rebuild' confirm
form.
2320
 * This can be used as an alternative to direct node_access_rebuild calls,
2321
 * allowing administrators to decide when they want to perform the actual
2322
 * (possibly time consuming) rebuild.
2323
 * When unsure the current user is an adminisrator, node_access_rebuild
2324
 * should be used instead.
2325
 *
2326
 * @param $rebuild
2327
 *   (Optional) The boolean value to be written.
2328
  * @return
2329
 *   (If no value was provided for $rebuild) The current value of the
flag.
2330
 */
23312027
function node_access_needs_rebuild($rebuild = NULL) {
2332206
  if (!isset($rebuild)) {
2333206
    return variable_get('node_access_needs_rebuild', FALSE);
23340
  }
23350
  elseif ($rebuild) {
23360
    variable_set('node_access_needs_rebuild', TRUE);
23370
  }
2338
  else {
23390
    variable_del('node_access_needs_rebuild');
2340
  }
23410
}
2342
2343
/**
2344
 * Rebuild the node access database. This is occasionally needed by
modules
2345
 * that make system-wide changes to access levels.
2346
 *
2347
 * When the rebuild is required by an admin-triggered action (e.g module
2348
 * settings form), calling node_access_needs_rebuild(TRUE) instead of
2349
 * node_access_rebuild() lets the user perform his changes and actually
2350
 * rebuild only once he is done.
2351
 *
2352
 * Note : As of Drupal 6, node access modules are not required to (and
actually
2353
 * should not) call node_access_rebuild() in hook_enable/disable anymore.
2354
 *
2355
 * @see node_access_needs_rebuild()
2356
 *
2357
 * @param $batch_mode
2358
 *   Set to TRUE to process in 'batch' mode, spawning processing over
several
2359
 *   HTTP requests (thus avoiding the risk of PHP timeout if the site has
a
2360
 *   large number of nodes).
2361
 *   hook_update_N and any form submit handler are safe contexts to use
the
2362
 *   'batch mode'. Less decidable cases (such as calls from hook_user,
2363
 *   hook_taxonomy, hook_node_type...) might consider using the non-batch
mode.
2364
 */
23652027
function node_access_rebuild($batch_mode = FALSE) {
23660
  db_query("DELETE FROM {node_access}");
2367
  // Only recalculate if the site is using a node_access module.
23680
  if (count(module_implements('node_grants'))) {
23690
    if ($batch_mode) {
2370
      $batch = array(
23710
        'title' => t('Rebuilding content access permissions'),
2372
        'operations' => array(
23730
          array('_node_access_rebuild_batch_operation', array()),
23740
        ),
2375
        'finished' => '_node_access_rebuild_batch_finished'
23760
      );
23770
      batch_set($batch);
23780
    }
2379
    else {
2380
      // If not in 'safe mode', increase the maximum execution time.
23810
      if (!ini_get('safe_mode')) {
23820
        set_time_limit(240);
23830
      }
23840
      $result = db_query("SELECT nid FROM {node}");
23850
      while ($node = db_fetch_object($result)) {
23860
        $loaded_node = node_load($node->nid, NULL, TRUE);
2387
        // To preserve database integrity, only aquire grants if the node
2388
        // loads successfully.
23890
        if (!empty($loaded_node)) {
23900
          node_access_acquire_grants($loaded_node);
23910
        }
23920
      }
2393
    }
23940
  }
2395
  else {
2396
    // Not using any node_access modules. Add the default grant.
23970
    db_query("INSERT INTO {node_access} VALUES (0, 0, 'all', 1, 0, 0)");
2398
  }
2399
24000
  if (!isset($batch)) {
24010
    drupal_set_message(t('Content permissions have been rebuilt.'));
24020
    node_access_needs_rebuild(FALSE);
24030
    cache_clear_all();
24040
  }
24050
}
2406
2407
/**
2408
 * Batch operation for node_access_rebuild_batch.
2409
 *
2410
 * This is a mutlistep operation : we go through all nodes by packs of 20.
2411
 * The batch processing engine interrupts processing and sends progress
2412
 * feedback after 1 second execution time.
2413
 */
24142027
function _node_access_rebuild_batch_operation(&$context) {
24150
  if (empty($context['sandbox'])) {
2416
    // Initiate multistep processing.
24170
    $context['sandbox']['progress'] = 0;
24180
    $context['sandbox']['current_node'] = 0;
24190
    $context['sandbox']['max'] = db_result(db_query('SELECT COUNT(DISTINCT
nid) FROM {node}'));
24200
  }
2421
2422
  // Process the next 20 nodes.
24230
  $limit = 20;
24240
  $result = db_query_range("SELECT nid FROM {node} WHERE nid > %d ORDER BY
nid ASC", $context['sandbox']['current_node'], 0, $limit);
24250
  while ($row = db_fetch_array($result)) {
24260
    $loaded_node = node_load($row['nid'], NULL, TRUE);
2427
    // To preserve database integrity, only aquire grants if the node
2428
    // loads successfully.
24290
    if (!empty($loaded_node)) {
24300
      node_access_acquire_grants($loaded_node);
24310
    }
24320
    $context['sandbox']['progress']++;
24330
    $context['sandbox']['current_node'] = $loaded_node->nid;
24340
  }
2435
2436
  // Multistep processing : report progress.
24370
  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
24380
    $context['finished'] = $context['sandbox']['progress'] /
$context['sandbox']['max'];
24390
  }
24400
}
2441
2442
/**
2443
 * Post-processing for node_access_rebuild_batch.
2444
 */
24452027
function _node_access_rebuild_batch_finished($success, $results,
$operations) {
24460
  if ($success) {
24470
    drupal_set_message(t('The content access permissions have been
rebuilt.'));
24480
    node_access_needs_rebuild(FALSE);
24490
  }
2450
  else {
24510
    drupal_set_message(t('The content access permissions have not been
properly rebuilt.'), 'error');
2452
  }
24530
  cache_clear_all();
24540
}
2455
2456
/**
2457
 * @} End of "defgroup node_access".
2458
 */
2459
2460
2461
/**
2462
 * @defgroup node_content Hook implementations for user-created content
types.
2463
 * @{
2464
 */
2465
2466
/**
2467
 * Implementation of hook_access().
2468
 *
2469
 * Named so as not to conflict with node_access()
2470
 */
24712027
function node_content_access($op, $node, $account) {
24721420
  $type = is_string($node) ? $node : (is_array($node) ? $node['type'] :
$node->type);
2473
24741420
  if ($op == 'create') {
24751378
    return user_access('create ' . $type . ' content', $account);
24760
  }
2477
2478228
  if ($op == 'update') {
2479141
    if (user_access('edit any ' . $type . ' content', $account) ||
(user_access('edit own ' . $type . ' content', $account) && ($account->uid
== $node->uid))) {
248083
      return TRUE;
24810
    }
248258
  }
2483
2484207
  if ($op == 'delete') {
248533
    if (user_access('delete any ' . $type . ' content', $account) ||
(user_access('delete own ' . $type . ' content', $account) &&
($account->uid == $node->uid))) {
248614
      return TRUE;
24870
    }
248819
  }
2489199
}
2490
2491
/**
2492
 * Implementation of hook_form().
2493
 */
24942027
function node_content_form($node, $form_state) {
249551
  $type = node_get_types('type', $node);
249651
  $form = array();
2497
249851
  if ($type->has_title) {
249951
    $form['title'] = array(
250051
      '#type' => 'textfield',
250151
      '#title' => check_plain($type->title_label),
250251
      '#required' => TRUE,
250351
      '#default_value' => $node->title,
250451
      '#maxlength' => 255,
250551
      '#weight' => -5,
2506
    );
250751
  }
2508
250951
  if ($type->has_body) {
251051
    $form['body_field'] = node_body_field($node, $type->body_label,
$type->min_word_count);
251151
  }
2512
251351
  return $form;
25140
}
2515
2516
/**
2517
 * @} End of "defgroup node_content".
2518
 */
2519
2520
/**
2521
 * Implementation of hook_forms(). All node forms share the same form
handler
2522
 */
25232027
function node_forms() {
2524135
  $forms = array();
2525135
  if ($types = node_get_types()) {
2526135
    foreach (array_keys($types) as $type) {
2527135
      $forms[$type . '_node_form']['callback'] = 'node_form';
2528135
    }
2529135
  }
2530135
  return $forms;
25310
}
2532
2533
/**
2534
 * Format the "Submitted by username on date/time" for each node
2535
 *
2536
 * @ingroup themeable
2537
 */
25382027
function theme_node_submitted($node) {
25390
  return t('Submitted by !username on @datetime',
2540
    array(
25410
      '!username' => theme('username', $node),
25420
      '@datetime' => format_date($node->created),
25430
    ));
25440
}
2545
2546
/**
2547
 * Implementation of hook_hook_info().
2548
 */
25492027
function node_hook_info() {
2550
  return array(
2551
    'node' => array(
2552
      'nodeapi' => array(
2553
        'presave' => array(
255455
          'runs when' => t('When either saving a new post or updating an
existing post'),
255555
        ),
2556
        'insert' => array(
255755
          'runs when' => t('After saving a new post'),
255855
        ),
2559
        'update' => array(
256055
          'runs when' => t('After saving an updated post'),
256155
        ),
2562
        'delete' => array(
256355
          'runs when' => t('After deleting a post')
256455
        ),
2565
        'view' => array(
256655
          'runs when' => t('When content is viewed by an authenticated
user')
256755
        ),
256855
      ),
256955
    ),
257055
  );
25710
}
2572
2573
/**
2574
 * Implementation of hook_action_info().
2575
 */
25762027
function node_action_info() {
2577
  return array(
2578
    'node_publish_action' => array(
2579131
      'type' => 'node',
2580131
      'description' => t('Publish post'),
2581131
      'configurable' => FALSE,
2582131
      'behavior' => array('changes_node_property'),
2583
      'hooks' => array(
2584131
        'nodeapi' => array('presave'),
2585131
        'comment' => array('insert', 'update'),
2586131
      ),
2587131
    ),
2588
    'node_unpublish_action' => array(
2589131
      'type' => 'node',
2590131
      'description' => t('Unpublish post'),
2591131
      'configurable' => FALSE,
2592131
      'behavior' => array('changes_node_property'),
2593
      'hooks' => array(
2594131
        'nodeapi' => array('presave'),
2595131
        'comment' => array('delete', 'insert', 'update'),
2596131
      ),
2597131
    ),
2598
    'node_make_sticky_action' => array(
2599131
      'type' => 'node',
2600131
      'description' => t('Make post sticky'),
2601131
      'configurable' => FALSE,
2602131
      'behavior' => array('changes_node_property'),
2603
      'hooks' => array(
2604131
        'nodeapi' => array('presave'),
2605131
        'comment' => array('insert', 'update'),
2606131
      ),
2607131
    ),
2608
    'node_make_unsticky_action' => array(
2609131
      'type' => 'node',
2610131
      'description' => t('Make post unsticky'),
2611131
      'configurable' => FALSE,
2612131
      'behavior' => array('changes_node_property'),
2613
      'hooks' => array(
2614131
        'nodeapi' => array('presave'),
2615131
        'comment' => array('delete', 'insert', 'update'),
2616131
      ),
2617131
    ),
2618
    'node_promote_action' => array(
2619131
      'type' => 'node',
2620131
      'description' => t('Promote post to front page'),
2621131
      'configurable' => FALSE,
2622131
      'behavior' => array('changes_node_property'),
2623
      'hooks' => array(
2624131
        'nodeapi' => array('presave'),
2625131
        'comment' => array('insert', 'update'),
2626131
      ),
2627131
    ),
2628
    'node_unpromote_action' => array(
2629131
      'type' => 'node',
2630131
      'description' => t('Remove post from front page'),
2631131
      'configurable' => FALSE,
2632131
      'behavior' => array('changes_node_property'),
2633
      'hooks' => array(
2634131
        'nodeapi' => array('presave'),
2635131
        'comment' => array('delete', 'insert', 'update'),
2636131
      ),
2637131
    ),
2638
    'node_assign_owner_action' => array(
2639131
      'type' => 'node',
2640131
      'description' => t('Change the author of a post'),
2641131
      'configurable' => TRUE,
2642131
      'behavior' => array('changes_node_property'),
2643
      'hooks' => array(
2644131
        'any' => TRUE,
2645131
        'nodeapi' => array('presave'),
2646131
        'comment' => array('delete', 'insert', 'update'),
2647131
      ),
2648131
    ),
2649
    'node_save_action' => array(
2650131
      'type' => 'node',
2651131
      'description' => t('Save post'),
2652131
      'configurable' => FALSE,
2653
      'hooks' => array(
2654131
        'comment' => array('delete', 'insert', 'update'),
2655131
      ),
2656131
    ),
2657
    'node_unpublish_by_keyword_action' => array(
2658131
      'type' => 'node',
2659131
      'description' => t('Unpublish post containing keyword(s)'),
2660131
      'configurable' => TRUE,
2661
      'hooks' => array(
2662131
        'nodeapi' => array('presave', 'insert', 'update'),
2663131
      ),
2664131
    ),
2665131
  );
26660
}
2667
2668
/**
2669
 * Implementation of a Drupal action.
2670
 * Sets the status of a node to 1, meaning published.
2671
 */
26722027
function node_publish_action(&$node, $context = array()) {
26731
  $node->status = 1;
26741
  watchdog('action', 'Set @type %title to published.', array('@type' =>
node_get_types('name', $node), '%title' => $node->title));
26751
}
2676
2677
/**
2678
 * Implementation of a Drupal action.
2679
 * Sets the status of a node to 0, meaning unpublished.
2680
 */
26812027
function node_unpublish_action(&$node, $context = array()) {
26821
  $node->status = 0;
26831
  watchdog('action', 'Set @type %title to unpublished.', array('@type' =>
node_get_types('name', $node), '%title' => $node->title));
26841
}
2685
2686
/**
2687
 * Implementation of a Drupal action.
2688
 * Sets the sticky-at-top-of-list property of a node to 1.
2689
 */
26902027
function node_make_sticky_action(&$node, $context = array()) {
26911
  $node->sticky = 1;
26921
  watchdog('action', 'Set @type %title to sticky.', array('@type' =>
node_get_types('name', $node), '%title' => $node->title));
26931
}
2694
2695
/**
2696
 * Implementation of a Drupal action.
2697
 * Sets the sticky-at-top-of-list property of a node to 0.
2698
 */
26992027
function node_make_unsticky_action(&$node, $context = array()) {
27001
  $node->sticky = 0;
27011
  watchdog('action', 'Set @type %title to unsticky.', array('@type' =>
node_get_types('name', $node), '%title' => $node->title));
27021
}
2703
2704
/**
2705
 * Implementation of a Drupal action.
2706
 * Sets the promote property of a node to 1.
2707
 */
27082027
function node_promote_action(&$node, $context = array()) {
27091
  $node->promote = 1;
27101
  watchdog('action', 'Promoted @type %title to front page.', array('@type'
=> node_get_types('name', $node), '%title' => $node->title));
27111
}
2712
2713
/**
2714
 * Implementation of a Drupal action.
2715
 * Sets the promote property of a node to 0.
2716
 */
27172027
function node_unpromote_action(&$node, $context = array()) {
27181
  $node->promote = 0;
27191
  watchdog('action', 'Removed @type %title from front page.', array('@type'
=> node_get_types('name', $node), '%title' => $node->title));
27201
}
2721
2722
/**
2723
 * Implementation of a Drupal action.
2724
 * Saves a node.
2725
 */
27262027
function node_save_action($node) {
27270
  node_save($node);
27280
  watchdog('action', 'Saved @type %title', array('@type' =>
node_get_types('name', $node), '%title' => $node->title));
27290
}
2730
2731
/**
2732
 * Implementation of a configurable Drupal action.
2733
 * Assigns ownership of a node to a user.
2734
 */
27352027
function node_assign_owner_action(&$node, $context) {
27360
  $node->uid = $context['owner_uid'];
27370
  $owner_name = db_result(db_query("SELECT name FROM {users} WHERE uid =
%d", $context['owner_uid']));
27380
  watchdog('action', 'Changed owner of @type %title to uid %name.',
array('@type' => node_get_types('type', $node), '%title' => $node->title,
'%name' => $owner_name));
27390
}
2740
27412027
function node_assign_owner_action_form($context) {
27420
  $description = t('The username of the user to which you would like to
assign ownership.');
27430
  $count = db_result(db_query("SELECT COUNT(*) FROM {users}"));
27440
  $owner_name = '';
27450
  if (isset($context['owner_uid'])) {
27460
    $owner_name = db_result(db_query("SELECT name FROM {users} WHERE uid =
%d", $context['owner_uid']));
27470
  }
2748
2749
  // Use dropdown for fewer than 200 users; textbox for more than that.
27500
  if (intval($count) < 200) {
27510
    $options = array();
27520
    $result = db_query("SELECT uid, name FROM {users} WHERE uid > 0 ORDER
BY name");
27530
    while ($data = db_fetch_object($result)) {
27540
      $options[$data->name] = $data->name;
27550
    }
27560
    $form['owner_name'] = array(
27570
      '#type' => 'select',
27580
      '#title' => t('Username'),
27590
      '#default_value' => $owner_name,
27600
      '#options' => $options,
27610
      '#description' => $description,
2762
    );
27630
  }
2764
  else {
27650
    $form['owner_name'] = array(
27660
      '#type' => 'textfield',
27670
      '#title' => t('Username'),
27680
      '#default_value' => $owner_name,
27690
      '#autocomplete_path' => 'user/autocomplete',
27700
      '#size' => '6',
27710
      '#maxlength' => '7',
27720
      '#description' => $description,
2773
    );
2774
  }
27750
  return $form;
27760
}
2777
27782027
function node_assign_owner_action_validate($form, $form_state) {
27790
  $count = db_result(db_query("SELECT COUNT(*) FROM {users} WHERE name =
'%s'", $form_state['values']['owner_name']));
27800
  if (intval($count) != 1) {
27810
    form_set_error('owner_name', t('Please enter a valid username.'));
27820
  }
27830
}
2784
27852027
function node_assign_owner_action_submit($form, $form_state) {
2786
  // Username can change, so we need to store the ID, not the username.
27870
  $uid = db_result(db_query("SELECT uid from {users} WHERE name = '%s'",
$form_state['values']['owner_name']));
27880
  return array('owner_uid' => $uid);
27890
}
2790
27912027
function node_unpublish_by_keyword_action_form($context) {
27920
  $form['keywords'] = array(
27930
    '#title' => t('Keywords'),
27940
    '#type' => 'textarea',
27950
    '#description' => t('The post will be unpublished if it contains any of
the character sequences above. Use a comma-separated list of character
sequences. Example: funny, bungee jumping, "Company, Inc." . Character
sequences are case-sensitive.'),
27960
    '#default_value' => isset($context['keywords']) ?
drupal_implode_tags($context['keywords']) : '',
2797
  );
27980
  return $form;
27990
}
2800
28012027
function node_unpublish_by_keyword_action_submit($form, $form_state) {
28020
  return array('keywords' =>
drupal_explode_tags($form_state['values']['keywords']));
28030
}
2804
2805
/**
2806
 * Implementation of a configurable Drupal action.
2807
 * Unpublish a node if it contains a certain string.
2808
 *
2809
 * @param $context
2810
 *   An array providing more information about the context of the call to
this action.
2811
 * @param $comment
2812
 *   A node object.
2813
 */
28142027
function node_unpublish_by_keyword_action($node, $context) {
28150
  foreach ($context['keywords'] as $keyword) {
28160
    if (strstr(node_view(clone $node), $keyword) || strstr($node->title,
$keyword)) {
28170
      $node->status = 0;
28180
      watchdog('action', 'Set @type %title to unpublished.', array('@type'
=> node_get_types('name', $node), '%title' => $node->title));
28190
      break;
28200
    }
28210
  }
28220
}
2823
2824
/**
2825
 * Helper function to generate standard node permission list for a given
type.
2826
 *
2827
 * @param $type
2828
 *   The machine-readable name of the node type.
2829
 * @return array
2830
 *   An array of permission names and descriptions.
2831
 */
28322027
function node_list_permissions($type) {
283387
  $info = node_get_types('type', $type);
283487
  $type = check_plain($info->type);
2835
2836
  // Build standard list of node permissions for this type.
283787
  $perms["create $type content"] = t('Create new %type_name content.',
array('%type_name' => $info->name));
283887
  $perms["delete any $type content"] = t('Delete any %type_name content,
regardless of its author.', array('%type_name' => $info->name));
283987
  $perms["delete own $type content"] = t('Delete %type_name content created
by the user.', array('%type_name' => $info->name));
284087
  $perms["edit own $type content"] = t('Edit %type_name content created by
the user.', array('%type_name' => $info->name));
284187
  $perms["edit any $type content"] = t('Edit any %type_name content,
regardless of its author.', array('%type_name' => $info->name));
2842
284387
  return $perms;
28440
}
28452027