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

Line #Times calledCode
1
<?php
2
// $Id: node.module,v 1.991 2008/11/01 19:51: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
 */
162366
define('NODE_NEW_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60);
17
18
/**
19
 * Node is being built before being viewed normally.
20
 */
212366
define('NODE_BUILD_NORMAL', 0);
22
23
/**
24
 * Node is being built before being previewed.
25
 */
262366
define('NODE_BUILD_PREVIEW', 1);
27
28
/**
29
 * Node is being built before being indexed by search module.
30
 */
312366
define('NODE_BUILD_SEARCH_INDEX', 2);
32
33
/**
34
 * Node is being built before being displayed as a search result.
35
 */
362366
define('NODE_BUILD_SEARCH_RESULT', 3);
37
38
/**
39
 * Node is being built before being displayed as part of an RSS feed.
40
 */
412366
define('NODE_BUILD_RSS', 4);
42
43
/**
44
 * Node is being built before being printed.
45
 */
462366
define('NODE_BUILD_PRINT', 5);
47
48
/**
49
 * Implementation of hook_help().
50
 */
512366
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.
551676
  if ($path != 'admin/content/node-settings/rebuild' && $path != 'batch' &&
strpos($path, '#') === FALSE
561676
      && 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) {
671676
    case 'admin/help#node':
681
      $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>';
691
      $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>';
701
      $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>';
711
      $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>';
721
      return $output;
731676
    case 'admin/content/node':
740
      return ' '; // Return a non-null value so that the 'more help' link
is shown.
751676
    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>';
771676
    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>';
791676
    case 'node/%/revisions':
803
      return '<p>' . t('The revisions let you track differences between
multiple versions of a post.') . '</p>';
811673
    case 'node/%/edit':
8238
      $node = node_load($arg[1]);
8338
      $type = node_get_types('type', $node->type);
8438
      return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) .
'</p>' : '');
850
  }
86
871635
  if ($arg[0] == 'node' && $arg[1] == 'add' && $arg[2]) {
8854
    $type = node_get_types('type', str_replace('-', '_', $arg[2]));
8954
    return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) .
'</p>' : '');
900
  }
911581
}
92
93
/**
94
 * Implementation of hook_theme().
95
 */
962366
function node_theme() {
97
  return array(
98
    'node' => array(
99178
      'arguments' => array('node' => NULL, 'teaser' => FALSE, 'page' =>
FALSE),
100178
      'template' => 'node',
101178
    ),
102
    'node_list' => array(
103178
      'arguments' => array('items' => NULL, 'title' => NULL),
104178
    ),
105
    'node_search_admin' => array(
106178
      'arguments' => array('form' => NULL),
107178
    ),
108
    'node_filter_form' => array(
109178
      'arguments' => array('form' => NULL),
110178
      'file' => 'node.admin.inc',
111178
    ),
112
    'node_filters' => array(
113178
      'arguments' => array('form' => NULL),
114178
      'file' => 'node.admin.inc',
115178
    ),
116
    'node_admin_nodes' => array(
117178
      'arguments' => array('form' => NULL),
118178
      'file' => 'node.admin.inc',
119178
    ),
120
    'node_add_list' => array(
121178
      'arguments' => array('content' => NULL),
122178
      'file' => 'node.pages.inc',
123178
    ),
124
    'node_form' => array(
125178
      'arguments' => array('form' => NULL),
126178
      'file' => 'node.pages.inc',
127178
    ),
128
    'node_preview' => array(
129178
      'arguments' => array('node' => NULL),
130178
      'file' => 'node.pages.inc',
131178
    ),
132
    'node_log_message' => array(
133178
      'arguments' => array('log' => NULL),
134178
    ),
135
    'node_submitted' => array(
136178
      'arguments' => array('node' => NULL),
137178
    ),
138178
  );
1390
}
140
141
/**
142
 * Implementation of hook_cron().
143
 */
1442366
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
 */
1632366
function node_title_list($result, $title = NULL) {
164179
  $items = array();
165179
  $num_rows = FALSE;
166179
  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
171179
  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
 */
1792366
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
 */
1862366
function node_tag_new($nid) {
187184
  global $user;
188
189184
  if ($user->uid) {
190173
    if (node_last_viewed($nid)) {
19185
      db_query('UPDATE {history} SET timestamp = %d WHERE uid = %d AND nid
= %d', REQUEST_TIME, $user->uid, $nid);
19285
    }
193
    else {
19488
      @db_query('INSERT INTO {history} (uid, nid, timestamp) VALUES (%d,
%d, %d)', $user->uid, $nid, REQUEST_TIME);
195
    }
196173
  }
197184
}
198
199
/**
200
 * Retrieves the timestamp at which the current user last viewed the
201
 * specified node.
202
 */
2032366
function node_last_viewed($nid) {
204193
  global $user;
205193
  static $history;
206
207193
  if (!isset($history[$nid])) {
208193
    $history[$nid] = db_fetch_object(db_query("SELECT timestamp FROM
{history} WHERE uid = %d AND nid = %d", $user->uid, $nid));
209193
  }
210
211193
  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
 */
2242366
function node_mark($nid, $timestamp) {
22546
  global $user;
22646
  static $cache;
227
22846
  if (!$user->uid) {
22910
    return MARK_READ;
2300
  }
23136
  if (!isset($cache[$nid])) {
23236
    $cache[$nid] = node_last_viewed($nid);
23336
  }
23436
  if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) {
23513
    return MARK_NEW;
2360
  }
23723
  elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) {
23811
    return MARK_UPDATED;
2390
  }
24020
  return MARK_READ;
2410
}
242
243
/**
244
 * See if the user used JS to submit a teaser.
245
 */
2462366
function node_teaser_js(&$form, &$form_state) {
247148
  if (isset($form['#post']['teaser_js'])) {
248
    // Glue the teaser to the body.
24970
    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.
25570
      $body = '<!--break-->' . $form_state['values']['body'];
256
    }
257
    // Pass updated body value on to preview/submit form processing.
25870
    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.
26170
    $form['body']['#value'] = $body;
26270
  }
263148
  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
 */
2812366
function node_teaser_include_verify(&$form, &$form_state) {
282148
  $message = '';
283
284
  // $form['#post'] is set only when the form is built for preview/submit.
285148
  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
306148
  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
 */
3302366
function node_teaser($body, $format = NULL, $size = NULL) {
331
33268
  if (!isset($size)) {
33367
    $size = variable_get('teaser_length', 600);
33467
  }
335
336
  // Find where the delimiter is in the body
33768
  $delimiter = strpos($body, '<!--break-->');
338
339
  // If the size is zero, and there is no delimiter, the entire body is the
teaser.
34068
  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.
34568
  if ($delimiter !== FALSE) {
34667
    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
 *   'base', 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
 */
4422366
function node_get_types($op = 'types', $node = NULL, $reset = FALSE) {
4432047
  static $_node_types, $_node_names;
444
4452047
  if ($reset || !isset($_node_types)) {
4462047
    list($_node_types, $_node_names) = _node_types_build();
4472047
  }
448
4492047
  if ($node) {
4502008
    if (is_array($node)) {
4510
      $type = $node['type'];
4520
    }
4532008
    elseif (is_object($node)) {
454683
      $type = $node->type;
455683
    }
4561797
    elseif (is_string($node)) {
4571797
      $type = $node;
4581797
    }
4592008
    if (!isset($_node_types[$type])) {
4600
      return FALSE;
4610
    }
4622008
  }
463
  switch ($op) {
4642047
    case 'types':
4651982
      return $_node_types;
4662015
    case 'type':
467333
      return isset($_node_types[$type]) ? $_node_types[$type] : FALSE;
4681902
    case 'base':
4691895
      return isset($_node_types[$type]->base) ? $_node_types[$type]->base :
FALSE;
470130
    case 'names':
47120
      return $_node_names;
472113
    case 'name':
473113
      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
 */
4812366
function node_types_rebuild() {
48212
  _node_types_build();
483
48412
  $node_types = node_get_types('types', NULL, TRUE);
485
48612
  foreach ($node_types as $type => $info) {
48712
    if (!empty($info->is_new)) {
4880
      node_type_save($info);
4890
    }
49012
    if (!empty($info->disabled)) {
4910
      node_type_delete($info->type);
4920
    }
49312
  }
494
49512
  _node_types_build();
49612
}
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
 */
5072366
function node_type_save($info) {
508135
  $is_existing = FALSE;
509135
  $existing_type = !empty($info->old_type) ? $info->old_type : $info->type;
510135
  $is_existing = db_result(db_query("SELECT COUNT(*) FROM {node_type} WHERE
type = '%s'", $existing_type));
511135
  $type = node_type_set_defaults($info);
512
513
  $fields = array(
514135
    'type' => (string) $type->type,
515135
    'name' => (string) $type->name,
516135
    'base' => (string) $type->base,
517135
    'has_title' => (int) $type->has_title,
518135
    'title_label' => (string) $type->title_label,
519135
    'has_body' => (int) $type->has_body,
520135
    'body_label' => (string) $type->body_label,
521135
    'description' => (string) $type->description,
522135
    'help' => (string) $type->help,
523135
    'min_word_count' => (int) $type->min_word_count,
524135
    'custom' => (int) $type->custom,
525135
    'modified' => (int) $type->modified,
526135
    'locked' => (int) $type->locked,
527135
  );
528
529135
  if ($is_existing) {
5301
    db_update('node_type')->fields($fields)->condition('type',
$existing_type)->execute();
531
5321
    module_invoke_all('node_type', 'update', $type);
5331
    return SAVED_UPDATED;
5340
  }
535
  else {
536134
    $fields['orig_type'] = (string) $type->orig_type;
537134
    db_insert('node_type')->fields($fields)->execute();
538
539134
    module_invoke_all('node_type', 'insert', $type);
540134
    return SAVED_NEW;
541
  }
5420
}
543
544
/**
545
 * Deletes a node type from the database.
546
 *
547
 * @param $type
548
 *   The machine-readable name of the node type to be deleted.
549
 */
5502366
function node_type_delete($type) {
5510
  $info = node_get_types('type', $type);
5520
  db_query("DELETE FROM {node_type} WHERE type = '%s'", $type);
5530
  module_invoke_all('node_type', 'delete', $info);
5540
}
555
556
/**
557
 * Updates all nodes of one type to be of another type.
558
 *
559
 * @param $old_type
560
 *   The current node type of the nodes.
561
 * @param $type
562
 *   The new node type of the nodes.
563
 *
564
 * @return
565
 *   The number of nodes whose node type field was modified.
566
 */
5672366
function node_type_update_nodes($old_type, $type) {
5680
  db_query("UPDATE {node} SET type = '%s' WHERE type = '%s'", $type,
$old_type);
5690
  return db_affected_rows();
5700
}
571
572
/**
573
 * Builds and returns the list of available node types.
574
 *
575
 * The list of types is built by querying hook_node_info() in all modules,
and
576
 * by comparing this information with the node types in the {node_type}
table.
577
 *
578
 */
5792366
function _node_types_build() {
5802047
  $_node_types = array();
5812047
  $_node_names = array();
582
5832047
  $info_array = module_invoke_all('node_info');
5842047
  foreach ($info_array as $type => $info) {
585378
    $info['type'] = $type;
586378
    $_node_types[$type] = node_type_set_defaults($info);
587378
    $_node_names[$type] = $info['name'];
588378
  }
589
5902047
  $type_result = db_query(db_rewrite_sql('SELECT nt.type, nt.* FROM
{node_type} nt ORDER BY nt.type ASC', 'nt', 'type'));
5912047
  while ($type_object = db_fetch_object($type_result)) {
592
    // Check for node types from disabled modules and mark their types for
removal.
593
    // Types defined by the node module in the database (rather than by a
separate
594
    // module using hook_node_info) have a base value of 'node_content'.
5952047
    if ($type_object->base != 'node_content' &&
empty($info_array[$type_object->type])) {
5960
      $type_object->disabled = TRUE;
5970
    }
5982047
    if (!isset($_node_types[$type_object->type]) || $type_object->modified)
{
5992047
      $_node_types[$type_object->type] = $type_object;
6002047
      $_node_names[$type_object->type] = $type_object->name;
601
6022047
      if ($type_object->type != $type_object->orig_type) {
6030
        unset($_node_types[$type_object->orig_type]);
6040
        unset($_node_names[$type_object->orig_type]);
6050
      }
6062047
    }
6072047
  }
608
6092047
  asort($_node_names);
610
6112047
  return array($_node_types, $_node_names);
6120
}
613
614
/**
615
 * Set the default values for a node type.
616
 *
617
 * The defaults are for a type defined through hook_node_info().
618
 * When populating a custom node type $info should have the 'custom'
619
 * key set to 1.
620
 *
621
 * @param $info
622
 *   An object or array containing values to override the defaults.
623
 *
624
 * @return
625
 *  A node type object.
626
 */
6272366
function node_type_set_defaults($info = array()) {
628507
  static $type;
629
630507
  if (!isset($type)) {
631507
    $type = new stdClass();
632507
    $type->type = '';
633507
    $type->name = '';
634507
    $type->base = '';
635507
    $type->description = '';
636507
    $type->help = '';
637507
    $type->min_word_count = 0;
638507
    $type->has_title = 1;
639507
    $type->has_body = 1;
640507
    $type->title_label = t('Title');
641507
    $type->body_label = t('Body');
642507
    $type->custom = 0;
643507
    $type->modified = 0;
644507
    $type->locked = 1;
645507
    $type->is_new = 1;
646507
  }
647
648507
  $new_type = clone $type;
649507
  $info = (array) $info;
650507
  foreach ($info as $key => $data) {
651507
    $new_type->$key = $data;
652507
  }
653
  // If the type has no title or body, set an empty label.
654507
  if (!$new_type->has_title) {
6550
    $new_type->title_label = '';
6560
  }
657507
  if (!$new_type->has_body) {
658146
    $new_type->body_label = '';
659146
  }
660507
  $new_type->orig_type = isset($info['type']) ? $info['type'] : '';
661
662507
  return $new_type;
6630
}
664
665
/**
666
 * Determine whether a node hook exists.
667
 *
668
 * @param &$node
669
 *   Either a node object, node array, or a string containing the node
type.
670
 * @param $hook
671
 *   A string containing the name of the hook.
672
 * @return
673
 *   TRUE iff the $hook exists in the node type of $node.
674
 */
6752366
function node_hook(&$node, $hook) {
6761893
  $base = node_get_types('base', $node);
6771893
  return module_hook($base, $hook);
6780
}
679
680
/**
681
 * Invoke a node hook.
682
 *
683
 * @param &$node
684
 *   Either a node object, node array, or a string containing the node
type.
685
 * @param $hook
686
 *   A string containing the name of the hook.
687
 * @param $a2, $a3, $a4
688
 *   Arguments to pass on to the hook, after the $node argument.
689
 * @return
690
 *   The returned value of the invoked hook.
691
 */
6922366
function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
693541
  if (node_hook($node, $hook)) {
694182
    $base = node_get_types('base', $node);
695182
    $function = $base . '_' . $hook;
696182
    return ($function($node, $a2, $a3, $a4));
6970
  }
698463
}
699
700
/**
701
 * Invoke a hook_nodeapi() operation in all modules.
702
 *
703
 * @param &$node
704
 *   A node object.
705
 * @param $op
706
 *   A string containing the name of the nodeapi operation.
707
 * @param $a3, $a4
708
 *   Arguments to pass on to the hook, after the $node and $op arguments.
709
 * @return
710
 *   The returned value of the invoked hooks.
711
 */
7122366
function node_invoke_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
713541
  $return = array();
714541
  $hook = 'nodeapi_' . $op;
715541
  foreach (module_implements($hook) as $name) {
716541
    $function = $name . '_' . $hook;
717541
    $result = $function($node, $a3, $a4);
718541
    if (isset($result) && is_array($result)) {
719436
      $return = array_merge($return, $result);
720436
    }
721307
    elseif (isset($result)) {
7225
      $return[] = $result;
7235
    }
724541
  }
725541
  return $return;
7260
}
727
728
/**
729
 * Load a node object from the database.
730
 *
731
 * @param $param
732
 *   Either the nid of the node or an array of conditions to match against
in the database query
733
 * @param $revision
734
 *   Which numbered revision to load. Defaults to the current version.
735
 * @param $reset
736
 *   Whether to reset the internal node_load cache.
737
 *
738
 * @return
739
 *   A fully-populated node object.
740
 */
7412366
function node_load($param = array(), $revision = NULL, $reset = NULL) {
742441
  static $nodes = array();
743
744441
  if ($reset) {
7452
    $nodes = array();
7462
  }
747
748441
  $cachable = ($revision == NULL);
749441
  $arguments = array();
750441
  if (is_numeric($param)) {
751427
    if ($cachable) {
752
      // Is the node statically cached?
753427
      if (isset($nodes[$param])) {
754330
        return is_object($nodes[$param]) ? clone $nodes[$param] :
$nodes[$param];
7550
      }
756427
    }
757427
    $cond = 'n.nid = %d';
758427
    $arguments[] = $param;
759427
  }
76014
  elseif (is_array($param)) {
761
    // Turn the conditions into a query.
76214
    foreach ($param as $key => $value) {
76314
      $cond[] = 'n.' . db_escape_table($key) . " = '%s'";
76414
      $arguments[] = $value;
76514
    }
76614
    $cond = implode(' AND ', $cond);
76714
  }
768
  else {
7690
    return FALSE;
770
  }
771
772
  // Retrieve a field list based on the site's schema.
773441
  $fields = drupal_schema_fields_sql('node', 'n');
774441
  $fields = array_merge($fields, drupal_schema_fields_sql('node_revisions',
'r'));
775441
  $fields = array_merge($fields, array('u.name', 'u.picture', 'u.data'));
776
  // Remove fields not needed in the query: n.vid and r.nid are redundant,
777
  // n.title is unnecessary because the node title comes from the
778
  // node_revisions table.  We'll keep r.vid, r.title, and n.nid.
779441
  $fields = array_diff($fields, array('n.vid', 'n.title', 'r.nid'));
780441
  $fields = implode(', ', $fields);
781
  // Rename timestamp field for clarity.
782441
  $fields = str_replace('r.timestamp', 'r.timestamp AS revision_timestamp',
$fields);
783
  // Change name of revision uid so it doesn't conflict with n.uid.
784441
  $fields = str_replace('r.uid', 'r.uid AS revision_uid', $fields);
785
786
  // Retrieve the node.
787
  // No db_rewrite_sql is applied so as to get complete indexing for
search.
788441
  if ($revision) {
7895
    array_unshift($arguments, $revision);
7905
    $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));
7915
  }
792
  else {
793441
    $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));
794
  }
795
796441
  if ($node && $node->nid) {
797
    // Call the node specific callback (if any) and piggy-back the
798
    // results to the node or overwrite some values.
799436
    if ($extra = node_invoke($node, 'load')) {
80088
      foreach ($extra as $key => $value) {
80188
        $node->$key = $value;
80288
      }
80388
    }
804
805436
    if ($extra = node_invoke_nodeapi($node, 'load')) {
806436
      foreach ($extra as $key => $value) {
807436
        $node->$key = $value;
808436
      }
809436
    }
810436
    if ($cachable) {
811436
      $nodes[$node->nid] = is_object($node) ? clone $node : $node;
812436
    }
813436
  }
814
815441
  return $node;
8160
}
817
818
/**
819
 * Perform validation checks on the given node.
820
 */
8212366
function node_validate($node, $form = array()) {
822
  // Convert the node to an object, if necessary.
82379
  $node = (object)$node;
82479
  $type = node_get_types('type', $node);
825
826
  // Make sure the body has the minimum number of words.
827
  // TODO : use a better word counting algorithm that will work in other
languages
82879
  if (!empty($type->min_word_count) && isset($node->body) &&
count(explode(' ', $node->body)) < $type->min_word_count) {
8290
    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)));
8300
  }
831
83279
  if (isset($node->nid) && (node_last_changed($node->nid) >
$node->changed)) {
8330
    form_set_error('changed', t('This content has been modified by another
user, changes cannot be saved.'));
8340
  }
835
83679
  if (user_access('administer nodes')) {
837
    // Validate the "authored by" field.
8386
    if (!empty($node->name) && !($account = user_load(array('name' =>
$node->name)))) {
839
      // The use of empty() is mandatory in the context of usernames
840
      // as the empty string denotes the anonymous user. In case we
841
      // are dealing with an anonymous user we set the user ID to 0.
8420
      form_set_error('name', t('The username %name does not exist.',
array('%name' => $node->name)));
8430
    }
844
845
    // Validate the "authored on" field.
8466
    if (!empty($node->date) && strtotime($node->date) === FALSE) {
8470
      form_set_error('date', t('You have to specify a valid date.'));
8480
    }
8496
  }
850
851
  // Do node-type-specific validation checks.
85279
  node_invoke($node, 'validate', $form);
85379
  node_invoke_nodeapi($node, 'validate', $form);
85479
}
855
856
/**
857
 * Prepare node for save and allow modules to make changes.
858
 */
8592366
function node_submit($node) {
86076
  global $user;
861
862
  // Convert the node to an object, if necessary.
86376
  $node = (object)$node;
864
865
  // Generate the teaser, but only if it hasn't been set (e.g. by a
866
  // module-provided 'teaser' form item).
86776
  if (!isset($node->teaser)) {
86869
    if (isset($node->body)) {
86967
      $node->format = (!empty($node->body_format) ? $node->body_format :
FILTER_FORMAT_DEFAULT);
87067
      $node->teaser = node_teaser($node->body, isset($node->format) ?
$node->format : NULL);
871
      // Chop off the teaser from the body if needed. The teaser_include
872
      // property might not be set (eg. in Blog API postings), so only act
on
873
      // it, if it was set with a given value.
87467
      if (isset($node->teaser_include) && !$node->teaser_include &&
$node->teaser == substr($node->body, 0, strlen($node->teaser))) {
8750
        $node->body = substr($node->body, strlen($node->teaser));
8760
      }
87767
    }
878
    else {
8792
      $node->teaser = '';
880
    }
88169
  }
882
88376
  if (user_access('administer nodes')) {
884
    // Populate the "authored by" field.
8856
    if ($account = user_load(array('name' => $node->name))) {
8866
      $node->uid = $account->uid;
8876
    }
888
    else {
8890
      $node->uid = 0;
890
    }
8916
  }
89276
  $node->created = !empty($node->date) ? strtotime($node->date) :
REQUEST_TIME;
89376
  $node->validated = TRUE;
894
89576
  return $node;
8960
}
897
898
/**
899
 * Save a node object into the database.
900
 */
9012366
function node_save(&$node) {
902
  // Let modules modify the node before it is saved to the database.
90388
  node_invoke_nodeapi($node, 'presave');
90488
  global $user;
905
90688
  $node->is_new = FALSE;
907
908
  // Apply filters to some default node fields:
90988
  if (empty($node->nid)) {
910
    // Insert a new node.
91158
    $node->is_new = TRUE;
912
913
    // When inserting a node, $node->log must be set because
914
    // {node_revisions}.log does not (and cannot) have a default
915
    // value.  If the user does not have permission to create
916
    // revisions, however, the form will not contain an element for
917
    // log so $node->log will be unset at this point.
91858
    if (!isset($node->log)) {
91935
      $node->log = '';
92035
    }
921
922
    // For the same reasons, make sure we have $node->teaser and
923
    // $node->body.  We should consider making these fields nullable
924
    // in a future version since node types are not required to use them.
92558
    if (!isset($node->teaser)) {
9260
      $node->teaser = '';
9270
    }
92858
    if (!isset($node->body)) {
9294
      $node->body = '';
9304
    }
93158
  }
93231
  elseif (!empty($node->revision)) {
9332
    $node->old_vid = $node->vid;
9342
  }
935
  else {
936
    // When updating a node, avoid clobberring an existing log entry with
an empty one.
93729
    if (empty($node->log)) {
93829
      unset($node->log);
93929
    }
940
  }
941
942
  // Set some required fields:
94388
  if (empty($node->created)) {
94417
    $node->created = REQUEST_TIME;
94517
  }
946
  // The changed timestamp is always updated for bookkeeping purposes
(revisions, searching, ...)
94788
  $node->changed = REQUEST_TIME;
948
94988
  $node->timestamp = REQUEST_TIME;
95088
  $update_node = TRUE;
951
952
  // Generate the node table query and the node_revisions table query.
95388
  if ($node->is_new) {
95458
    drupal_write_record('node', $node);
95558
    _node_save_revision($node, $user->uid);
95658
    $op = 'insert';
95758
  }
958
  else {
95931
    drupal_write_record('node', $node, 'nid');
96031
    if (!empty($node->revision)) {
9612
      _node_save_revision($node, $user->uid);
9622
    }
963
    else {
96429
      _node_save_revision($node, $user->uid, 'vid');
96529
      $update_node = FALSE;
966
    }
96731
    $op = 'update';
968
  }
96988
  if ($update_node) {
97059
    db_query('UPDATE {node} SET vid = %d WHERE nid = %d', $node->vid,
$node->nid);
97159
  }
972
973
  // Call the node specific callback (if any). This can be
974
  // node_invoke($node, 'insert') or
975
  // node_invoke($node, 'update').
97688
  node_invoke($node, $op);
97788
  node_invoke_nodeapi($node, $op);
978
979
  // Update the node access table for this node.
98088
  node_access_acquire_grants($node);
981
982
  // Clear the page and block caches.
98388
  cache_clear_all();
98488
}
985
986
/**
987
 * Helper function to save a revision with the uid of the current user.
988
 *
989
 * Node is taken by reference, becuse drupal_write_record() updates the
990
 * $node with the revision id, and we need to pass that back to the caller.
991
 */
9922366
function _node_save_revision(&$node, $uid, $update = NULL) {
99388
  $temp_uid = $node->uid;
99488
  $node->uid = $uid;
99588
  if (isset($update)) {
99629
    drupal_write_record('node_revisions', $node, $update);
99729
  }
998
  else {
99959
    drupal_write_record('node_revisions', $node);
1000
  }
100188
  $node->uid = $temp_uid;
100288
}
1003
1004
/**
1005
 * Delete a node.
1006
 */
10072366
function node_delete($nid) {
1008
100912
  $node = node_load($nid);
1010
101112
  if (node_access('delete', $node)) {
101212
    db_query('DELETE FROM {node} WHERE nid = %d', $node->nid);
101312
    db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);
1014
1015
    // Call the node-specific callback (if any):
101612
    node_invoke($node, 'delete');
101712
    node_invoke_nodeapi($node, 'delete');
1018
1019
    // Clear the page and block caches.
102012
    cache_clear_all();
1021
1022
    // Remove this node from the search index if needed.
1023
    // This code is implemented in node module rather than in search
module,
1024
    // because node module is implementing search module's API, not the
other
1025
    // way around.
102612
    if (module_exists('search')) {
10270
      search_wipe($node->nid, 'node');
10280
    }
102912
    watchdog('content', '@type: deleted %title.', array('@type' =>
$node->type, '%title' => $node->title));
103012
    drupal_set_message(t('@type %title has been deleted.', array('@type' =>
node_get_types('name', $node), '%title' => $node->title)));
103112
  }
103212
}
1033
1034
/**
1035
 * Generate a display of the given node.
1036
 *
1037
 * @param $node
1038
 *   A node array or node object.
1039
 * @param $teaser
1040
 *   Whether to display the teaser only or the full form.
1041
 * @param $page
1042
 *   Whether the node is being displayed by itself as a page.
1043
 * @param $links
1044
 *   Whether or not to display node links. Links are omitted for node
previews.
1045
 *
1046
 * @return
1047
 *   An HTML representation of the themed node.
1048
 */
10492366
function node_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) {
1050255
  $node = (object)$node;
1051
1052255
  $node = node_build_content($node, $teaser, $page);
1053
1054254
  if ($links) {
1055251
    $node->links = module_invoke_all('link', 'node', $node, $teaser);
1056251
    drupal_alter('link', $node->links, $node);
1057251
  }
1058
1059
  // Set the proper node part, then unset unused $node part so that a bad
1060
  // theme can not open a security hole.
1061254
  $content = drupal_render($node->content);
1062254
  if ($teaser) {
106329
    $node->teaser = $content;
106429
    unset($node->body);
106529
  }
1066
  else {
1067226
    $node->body = $content;
1068226
    unset($node->teaser);
1069
  }
1070
1071
  // Allow modules to modify the fully-built node.
1072254
  node_invoke_nodeapi($node, 'alter', $teaser, $page);
1073
1074254
  return theme('node', $node, $teaser, $page);
10750
}
1076
1077
/**
1078
 * Apply filters and build the node's standard elements.
1079
 */
10802366
function node_prepare($node, $teaser = FALSE) {
1081
  // First we'll overwrite the existing node teaser and body with
1082
  // the filtered copies! Then, we'll stick those into the content
1083
  // array and set the read more flag if appropriate.
1084257
  $node->readmore = (strlen($node->teaser) < strlen($node->body));
1085
1086257
  if ($teaser == FALSE) {
1087230
    $node->body = check_markup($node->body, $node->format, FALSE);
1088230
  }
1089
  else {
109028
    $node->teaser = check_markup($node->teaser, $node->format, FALSE);
1091
  }
1092
1093257
  $node->content['body'] = array(
1094257
    '#markup' => $teaser ? $node->teaser : $node->body,
1095257
    '#weight' => 0,
1096
  );
1097
1098257
  return $node;
10990
}
1100
1101
/**
1102
 * Builds a structured array representing the node's content.
1103
 *
1104
 * @param $node
1105
 *   A node object.
1106
 * @param $teaser
1107
 *   Whether to display the teaser only, as on the main page.
1108
 * @param $page
1109
 *   Whether the node is being displayed by itself as a page.
1110
 *
1111
 * @return
1112
 *   An structured array containing the individual elements
1113
 *   of the node's body.
1114
 */
11152366
function node_build_content($node, $teaser = FALSE, $page = FALSE) {
1116
1117
  // The build mode identifies the target for which the node is built.
1118266
  if (!isset($node->build_mode)) {
1119252
    $node->build_mode = NODE_BUILD_NORMAL;
1120252
  }
1121
1122
  // Remove the delimiter (if any) that separates the teaser from the body.
1123266
  $node->body = isset($node->body) ? str_replace('<!--break-->', '',
$node->body) : '';
1124
1125
  // The 'view' hook can be implemented to overwrite the default function
1126
  // to display nodes.
1127266
  if (node_hook($node, 'view')) {
112818
    $node = node_invoke($node, 'view', $teaser, $page);
112917
  }
1130
  else {
1131248
    $node = node_prepare($node, $teaser);
1132
  }
1133
1134
  // Allow modules to make their own additions to the node.
1135265
  node_invoke_nodeapi($node, 'view', $teaser, $page);
1136
1137265
  return $node;
11380
}
1139
1140
/**
1141
 * Generate a page displaying a single node, along with its comments.
1142
 */
11432366
function node_show($node, $cid, $message = FALSE) {
1144185
  if ($message) {
11451
    drupal_set_title(t('Revision of %title from %date', array('%title' =>
$node->title, '%date' => format_date($node->revision_timestamp))),
PASS_THROUGH);
11461
  }
1147185
  $output = node_view($node, FALSE, TRUE);
1148
1149184
  if (function_exists('comment_render') && $node->comment) {
1150149
    $output .= comment_render($node, $cid);
1151149
  }
1152
1153
  // Update the history table, stating that this user viewed this node.
1154184
  node_tag_new($node->nid);
1155
1156184
  return $output;
11570
}
1158
1159
/**
1160
 * Theme a log message.
1161
 *
1162
 * @ingroup themeable
1163
 */
11642366
function theme_node_log_message($log) {
11650
  return '<div class="log"><div class="title">' . t('Log') . ':</div>' .
$log . '</div>';
11660
}
1167
1168
/**
1169
 * Implementation of hook_perm().
1170
 */
11712366
function node_perm() {
1172
  $perms = array(
1173
    'administer content types' => array(
1174172
      'title' => t('Administer content types'),
1175172
      'description' => t('Manage content types and content type
administration settings.'),
1176172
    ),
1177
    'administer nodes' => array(
1178172
      'title' => t('Administer nodes'),
1179172
      'description' => t('Manage all information associated with site
content, such as author, publication date and current revision. %warning',
array('%warning' => t('Warning: Give to trusted roles only; this permission
has security implications.'))),
1180172
    ),
1181
    'access content' => array(
1182172
      'title' => t('Access content'),
1183172
      'description' => t('View published content.'),
1184172
    ),
1185
    'bypass node access' => array(
1186172
      'title' => t('Bypass node access'),
1187172
      'description' => t('View, edit and delete all site content. Users
with this permission will bypass any content-related access control.
%warning', array('%warning' => t('Warning: Give to trusted roles only; this
permission has security implications.'))),
1188172
    ),
1189
    'view revisions' => array(
1190172
      'title' => t('View revisions'),
1191172
      'description' => t('View content revisions.'),
1192172
    ),
1193
    'revert revisions' => array(
1194172
      'title' => t('Revert revisions'),
1195172
      'description' => t('Replace content with an older revision.'),
1196172
    ),
1197
    'delete revisions' => array(
1198172
      'title' => t('Delete revisions'),
1199172
      'description' => t('Delete content revisions.'),
1200172
    ),
1201172
  );
1202
1203172
  foreach (node_get_types() as $type) {
1204172
    if ($type->base == 'node_content') {
1205172
      $perms += node_list_permissions($type);
1206172
    }
1207172
  }
1208
1209172
  return $perms;
12100
}
1211
1212
/**
1213
 * Gather the rankings from the the hook_ranking implementations.
1214
 */
12152366
function _node_rankings() {
1216
  $rankings = array(
12177
    'total' => 0, 'join' => array(), 'score' => array(), 'args' => array(),
12187
  );
12197
  if ($ranking = module_invoke_all('ranking')) {
12207
    foreach ($ranking as $rank => $values) {
12217
      if ($node_rank = variable_get('node_rank_'. $rank, 0)) {
1222
        // If the table defined in the ranking isn't already joined, then
add it.
12231
        if (isset($values['join']) &&
!isset($rankings['join'][$values['join']])) {
12241
          $rankings['join'][$values['join']] = $values['join'];
12251
        }
1226
1227
        // Add the rankings weighted score multiplier value, handling NULL
gracefully.
12281
        $rankings['score'][] = '%f * COALESCE(('. $values['score'] .'),
0)';
1229
1230
        // Add the the administrator's weighted score multiplier value for
this ranking.
12311
        $rankings['total'] += $node_rank;
12321
        $rankings['arguments'][] = $node_rank;
1233
1234
        // Add any additional arguments used by this ranking.
12351
        if (isset($values['arguments'])) {
12361
          $rankings['arguments'] = array_merge($rankings['arguments'],
$values['arguments']);
12371
        }
12381
      }
12397
    }
12407
  }
12417
  return $rankings;
12420
}
1243
1244
1245
/**
1246
 * Implementation of hook_search().
1247
 */
12482366
function node_search($op = 'search', $keys = NULL) {
1249
  switch ($op) {
125012
    case 'name':
125111
      return t('Content');
1252
12537
    case 'reset':
12540
      db_query("UPDATE {search_dataset} SET reindex = %d WHERE type =
'node'", REQUEST_TIME);
12550
      return;
1256
12577
    case 'status':
12580
      $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status
= 1'));
12590
      $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"));
12600
      return array('remaining' => $remaining, 'total' => $total);
1261
12627
    case 'admin':
12630
      $form = array();
1264
      // Output form for defining rank factor weights.
12650
      $form['content_ranking'] = array(
12660
        '#type' => 'fieldset',
12670
        '#title' => t('Content ranking'),
1268
      );
12690
      $form['content_ranking']['#theme'] = 'node_search_admin';
12700
      $form['content_ranking']['info'] = array(
12710
        '#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>'
12720
      );
1273
1274
      // Note: reversed to reflect that higher number = higher ranking.
12750
      $options = drupal_map_assoc(range(0, 10));
12760
      foreach (module_invoke_all('ranking') as $var => $values) {
12770
        $form['content_ranking']['factors']['node_rank_'. $var] = array(
12780
          '#title' => $values['title'],
12790
          '#type' => 'select',
12800
          '#options' => $options,
12810
          '#default_value' => variable_get('node_rank_'. $var, 0),
1282
        );
12830
      }
12840
      return $form;
1285
12867
    case 'search':
1287
      // Build matching conditions
12887
      list($join1, $where1) = _db_rewrite_sql();
12897
      $arguments1 = array();
12907
      $conditions1 = 'n.status = 1';
1291
12927
      if ($type = search_query_extract($keys, 'type')) {
12932
        $types = array();
12942
        foreach (explode(',', $type) as $t) {
12952
          $types[] = "n.type = '%s'";
12962
          $arguments1[] = $t;
12972
        }
12982
        $conditions1 .= ' AND (' . implode(' OR ', $types) . ')';
12992
        $keys = search_query_insert($keys, 'type');
13002
      }
1301
13027
      if ($category = search_query_extract($keys, 'category')) {
13030
        $categories = array();
13040
        foreach (explode(',', $category) as $c) {
13050
          $categories[] = "tn.tid = %d";
13060
          $arguments1[] = $c;
13070
        }
13080
        $conditions1 .= ' AND (' . implode(' OR ', $categories) . ')';
13090
        $join1 .= ' INNER JOIN {term_node} tn ON n.vid = tn.vid';
13100
        $keys = search_query_insert($keys, 'category');
13110
      }
1312
13137
      if ($languages = search_query_extract($keys, 'language')) {
13140
        $categories = array();
13150
        foreach (explode(',', $languages) as $l) {
13160
          $categories[] = "n.language = '%s'";
13170
          $arguments1[] = $l;
13180
        }
13190
        $conditions1 .= ' AND (' . implode(' OR ', $categories) . ')';
13200
        $keys = search_query_insert($keys, 'language');
13210
      }
1322
1323
      // Get the ranking expressions.
13247
      $rankings = _node_rankings();
1325
1326
      // When all search factors are disabled (ie they have a weight of
zero),
1327
      // The default score is based only on keyword relevance.
13287
      if ($rankings['total'] == 0) {
13296
        $total = 1;
13306
        $arguments2 = array();
13316
        $join2 = '';
13326
        $select2 = 'i.relevance AS score';
13336
      }
1334
      else {
13351
        $total = $rankings['total'];
13361
        $arguments2 = $rankings['arguments'];
13371
        $join2 = implode(' ', $rankings['join']);
13381
        $select2 = '('. implode(' + ', $rankings['score']) .') AS score';
1339
      }
1340
1341
      // Do search.
13427
      $find = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid =
i.sid ' . $join1, $conditions1 . (empty($where1) ? '' : ' AND ' . $where1),
$arguments1, $select2, $join2, $arguments2);
1343
1344
      // Load results.
13457
      $results = array();
13467
      foreach ($find as $item) {
1347
        // Build the node body.
13484
        $node = node_load($item->sid);
13494
        $node->build_mode = NODE_BUILD_SEARCH_RESULT;
13504
        $node = node_build_content($node, FALSE, FALSE);
13514
        $node->body = drupal_render($node->content);
1352
1353
        // Fetch comments for snippet.
13544
        $node->body .= module_invoke('comment', 'nodeapi', $node,
'update_index');
1355
        // Fetch terms for snippet.
13564
        $node->body .= module_invoke('taxonomy', 'nodeapi', $node,
'update_index');
1357
13584
        $extra = node_invoke_nodeapi($node, 'search_result');
1359
13604
        $results[] = array(
13614
          'link' => url('node/' . $item->sid, array('absolute' => TRUE)),
13624
          'type' => check_plain(node_get_types('name', $node)),
13634
          'title' => $node->title,
13644
          'user' => theme('username', $node),
13654
          'date' => $node->changed,
13664
          'node' => $node,
13674
          'extra' => $extra,
13684
          'score' => $total ? ($item->score / $total) : 0,
13694
          'snippet' => search_excerpt($keys, $node->body),
1370
        );
13714
      }
13727
      return $results;
13730
  }
13740
}
1375
1376
/**
1377
 * Implementation of hook_ranking().
1378
 */
13792366
function node_ranking() {
1380
  // Create the ranking array and add the basic ranking options.
1381
  $ranking = array(
1382
    'relevance' => array(
13837
      'title' => t('Keyword relevance'),
1384
      // Average relevance values hover around 0.15
13857
      'score' => 'i.relevance',
13867
    ),
1387
    'sticky' => array(
13887
      'title' => t('Content is sticky at top of lists'),
1389
      // The sticky flag is either 0 or 1, which is automatically
normalized.
13907
      'score' => 'n.sticky',
13917
    ),
1392
    'promote' => array(
13937
      'title' => t('Content is promoted to the front page'),
1394
      // The promote flag is either 0 or 1, which is automatically
normalized.
13957
      'score' => 'n.promote',
13967
    ),
13977
  );
1398
1399
  // Add relevance based on creation or changed date.
14007
  if ($node_cron_last = variable_get('node_cron_last', 0)) {
14016
    $ranking['recent'] = array(
14026
      'title' => t('Recently posted'),
1403
      // Exponential decay with half-life of 6 months, starting at last
indexed node
14046
      'score' => '(POW(2, GREATEST(n.created, n.changed) - %d) * 6.43e-8)',
14056
      'arguments' => array($node_cron_last),
1406
    );
14076
  }
14087
  return $ranking;
14090
}
1410
1411
/**
1412
 * Implementation of hook_user_delete().
1413
 */
14142366
function node_user_delete(&$edit, &$user) {
14152
  db_query('UPDATE {node} SET uid = 0 WHERE uid = %d', $user->uid);
14162
  db_query('UPDATE {node_revisions} SET uid = 0 WHERE uid = %d',
$user->uid);
14172
}
1418
1419
/**
1420
 * Theme the content ranking part of the search settings admin page.
1421
 *
1422
 * @ingroup themeable
1423
 */
14242366
function theme_node_search_admin($form) {
14250
  $output = drupal_render($form['info']);
1426
14270
  $header = array(t('Factor'), t('Weight'));
14280
  foreach (element_children($form['factors']) as $key) {
14290
    $row = array();
14300
    $row[] = $form['factors'][$key]['#title'];
14310
    unset($form['factors'][$key]['#title']);
14320
    $row[] = drupal_render($form['factors'][$key]);
14330
    $rows[] = $row;
14340
  }
14350
  $output .= theme('table', $header, $rows);
1436
14370
  $output .= drupal_render($form);
14380
  return $output;
14390
}
1440
1441
/**
1442
 * Retrieve the comment mode for the given node ID (none, read, or
read/write).
1443
 */
14442366
function node_comment_mode($nid) {
1445179
  static $comment_mode;
1446179
  if (!isset($comment_mode[$nid])) {
1447179
    $comment_mode[$nid] = db_result(db_query('SELECT comment FROM {node}
WHERE nid = %d', $nid));
1448179
  }
1449179
  return $comment_mode[$nid];
14500
}
1451
1452
/**
1453
 * Implementation of hook_link().
1454
 */
14552366
function node_link($type, $node = NULL, $teaser = FALSE) {
1456251
  $links = array();
1457
1458251
  if ($type == 'node') {
1459251
    if ($teaser == 1 && $node->teaser && !empty($node->readmore)) {
14600
      $links['node_read_more'] = array(
14610
        'title' => t('Read more'),
14620
        'href' => "node/$node->nid",
1463
        // The title attribute gets escaped when the links are processed,
so
1464
        // there is no need to escape here.
14650
        'attributes' => array('title' => t('Read the rest of !title.',
array('!title' => $node->title)))
14660
      );
14670
    }
1468251
  }
1469
1470251
  return $links;
14710
}
1472
14732366
function _node_revision_access($node, $op = 'view') {
1474232
  static $access = array();
1475232
  if (!isset($access[$node->vid])) {
1476232
    $node_current_revision = node_load($node->nid);
1477232
    $is_current_revision = $node_current_revision->vid == $node->vid;
1478
    // There should be at least two revisions. If the vid of the given node
1479
    // and the vid of the current revision differs, then we already have
two
1480
    // different revisions so there is no need for a separate database
check.
1481
    // Also, if you try to revert to or delete the current revision, that's
1482
    // not good.
1483232
    if ($is_current_revision && (db_result(db_query('SELECT COUNT(vid) FROM
{node_revisions} WHERE nid = %d', $node->nid)) == 1 || $op == 'update' ||
$op == 'delete')) {
1484224
      $access[$node->vid] = FALSE;
1485224
    }
14868
    elseif (user_access('administer nodes')) {
14870
      $access[$node->vid] = TRUE;
14880
    }
1489
    else {
14908
      $map = array('view' => 'view revisions', 'update' => 'revert
revisions', 'delete' => 'delete revisions');
1491
      // First check the user permission, second check the access to the
1492
      // current revision and finally, if the node passed in is not the
current
1493
      // revision then access to that, too.
14948
      $access[$node->vid] = isset($map[$op]) && user_access($map[$op]) &&
node_access($op, $node_current_revision) && ($is_current_revision ||
node_access($op, $node));
1495
    }
1496232
  }
1497232
  return $access[$node->vid];
14980
}
1499
15002366
function _node_add_access() {
15011748
  $types = node_get_types();
15021748
  foreach ($types as $type) {
15031748
    if (node_hook($type->type, 'form') && node_access('create',
$type->type)) {
1504456
      return TRUE;
15050
    }
15061444
  }
15071292
  return FALSE;
15080
}
1509
1510
/**
1511
 * Implementation of hook_menu().
1512
 */
15132366
function node_menu() {
1514161
  $items['admin/content/node'] = array(
1515161
    'title' => 'Content',
1516161
    'description' => "View, edit, and delete your site's content.",
1517161
    'page callback' => 'drupal_get_form',
1518161
    'page arguments' => array('node_admin_content'),
1519161
    'access arguments' => array('administer nodes'),
1520
  );
1521
1522161
  $items['admin/content/node/overview'] = array(
1523161
    'title' => 'List',
1524161
    'type' => MENU_DEFAULT_LOCAL_TASK,
1525161
    'weight' => -10,
1526
  );
1527
1528161
  $items['admin/content/node-settings'] = array(
1529161
    'title' => 'Post settings',
1530161
    'description' => 'Control posting behavior, such as teaser length,
requiring previews before posting, and the number of posts on the front
page.',
1531161
    'page callback' => 'drupal_get_form',
1532161
    'page arguments' => array('node_configure'),
1533161
    'access arguments' => array('administer nodes'),
1534
  );
1535161
  $items['admin/content/node-settings/rebuild'] = array(
1536161
    'title' => 'Rebuild permissions',
1537161
    'page arguments' => array('node_configure_rebuild_confirm'),
1538
    // Any user than can potentially trigger a
node_acess_needs_rebuild(TRUE)
1539
    // has to be allowed access to the 'node access rebuild' confirm form.
1540161
    'access arguments' => array('access administration pages'),
1541161
    'type' => MENU_CALLBACK,
1542
  );
1543
1544161
  $items['admin/build/types'] = array(
1545161
    'title' => 'Content types',
1546161
    'description' => 'Manage posts by content type, including default
status, front page promotion, comment settings, etc.',
1547161
    'page callback' => 'node_overview_types',
1548161
    'access arguments' => array('administer content types'),
1549
  );
1550161
  $items['admin/build/types/list'] = array(
1551161
    'title' => 'List',
1552161
    'type' => MENU_DEFAULT_LOCAL_TASK,
1553161
    'weight' => -10,
1554
  );
1555161
  $items['admin/build/types/add'] = array(
1556161
    'title' => 'Add content type',
1557161
    'page callback' => 'drupal_get_form',
1558161
    'page arguments' => array('node_type_form'),
1559161
    'access arguments' => array('administer content types'),
1560161
    'type' => MENU_LOCAL_TASK,
1561
  );
1562161
  $items['node'] = array(
1563161
    'title' => 'Content',
1564161
    'page callback' => 'node_page_default',
1565161
    'access arguments' => array('access content'),
1566161
    'type' => MENU_CALLBACK,
1567
  );
1568161
  $items['node/add'] = array(
1569161
    'title' => 'Create content',
1570161
    'page callback' => 'node_add_page',
1571161
    'access callback' => '_node_add_access',
1572161
    'weight' => 1,
1573
  );
1574161
  $items['rss.xml'] = array(
1575161
    'title' => 'RSS feed',
1576161
    'page callback' => 'node_feed',
1577161
    'access arguments' => array('access content'),
1578161
    'type' => MENU_CALLBACK,
1579
  );
1580161
  foreach (node_get_types('types', NULL, TRUE) as $type) {
1581161
    $type_url_str = str_replace('_', '-', $type->type);
1582161
    $items['node/add/' . $type_url_str] = array(
1583161
      'title' => $type->name,
1584161
      'title callback' => 'check_plain',
1585161
      'page callback' => 'node_add',
1586161
      'page arguments' => array(2),
1587161
      'access callback' => 'node_access',
1588161
      'access arguments' => array('create', $type->type),
1589161
      'description' => $type->description,
1590
    );
1591161
    $items['admin/build/node-type/' . $type_url_str] = array(
1592161
      'title' => $type->name,
1593161
      'page callback' => 'drupal_get_form',
1594161
      'page arguments' => array('node_type_form', $type),
1595161
      'access arguments' => array('administer content types'),
1596161
      'type' => MENU_CALLBACK,
1597
    );
1598161
    $items['admin/build/node-type/' . $type_url_str . '/edit'] = array(
1599161
      'title' => 'Edit',
1600161
      'type' => MENU_DEFAULT_LOCAL_TASK,
1601
    );
1602161
    $items['admin/build/node-type/' . $type_url_str . '/delete'] = array(
1603161
      'title' => 'Delete',
1604161
      'page arguments' => array('node_type_delete_confirm', $type),
1605161
      'access arguments' => array('administer content types'),
1606161
      'type' => MENU_CALLBACK,
1607
    );
1608161
  }
1609161
  $items['node/%node'] = array(
1610161
    'title callback' => 'node_page_title',
1611161
    'title arguments' => array(1),
1612161
    'page callback' => 'node_page_view',
1613161
    'page arguments' => array(1),
1614161
    'access callback' => 'node_access',
1615161
    'access arguments' => array('view', 1),
1616161
    'type' => MENU_CALLBACK);
1617161
  $items['node/%node/view'] = array(
1618161
    'title' => 'View',
1619161
    'type' => MENU_DEFAULT_LOCAL_TASK,
1620161
    'weight' => -10);
1621161
  $items['node/%node/edit'] = array(
1622161
    'title' => 'Edit',
1623161
    'page callback' => 'node_page_edit',
1624161
    'page arguments' => array(1),
1625161
    'access callback' => 'node_access',
1626161
    'access arguments' => array('update', 1),
1627161
    'weight' => 1,
1628161
    'type' => MENU_LOCAL_TASK,
1629
  );
1630161
  $items['node/%node/delete'] = array(
1631161
    'title' => 'Delete',
1632161
    'page callback' => 'drupal_get_form',
1633161
    'page arguments' => array('node_delete_confirm', 1),
1634161
    'access callback' => 'node_access',
1635161
    'access arguments' => array('delete', 1),
1636161
    'weight' => 1,
1637161
    'type' => MENU_CALLBACK);
1638161
  $items['node/%node/revisions'] = array(
1639161
    'title' => 'Revisions',
1640161
    'page callback' => 'node_revision_overview',
1641161
    'page arguments' => array(1),
1642161
    'access callback' => '_node_revision_access',
1643161
    'access arguments' => array(1),
1644161
    'weight' => 2,
1645161
    'type' => MENU_LOCAL_TASK,
1646
  );
1647161
  $items['node/%node/revisions/%/view'] = array(
1648161
    'title' => 'Revisions',
1649161
    'load arguments' => array(3),
1650161
    'page callback' => 'node_show',
1651161
    'page arguments' => array(1, NULL, TRUE),
1652161
    'access callback' => '_node_revision_access',
1653161
    'access arguments' => array(1),
1654161
    'type' => MENU_CALLBACK,
1655
  );
1656161
  $items['node/%node/revisions/%/revert'] = array(
1657161
    'title' => 'Revert to earlier revision',
1658161
    'load arguments' => array(3),
1659161
    'page callback' => 'drupal_get_form',
1660161
    'page arguments' => array('node_revision_revert_confirm', 1),
1661161
    'access callback' => '_node_revision_access',
1662161
    'access arguments' => array(1, 'update'),
1663161
    'type' => MENU_CALLBACK,
1664
  );
1665161
  $items['node/%node/revisions/%/delete'] = array(
1666161
    'title' => 'Delete earlier revision',
1667161
    'load arguments' => array(3),
1668161
    'page callback' => 'drupal_get_form',
1669161
    'page arguments' => array('node_revision_delete_confirm', 1),
1670161
    'access callback' => '_node_revision_access',
1671161
    'access arguments' => array(1, 'delete'),
1672161
    'type' => MENU_CALLBACK,
1673
  );
1674161
  return $items;
16750
}
1676
1677
/**
1678
 * Title callback.
1679
 */
16802366
function node_page_title($node) {
1681247
  return $node->title;
16820
}
1683
1684
/**
1685
 * Implementation of hook_init().
1686
 */
16872366
function node_init() {
16882366
  drupal_add_css(drupal_get_path('module', 'node') . '/node.css');
16892366
}
1690
16912366
function node_last_changed($nid) {
169229
  $node = db_fetch_object(db_query('SELECT changed FROM {node} WHERE nid =
%d', $nid));
169329
  return ($node->changed);
16940
}
1695
1696
/**
1697
 * Return a list of all the existing revision numbers.
1698
 */
16992366
function node_revision_list($node) {
17003
  $revisions = array();
17013
  $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);
17023
  while ($revision = db_fetch_object($result)) {
17033
    $revisions[$revision->vid] = $revision;
17043
  }
1705
17063
  return $revisions;
17070
}
1708
1709
/**
1710
 * Implementation of hook_block().
1711
 */
17122366
function node_block($op = 'list', $delta = '') {
171329
  if ($op == 'list') {
171429
    $blocks['syndicate']['info'] = t('Syndicate');
1715
    // Not worth caching.
171629
    $blocks['syndicate']['cache'] = BLOCK_NO_CACHE;
171729
    return $blocks;
17180
  }
17190
  elseif ($op == 'view') {
17200
    $block['subject'] = t('Syndicate');
17210
    $block['content'] = theme('feed_icon', url('rss.xml'), t('Syndicate'));
1722
17230
    return $block;
17240
  }
17250
}
1726
1727
/**
1728
 * A generic function for generating RSS feeds from a set of nodes.
1729
 *
1730
 * @param $nids
1731
 *   An array of node IDs (nid). Defaults to FALSE so empty feeds can be
1732
 *   generated with passing an empty array, if no items are to be added
1733
 *   to the feed.
1734
 * @param $channel
1735
 *   An associative array containing title, link, description and other
keys.
1736
 *   The link should be an absolute URL.
1737
 */
17382366
function node_feed($nids = FALSE, $channel = array()) {
173910
  global $base_url, $language;
1740
174110
  if ($nids === FALSE) {
17426
    $nids = array();
17436
    $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));
17446
    while ($row = db_fetch_object($result)) {
17450
      $nids[] = $row->nid;
17460
    }
17476
  }
1748
174910
  $item_length = variable_get('feed_item_length', 'teaser');
175010
  $namespaces = array('xmlns:dc' => 'http://purl.org/dc/elements/1.1/');
1751
175210
  $items = '';
175310
  foreach ($nids as $nid) {
1754
    // Load the specified node:
17550
    $item = node_load($nid);
17560
    $item->build_mode = NODE_BUILD_RSS;
17570
    $item->link = url("node/$nid", array('absolute' => TRUE));
1758
17590
    if ($item_length != 'title') {
17600
      $teaser = ($item_length == 'teaser');
1761
1762
      // Filter and prepare node teaser
17630
      if (node_hook($item, 'view')) {
17640
        $item = node_invoke($item, 'view', $teaser, FALSE);
17650
      }
1766
      else {
17670
        $item = node_prepare($item, $teaser);
1768
      }
1769
1770
      // Allow modules to change $node->teaser before viewing.
17710
      node_invoke_nodeapi($item, 'view', $teaser, FALSE);
17720
    }
1773
1774
    // Allow modules to add additional item fields and/or modify $item
17750
    $extra = node_invoke_nodeapi($item, 'rss_item');
17760
    $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'))));
17770
    foreach ($extra as $element) {
17780
      if (isset($element['namespace'])) {
17790
        $namespaces = array_merge($namespaces, $element['namespace']);
17800
      }
17810
    }
1782
1783
    // Prepare the item description
1784
    switch ($item_length) {
17850
      case 'fulltext':
17860
        $item_text = $item->body;
17870
        break;
17880
      case 'teaser':
17890
        $item_text = $item->teaser;
17900
        if (!empty($item->readmore)) {
17910
          $item_text .= '<p>' . l(t('read more'), 'node/' . $item->nid,
array('absolute' => TRUE, 'attributes' => array('target' => '_blank'))) .
'</p>';
17920
        }
17930
        break;
17940
      case 'title':
17950
        $item_text = '';
17960
        break;
17970
    }
1798
17990
    $items .= format_rss_item($item->title, $item->link, $item_text,
$extra);
18000
  }
1801
1802
  $channel_defaults = array(
180310
    'version'     => '2.0',
180410
    'title'       => variable_get('site_name', 'Drupal'),
180510
    'link'        => $base_url,
180610
    'description' => variable_get('site_mission', ''),
180710
    'language'    => $language->language
180810
  );
180910
  $channel = array_merge($channel_defaults, $channel);
1810
181110
  $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
181210
  $output .= "<rss version=\"" . $channel["version"] . "\" xml:base=\"" .
$base_url . "\" " . drupal_attributes($namespaces) . ">\n";
181310
  $output .= format_rss_channel($channel['title'], $channel['link'],
$channel['description'], $items, $channel['language']);
181410
  $output .= "</rss>\n";
1815
181610
  drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
181710
  print $output;
181810
}
1819
1820
/**
1821
 * Menu callback; Generate a listing of promoted nodes.
1822
 */
18232366
function node_page_default() {
1824244
  $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));
1825
1826244
  $output = '';
1827244
  $num_rows = FALSE;
1828244
  while ($node = db_fetch_object($result)) {
182928
    $output .= node_view(node_load($node->nid), 1);
183028
    $num_rows = TRUE;
183128
  }
1832
1833244
  if ($num_rows) {
183428
    $feed_url = url('rss.xml', array('absolute' => TRUE));
183528
    drupal_add_feed($feed_url, variable_get('site_name', 'Drupal') . ' ' .
t('RSS'));
183628
    $output .= theme('pager', NULL, variable_get('default_nodes_main',
10));
183728
  }
1838
  else {
1839216
    $default_message = '<h1 class="title">' . t('Welcome to your new Drupal
website!') . '</h1>';
1840216
    $default_message .= '<p>' . t('Please follow these steps to set up and
start using your website:') . '</p>';
1841216
    $default_message .= '<ol>';
1842216
    $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>';
1843216
    $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>';
1844216
    $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>';
1845216
    $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>';
1846216
    $default_message .= '</ol>';
1847216
    $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>';
1848
1849216
    $output = '<div id="first-time">' . $default_message . '</div>';
1850
  }
1851244
  drupal_set_title('');
1852
1853244
  return $output;
18540
}
1855
1856
/**
1857
 * Menu callback; view a single node.
1858
 */
18592366
function node_page_view($node, $cid = NULL) {
1860184
  drupal_set_title($node->title);
1861184
  return node_show($node, $cid);
18620
}
1863
1864
/**
1865
 * Implementation of hook_update_index().
1866
 */
18672366
function node_update_index() {
18682
  $limit = (int)variable_get('search_cron_limit', 100);
1869
1870
  // Store the maximum possible comments per thread (used for ranking by
reply count)
18712
  variable_set('node_cron_comments_scale', 1.0 / max(1,
db_result(db_query('SELECT MAX(comment_count) FROM
{node_comment_statistics}'))));
18722
  variable_set('node_cron_views_scale', 1.0 / max(1,
db_result(db_query('SELECT MAX(totalcount) FROM {node_counter}'))));
1873
18742
  $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);
1875
18762
  while ($node = db_fetch_object($result)) {
18772
    _node_index_node($node);
18782
  }
18792
}
1880
1881
/**
1882
 * Index a single node.
1883
 *
1884
 * @param $node
1885
 *   The node to index.
1886
 */
18872366
function _node_index_node($node) {
18882
  $node = node_load($node->nid);
1889
1890
  // save the changed time of the most recent indexed node, for the search
results half-life calculation
18912
  variable_set('node_cron_last', $node->changed);
1892
1893
  // Build the node body.
18942
  $node->build_mode = NODE_BUILD_SEARCH_INDEX;
18952
  $node = node_build_content($node, FALSE, FALSE);
18962
  $node->body = drupal_render($node->content);
1897
18982
  $text = '<h1>' . check_plain($node->title) . '</h1>' . $node->body;
1899
1900
  // Fetch extra data normally not visible
19012
  $extra = node_invoke_nodeapi($node, 'update_index');
19022
  foreach ($extra as $t) {
19032
    $text .= $t;
19042
  }
1905
1906
  // Update index
19072
  search_index($node->nid, 'node', $text);
19082
}
1909
1910
/**
1911
 * Implementation of hook_form_alter().
1912
 */
19132366
function node_form_alter(&$form, $form_state, $form_id) {
1914
  // Advanced node search form
19151575
  if ($form_id == 'search_form' && $form['module']['#value'] == 'node' &&
user_access('use advanced search')) {
1916
    // Keyword boxes:
191711
    $form['advanced'] = array(
191811
      '#type' => 'fieldset',
191911
      '#title' => t('Advanced search'),
192011
      '#collapsible' => TRUE,
192111
      '#collapsed' => TRUE,
192211
      '#attributes' => array('class' => 'search-advanced'),
1923
    );
192411
    $form['advanced']['keywords'] = array(
192511
      '#prefix' => '<div class="criterion">',
192611
      '#suffix' => '</div>',
1927
    );
192811
    $form['advanced']['keywords']['or'] = array(
192911
      '#type' => 'textfield',
193011
      '#title' => t('Containing any of the words'),
193111
      '#size' => 30,
193211
      '#maxlength' => 255,
1933
    );
193411
    $form['advanced']['keywords']['phrase'] = array(
193511
      '#type' => 'textfield',
193611
      '#title' => t('Containing the phrase'),
193711
      '#size' => 30,
193811
      '#maxlength' => 255,
1939
    );
194011
    $form['advanced']['keywords']['negative'] = array(
194111
      '#type' => 'textfield',
194211
      '#title' => t('Containing none of the words'),
194311
      '#size' => 30,
194411
      '#maxlength' => 255,
1945
    );
1946
1947
    // Taxonomy box:
194811
    if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
19490
      $form['advanced']['category'] = array(
19500
        '#type' => 'select',
19510
        '#title' => t('Only in the category(s)'),
19520
        '#prefix' => '<div class="criterion">',
19530
        '#size' => 10,
19540
        '#suffix' => '</div>',
19550
        '#options' => $taxonomy,
19560
        '#multiple' => TRUE,
1957
      );
19580
    }
1959
1960
    // Node types:
196111
    $types = array_map('check_plain', node_get_types('names'));
196211
    $form['advanced']['type'] = array(
196311
      '#type' => 'checkboxes',
196411
      '#title' => t('Only of the type(s)'),
196511
      '#prefix' => '<div class="criterion">',
196611
      '#suffix' => '</div>',
196711
      '#options' => $types,
1968
    );
196911
    $form['advanced']['submit'] = array(
197011
      '#type' => 'submit',
197111
      '#value' => t('Advanced search'),
197211
      '#prefix' => '<div class="action">',
197311
      '#suffix' => '</div>',
1974
    );
1975
1976
    // Languages:
197711
    $language_options = array();
197811
    foreach (language_list('language') as $key => $object) {
197911
      $language_options[$key] = $object->name;
198011
    }
198111
    if (count($language_options) > 1) {
19820
      $form['advanced']['language'] = array(
19830
        '#type' => 'checkboxes',
19840
        '#title' => t('Languages'),
19850
        '#prefix' => '<div class="criterion">',
19860
        '#suffix' => '</div>',
19870
        '#options' => $language_options,
1988
      );
19890
    }
1990
1991
199211
    $form['#validate'][] = 'node_search_validate';
199311
  }
19941575
}
1995
1996
/**
1997
 * Form API callback for the search form. Registered in node_form_alter().
1998
 */
19992366
function node_search_validate($form, &$form_state) {
2000
  // Initialise using any existing basic search keywords.
20013
  $keys = $form_state['values']['processed_keys'];
2002
2003
  // Insert extra restrictions into the search keywords string.
20043
  if (isset($form_state['values']['type']) &&
is_array($form_state['values']['type'])) {
2005
    // Retrieve selected types - Forms API sets the value of unselected
checkboxes to 0.
20063
    $form_state['values']['type'] =
array_filter($form_state['values']['type']);
20073
    if (count($form_state['values']['type'])) {
20082
      $keys = search_query_insert($keys, 'type', implode(',',
array_keys($form_state['values']['type'])));
20092
    }
20103
  }
2011
20123
  if (isset($form_state['values']['category']) &&
is_array($form_state['values']['category'])) {
20130
    $keys = search_query_insert($keys, 'category', implode(',',
$form_state['values']['category']));
20140
  }
20153
  if (isset($form_state['values']['language']) &&
is_array($form_state['values']['language'])) {
20160
    $keys = search_query_insert($keys, 'language', implode(',',
array_filter($form_state['values']['language'])));
20170
  }
20183
  if ($form_state['values']['or'] != '') {
20193
    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' .
$form_state['values']['or'], $matches)) {
20203
      $keys .= ' ' . implode(' OR ', $matches[1]);
20213
    }
20223
  }
20233
  if ($form_state['values']['negative'] != '') {
20240
    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' .
$form_state['values']['negative'], $matches)) {
20250
      $keys .= ' -' . implode(' -', $matches[1]);
20260
    }
20270
  }
20283
  if ($form_state['values']['phrase'] != '') {
20290
    $keys .= ' "' . str_replace('"', ' ', $form_state['values']['phrase'])
. '"';
20300
  }
20313
  if (!empty($keys)) {
20323
    form_set_value($form['basic']['inline']['processed_keys'], trim($keys),
$form_state);
20333
  }
20343
}
2035
2036
/**
2037
 * @defgroup node_access Node access rights
2038
 * @{
2039
 * The node access system determines who can do what to which nodes.
2040
 *
2041
 * In determining access rights for a node, node_access() first checks
2042
 * whether the user has the "administer nodes" permission. Such users have
2043
 * unrestricted access to all nodes. Then the node module's hook_access()
2044
 * is called, and a TRUE or FALSE return value will grant or deny access.
2045
 * This allows, for example, the blog module to always grant access to the
2046
 * blog author, and for the book module to always deny editing access to
2047
 * PHP pages.
2048
 *
2049
 * If node module does not intervene (returns NULL), then the
2050
 * node_access table is used to determine access. All node access
2051
 * modules are queried using hook_node_grants() to assemble a list of
2052
 * "grant IDs" for the user. This list is compared against the table.
2053
 * If any row contains the node ID in question (or 0, which stands for "all
2054
 * nodes"), one of the grant IDs returned, and a value of TRUE for the
2055
 * operation in question, then access is granted. Note that this table is a
2056
 * list of grants; any matching row is sufficient to grant access to the
2057
 * node.
2058
 *
2059
 * In node listings, the process above is followed except that
2060
 * hook_access() is not called on each node for performance reasons and for
2061
 * proper functioning of the pager system. When adding a node listing to
your
2062
 * module, be sure to use db_rewrite_sql() to add
2063
 * the appropriate clauses to your query for access checks.
2064
 *
2065
 * To see how to write a node access module of your own, see
2066
 * node_access_example.module.
2067
 */
2068
2069
/**
2070
 * Determine whether the current user may perform the given operation on
the
2071
 * specified node.
2072
 *
2073
 * @param $op
2074
 *   The operation to be performed on the node. Possible values are:
2075
 *   - "view"
2076
 *   - "update"
2077
 *   - "delete"
2078
 *   - "create"
2079
 * @param $node
2080
 *   The node object (or node array) on which the operation is to be
performed,
2081
 *   or node type (e.g. 'forum') for "create" operation.
2082
 * @param $account
2083
 *   Optional, a user object representing the user for whom the operation
is to
2084
 *   be performed. Determines access for a user other than the current
user.
2085
 * @return
2086
 *   TRUE if the operation may be performed.
2087
 */
20882366
function node_access($op, $node, $account = NULL) {
20891856
  global $user;
2090
20911856
  if (!$node) {
20920
    return FALSE;
20930
  }
2094
  // Convert the node to an object if necessary:
20951856
  if ($op != 'create') {
2096373
    $node = (object)$node;
2097373
  }
2098
  // If no user object is supplied, the access check is for the current
user.
20991856
  if (empty($account)) {
21001856
    $account = $user;
21011856
  }
2102
  // If the node is in a restricted format, disallow editing.
21031856
  if ($op == 'update' && !filter_access($node->format)) {
21040
    return FALSE;
21050
  }
2106
21071856
  if (user_access('bypass node access', $account)) {
21083
    return TRUE;
21090
  }
2110
21111853
  if (!user_access('access content', $account)) {
21120
    return FALSE;
21130
  }
2114
2115
  // Can't use node_invoke('access', $node), because the access hook takes
the
2116
  // $op parameter before the $node parameter.
21171853
  $base = node_get_types('base', $node);
21181853
  $access = module_invoke($base, 'access', $op, $node, $account);
21191853
  if (!is_null($access)) {
21201830
    return $access;
21210
  }
2122
2123
  // If the module did not override the access rights, use those set in the
2124
  // node_access table.
2125315
  if ($op != 'create' && $node->nid && $node->status) {
2126314
    $grants = array();
2127314
    foreach (node_access_grants($op, $account) as $realm => $gids) {
2128314
      foreach ($gids as $gid) {
2129314
        $grants[] = "(gid = $gid AND realm = '$realm')";
2130314
      }
2131314
    }
2132
2133314
    $grants_sql = '';
2134314
    if (count($grants)) {
2135314
      $grants_sql = 'AND (' . implode(' OR ', $grants) . ')';
2136314
    }
2137
2138314
    $sql = "SELECT COUNT(*) FROM {node_access} WHERE (nid = 0 OR nid = %d)
$grants_sql AND grant_$op >= 1";
2139314
    $result = db_query($sql, $node->nid);
2140314
    return (db_result($result));
21410
  }
2142
2143
  // Let authors view their own nodes.
21441
  if ($op == 'view' && $account->uid == $node->uid && $account->uid != 0) {
21451
    return TRUE;
21460
  }
2147
21481
  return FALSE;
21490
}
2150
2151
/**
2152
 * Generate an SQL join clause for use in fetching a node listing.
2153
 *
2154
 * @param $node_alias
2155
 *   If the node table has been given an SQL alias other than the default
2156
 *   "n", that must be passed here.
2157
 * @param $node_access_alias
2158
 *   If the node_access table has been given an SQL alias other than the
default
2159
 *   "na", that must be passed here.
2160
 * @return
2161
 *   An SQL join clause.
2162
 */
21632366
function _node_access_join_sql($node_alias = 'n', $node_access_alias =
'na') {
21640
  if (user_access('bypass node access')) {
21650
    return '';
21660
  }
2167
21680
  return 'INNER JOIN {node_access} ' . $node_access_alias . ' ON ' .
$node_access_alias . '.nid = ' . $node_alias . '.nid';
21690
}
2170
2171
/**
2172
 * Generate an SQL where clause for use in fetching a node listing.
2173
 *
2174
 * @param $op
2175
 *   The operation that must be allowed to return a node.
2176
 * @param $node_access_alias
2177
 *   If the node_access table has been given an SQL alias other than the
default
2178
 *   "na", that must be passed here.
2179
 * @param $account
2180
 *   The user object for the user performing the operation. If omitted, the
2181
 *   current user is used.
2182
 * @return
2183
 *   An SQL where clause.
2184
 */
21852366
function _node_access_where_sql($op = 'view', $node_access_alias = 'na',
$account = NULL) {
21860
  if (user_access('bypass node access')) {
21870
    return;
21880
  }
2189
21900
  $grants = array();
21910
  foreach (node_access_grants($op, $account) as $realm => $gids) {
21920
    foreach ($gids as $gid) {
21930
      $grants[] = "($node_access_alias.gid = $gid AND
$node_access_alias.realm = '$realm')";
21940
    }
21950
  }
2196
21970
  $grants_sql = '';
21980
  if (count($grants)) {
21990
    $grants_sql = 'AND (' . implode(' OR ', $grants) . ')';
22000
  }
2201
22020
  $sql = "$node_access_alias.grant_$op >= 1 $grants_sql";
22030
  return $sql;
22040
}
2205
2206
/**
2207
 * Fetch an array of permission IDs granted to the given user ID.
2208
 *
2209
 * The implementation here provides only the universal "all" grant. A node
2210
 * access module should implement hook_node_grants() to provide a grant
2211
 * list for the user.
2212
 *
2213
 * @param $op
2214
 *   The operation that the user is trying to perform.
2215
 * @param $account
2216
 *   The user object for the user performing the operation. If omitted, the
2217
 *   current user is used.
2218
 * @return
2219
 *   An associative array in which the keys are realms, and the values are
2220
 *   arrays of grants for those realms.
2221
 */
22222366
function node_access_grants($op, $account = NULL) {
2223
2224744
  if (!isset($account)) {
2225568
    $account = $GLOBALS['user'];
2226568
  }
2227
2228744
  return array_merge(array('all' => array(0)),
module_invoke_all('node_grants', $account, $op));
22290
}
2230
2231
/**
2232
 * Determine whether the user has a global viewing grant for all nodes.
2233
 */
22342366
function node_access_view_all_nodes() {
2235568
  static $access;
2236
2237568
  if (!isset($access)) {
2238568
    $grants = array();
2239568
    foreach (node_access_grants('view') as $realm => $gids) {
2240568
      foreach ($gids as $gid) {
2241568
        $grants[] = "(gid = $gid AND realm = '$realm')";
2242568
      }
2243568
    }
2244
2245568
    $grants_sql = '';
2246568
    if (count($grants)) {
2247568
      $grants_sql = 'AND (' . implode(' OR ', $grants) . ')';
2248568
    }
2249
2250568
    $sql = "SELECT COUNT(*) FROM {node_access} WHERE nid = 0 $grants_sql
AND grant_view >= 1";
2251568
    $result = db_query($sql);
2252568
    $access = db_result($result);
2253568
  }
2254
2255568
  return $access;
22560
}
2257
2258
/**
2259
 * Implementation of hook_db_rewrite_sql().
2260
 */
22612366
function node_db_rewrite_sql($query, $primary_table, $primary_field) {
22622063
  if ($primary_field == 'nid' && !node_access_view_all_nodes()) {
22630
    $return['join'] = _node_access_join_sql($primary_table);
22640
    $return['where'] = _node_access_where_sql();
22650
    $return['distinct'] = 1;
22660
    return $return;
22670
  }
22682063
}
2269
2270
2271
/**
2272
 * Implementation of hook_query_alter().
2273
 * @todo This doesn't quite work yet.
2274
 */
22752366
function DISABLED_node_query_alter(Query $query) {
22760
  if ($query->hasTag('node_access')) {
22770
    if (! user_access('administer nodes')) {
22780
      $query->distinct();
22790
      $access_alias = $query->join('node_access', 'na', 'na.nid = n.nid');
22800
      dsm('hello');
22810
      _node_query_alter_where($query, 'view', $access_alias);
22820
    }
22830
  }
22840
}
2285
22862366
function _node_query_alter_where($query, $op = 'view', $node_access_alias =
'na', $account = NULL) {
22870
  $or = db_or();
22880
  foreach (node_access_grants($op, $account) as $realm => $gids) {
22890
    foreach ($gids as $gid) {
22900
      $or->condition("{$node_access_alias}.gid = :gid AND
{$node_access_alias}.realm = :realm", array(':gid' => $gid, ':realm' =>
$realm));
22910
    }
22920
  }
2293
22940
  if (count($or->conditions())) {
22950
    $query->condition($or);
22960
  }
2297
22980
  $query->condition("$node_access_alias.grant_$op", '>=', 1);
22990
}
2300
2301
/**
2302
 * This function will call module invoke to get a list of grants and then
2303
 * write them to the database. It is called at node save, and should be
2304
 * called by modules whenever something other than a node_save causes
2305
 * the permissions on a node to change.
2306
 *
2307
 * This function is the only function that should write to the node_access
2308
 * table.
2309
 *
2310
 * @param $node
2311
 *   The $node to acquire grants for.
2312
 */
23132366
function node_access_acquire_grants($node) {
231488
  $grants = module_invoke_all('node_access_records', $node);
231588
  if (empty($grants)) {
231688
    $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1,
'grant_update' => 0, 'grant_delete' => 0);
231788
  }
2318
  else {
2319
    // retain grants by highest priority
23200
    $grant_by_priority = array();
23210
    foreach ($grants as $g) {
23220
      $grant_by_priority[intval($g['priority'])][] = $g;
23230
    }
23240
    krsort($grant_by_priority);
23250
    $grants = array_shift($grant_by_priority);
2326
  }
2327
232888
  node_access_write_grants($node, $grants);
232988
}
2330
2331
/**
2332
 * This function will write a list of grants to the database, deleting
2333
 * any pre-existing grants. If a realm is provided, it will only
2334
 * delete grants from that realm, but it will always delete a grant
2335
 * from the 'all' realm. Modules which utilize node_access can
2336
 * use this function when doing mass updates due to widespread permission
2337
 * changes.
2338
 *
2339
 * @param $node
2340
 *   The $node being written to. All that is necessary is that it contain a
nid.
2341
 * @param $grants
2342
 *   A list of grants to write. Each grant is an array that must contain
the
2343
 *   following keys: realm, gid, grant_view, grant_update, grant_delete.
2344
 *   The realm is specified by a particular module; the gid is as well, and
2345
 *   is a module-defined id to define grant privileges. each grant_* field
2346
 *   is a boolean value.
2347
 * @param $realm
2348
 *   If provided, only read/write grants for that realm.
2349
 * @param $delete
2350
 *   If false, do not delete records. This is only for optimization
purposes,
2351
 *   and assumes the caller has already performed a mass delete of some
form.
2352
 */
23532366
function node_access_write_grants($node, $grants, $realm = NULL, $delete =
TRUE) {
235488
  if ($delete) {
235588
    $query = db_delete('node_access')->condition('nid', $node->nid);
235688
    if ($realm) {
23570
      $query->condition('realm', array($realm, 'all'), 'IN');
23580
    }
235988
    $query->execute();
236088
  }
2361
2362
  // Only perform work when node_access modules are active.
236388
  if (count(module_implements('node_grants'))) {
23640
    foreach ($grants as $grant) {
23650
      if ($realm && $realm != $grant['realm']) {
23660
        continue;
23670
      }
2368
      // Only write grants; denies are implicit.
23690
      if ($grant['grant_view'] || $grant['grant_update'] ||
$grant['grant_delete']) {
23700
        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']);
23710
      }
23720
    }
23730
  }
237488
}
2375
2376
/**
2377
 * Flag / unflag the node access grants for rebuilding, or read the current
2378
 * value of the flag.
2379
 *
2380
 * When the flag is set, a message is displayed to users with 'access
2381
 * administration pages' permission, pointing to the 'rebuild' confirm
form.
2382
 * This can be used as an alternative to direct node_access_rebuild calls,
2383
 * allowing administrators to decide when they want to perform the actual
2384
 * (possibly time consuming) rebuild.
2385
 * When unsure the current user is an adminisrator, node_access_rebuild
2386
 * should be used instead.
2387
 *
2388
 * @param $rebuild
2389
 *   (Optional) The boolean value to be written.
2390
  * @return
2391
 *   (If no value was provided for $rebuild) The current value of the flag.
2392
 */
23932366
function node_access_needs_rebuild($rebuild = NULL) {
2394290
  if (!isset($rebuild)) {
2395290
    return variable_get('node_access_needs_rebuild', FALSE);
23960
  }
23970
  elseif ($rebuild) {
23980
    variable_set('node_access_needs_rebuild', TRUE);
23990
  }
2400
  else {
24010
    variable_del('node_access_needs_rebuild');
2402
  }
24030
}
2404
2405
/**
2406
 * Rebuild the node access database. This is occasionally needed by modules
2407
 * that make system-wide changes to access levels.
2408
 *
2409
 * When the rebuild is required by an admin-triggered action (e.g module
2410
 * settings form), calling node_access_needs_rebuild(TRUE) instead of
2411
 * node_access_rebuild() lets the user perform his changes and actually
2412
 * rebuild only once he is done.
2413
 *
2414
 * Note : As of Drupal 6, node access modules are not required to (and
actually
2415
 * should not) call node_access_rebuild() in hook_enable/disable anymore.
2416
 *
2417
 * @see node_access_needs_rebuild()
2418
 *
2419
 * @param $batch_mode
2420
 *   Set to TRUE to process in 'batch' mode, spawning processing over
several
2421
 *   HTTP requests (thus avoiding the risk of PHP timeout if the site has a
2422
 *   large number of nodes).
2423
 *   hook_update_N and any form submit handler are safe contexts to use the
2424
 *   'batch mode'. Less decidable cases (such as calls from hook_user,
2425
 *   hook_taxonomy, hook_node_type...) might consider using the non-batch
mode.
2426
 */
24272366
function node_access_rebuild($batch_mode = FALSE) {
24280
  db_query("DELETE FROM {node_access}");
2429
  // Only recalculate if the site is using a node_access module.
24300
  if (count(module_implements('node_grants'))) {
24310
    if ($batch_mode) {
2432
      $batch = array(
24330
        'title' => t('Rebuilding content access permissions'),
2434
        'operations' => array(
24350
          array('_node_access_rebuild_batch_operation', array()),
24360
        ),
2437
        'finished' => '_node_access_rebuild_batch_finished'
24380
      );
24390
      batch_set($batch);
24400
    }
2441
    else {
2442
      // If not in 'safe mode', increase the maximum execution time.
24430
      if (!ini_get('safe_mode')) {
24440
        set_time_limit(240);
24450
      }
24460
      $result = db_query("SELECT nid FROM {node}");
24470
      while ($node = db_fetch_object($result)) {
24480
        $loaded_node = node_load($node->nid, NULL, TRUE);
2449
        // To preserve database integrity, only aquire grants if the node
2450
        // loads successfully.
24510
        if (!empty($loaded_node)) {
24520
          node_access_acquire_grants($loaded_node);
24530
        }
24540
      }
2455
    }
24560
  }
2457
  else {
2458
    // Not using any node_access modules. Add the default grant.
24590
    db_query("INSERT INTO {node_access} VALUES (0, 0, 'all', 1, 0, 0)");
2460
  }
2461
24620
  if (!isset($batch)) {
24630
    drupal_set_message(t('Content permissions have been rebuilt.'));
24640
    node_access_needs_rebuild(FALSE);
24650
    cache_clear_all();
24660
  }
24670
}
2468
2469
/**
2470
 * Batch operation for node_access_rebuild_batch.
2471
 *
2472
 * This is a mutlistep operation : we go through all nodes by packs of 20.
2473
 * The batch processing engine interrupts processing and sends progress
2474
 * feedback after 1 second execution time.
2475
 */
24762366
function _node_access_rebuild_batch_operation(&$context) {
24770
  if (empty($context['sandbox'])) {
2478
    // Initiate multistep processing.
24790
    $context['sandbox']['progress'] = 0;
24800
    $context['sandbox']['current_node'] = 0;
24810
    $context['sandbox']['max'] = db_result(db_query('SELECT COUNT(DISTINCT
nid) FROM {node}'));
24820
  }
2483
2484
  // Process the next 20 nodes.
24850
  $limit = 20;
24860
  $result = db_query_range("SELECT nid FROM {node} WHERE nid > %d ORDER BY
nid ASC", $context['sandbox']['current_node'], 0, $limit);
24870
  while ($row = db_fetch_array($result)) {
24880
    $loaded_node = node_load($row['nid'], NULL, TRUE);
2489
    // To preserve database integrity, only aquire grants if the node
2490
    // loads successfully.
24910
    if (!empty($loaded_node)) {
24920
      node_access_acquire_grants($loaded_node);
24930
    }
24940
    $context['sandbox']['progress']++;
24950
    $context['sandbox']['current_node'] = $loaded_node->nid;
24960
  }
2497
2498
  // Multistep processing : report progress.
24990
  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
25000
    $context['finished'] = $context['sandbox']['progress'] /
$context['sandbox']['max'];
25010
  }
25020
}
2503
2504
/**
2505
 * Post-processing for node_access_rebuild_batch.
2506
 */
25072366
function _node_access_rebuild_batch_finished($success, $results,
$operations) {
25080
  if ($success) {
25090
    drupal_set_message(t('The content access permissions have been
rebuilt.'));
25100
    node_access_needs_rebuild(FALSE);
25110
  }
2512
  else {
25130
    drupal_set_message(t('The content access permissions have not been
properly rebuilt.'), 'error');
2514
  }
25150
  cache_clear_all();
25160
}
2517
2518
/**
2519
 * @} End of "defgroup node_access".
2520
 */
2521
2522
2523
/**
2524
 * @defgroup node_content Hook implementations for user-created content
types.
2525
 * @{
2526
 */
2527
2528
/**
2529
 * Implementation of hook_access().
2530
 *
2531
 * Named so as not to conflict with node_access()
2532
 */
25332366
function node_content_access($op, $node, $account) {
25341703
  $type = is_string($node) ? $node : (is_array($node) ? $node['type'] :
$node->type);
2535
25361703
  if ($op == 'create') {
25371657
    return user_access('create ' . $type . ' content', $account);
25380
  }
2539
2540258
  if ($op == 'update') {
2541164
    if (user_access('edit any ' . $type . ' content', $account) ||
(user_access('edit own ' . $type . ' content', $account) && ($account->uid
== $node->uid))) {
254289
      return TRUE;
25430
    }
254475
  }
2545
2546237
  if ($op == 'delete') {
254733
    if (user_access('delete any ' . $type . ' content', $account) ||
(user_access('delete own ' . $type . ' content', $account) &&
($account->uid == $node->uid))) {
254814
      return TRUE;
25490
    }
255019
  }
2551229
}
2552
2553
/**
2554
 * Implementation of hook_form().
2555
 */
25562366
function node_content_form($node, $form_state) {
255757
  $type = node_get_types('type', $node);
255857
  $form = array();
2559
256057
  if ($type->has_title) {
256157
    $form['title'] = array(
256257
      '#type' => 'textfield',
256357
      '#title' => check_plain($type->title_label),
256457
      '#required' => TRUE,
256557
      '#default_value' => $node->title,
256657
      '#maxlength' => 255,
256757
      '#weight' => -5,
2568
    );
256957
  }
2570
257157
  if ($type->has_body) {
257257
    $form['body_field'] = node_body_field($node, $type->body_label,
$type->min_word_count);
257357
  }
2574
257557
  return $form;
25760
}
2577
2578
/**
2579
 * @} End of "defgroup node_content".
2580
 */
2581
2582
/**
2583
 * Implementation of hook_forms(). All node forms share the same form
handler
2584
 */
25852366
function node_forms() {
2586150
  $forms = array();
2587150
  if ($types = node_get_types()) {
2588150
    foreach (array_keys($types) as $type) {
2589150
      $forms[$type . '_node_form']['callback'] = 'node_form';
2590150
    }
2591150
  }
2592150
  return $forms;
25930
}
2594
2595
/**
2596
 * Format the "Submitted by username on date/time" for each node
2597
 *
2598
 * @ingroup themeable
2599
 */
26002366
function theme_node_submitted($node) {
26010
  return t('Submitted by !username on @datetime',
2602
    array(
26030
      '!username' => theme('username', $node),
26040
      '@datetime' => format_date($node->created),
26050
    ));
26060
}
2607
2608
/**
2609
 * Implementation of hook_hook_info().
2610
 */
26112366
function node_hook_info() {
2612
  return array(
2613
    'node' => array(
2614
      'nodeapi' => array(
2615
        'presave' => array(
261655
          'runs when' => t('When either saving a new post or updating an
existing post'),
261755
        ),
2618
        'insert' => array(
261955
          'runs when' => t('After saving a new post'),
262055
        ),
2621
        'update' => array(
262255
          'runs when' => t('After saving an updated post'),
262355
        ),
2624
        'delete' => array(
262555
          'runs when' => t('After deleting a post')
262655
        ),
2627
        'view' => array(
262855
          'runs when' => t('When content is viewed by an authenticated
user')
262955
        ),
263055
      ),
263155
    ),
263255
  );
26330
}
2634
2635
/**
2636
 * Implementation of hook_action_info().
2637
 */
26382366
function node_action_info() {
2639
  return array(
2640
    'node_publish_action' => array(
2641215
      'type' => 'node',
2642215
      'description' => t('Publish post'),
2643215
      'configurable' => FALSE,
2644215
      'behavior' => array('changes_node_property'),
2645
      'hooks' => array(
2646215
        'nodeapi' => array('presave'),
2647215
        'comment' => array('insert', 'update'),
2648215
      ),
2649215
    ),
2650
    'node_unpublish_action' => array(
2651215
      'type' => 'node',
2652215
      'description' => t('Unpublish post'),
2653215
      'configurable' => FALSE,
2654215
      'behavior' => array('changes_node_property'),
2655
      'hooks' => array(
2656215
        'nodeapi' => array('presave'),
2657215
        'comment' => array('delete', 'insert', 'update'),
2658215
      ),
2659215
    ),
2660
    'node_make_sticky_action' => array(
2661215
      'type' => 'node',
2662215
      'description' => t('Make post sticky'),
2663215
      'configurable' => FALSE,
2664215
      'behavior' => array('changes_node_property'),
2665
      'hooks' => array(
2666215
        'nodeapi' => array('presave'),
2667215
        'comment' => array('insert', 'update'),
2668215
      ),
2669215
    ),
2670
    'node_make_unsticky_action' => array(
2671215
      'type' => 'node',
2672215
      'description' => t('Make post unsticky'),
2673215
      'configurable' => FALSE,
2674215
      'behavior' => array('changes_node_property'),
2675
      'hooks' => array(
2676215
        'nodeapi' => array('presave'),
2677215
        'comment' => array('delete', 'insert', 'update'),
2678215
      ),
2679215
    ),
2680
    'node_promote_action' => array(
2681215
      'type' => 'node',
2682215
      'description' => t('Promote post to front page'),
2683215
      'configurable' => FALSE,
2684215
      'behavior' => array('changes_node_property'),
2685
      'hooks' => array(
2686215
        'nodeapi' => array('presave'),
2687215
        'comment' => array('insert', 'update'),
2688215
      ),
2689215
    ),
2690
    'node_unpromote_action' => array(
2691215
      'type' => 'node',
2692215
      'description' => t('Remove post from front page'),
2693215
      'configurable' => FALSE,
2694215
      'behavior' => array('changes_node_property'),
2695
      'hooks' => array(
2696215
        'nodeapi' => array('presave'),
2697215
        'comment' => array('delete', 'insert', 'update'),
2698215
      ),
2699215
    ),
2700
    'node_assign_owner_action' => array(
2701215
      'type' => 'node',
2702215
      'description' => t('Change the author of a post'),
2703215
      'configurable' => TRUE,
2704215
      'behavior' => array('changes_node_property'),
2705
      'hooks' => array(
2706215
        'any' => TRUE,
2707215
        'nodeapi' => array('presave'),
2708215
        'comment' => array('delete', 'insert', 'update'),
2709215
      ),
2710215
    ),
2711
    'node_save_action' => array(
2712215
      'type' => 'node',
2713215
      'description' => t('Save post'),
2714215
      'configurable' => FALSE,
2715
      'hooks' => array(
2716215
        'comment' => array('delete', 'insert', 'update'),
2717215
      ),
2718215
    ),
2719
    'node_unpublish_by_keyword_action' => array(
2720215
      'type' => 'node',
2721215
      'description' => t('Unpublish post containing keyword(s)'),
2722215
      'configurable' => TRUE,
2723
      'hooks' => array(
2724215
        'nodeapi' => array('presave', 'insert', 'update'),
2725215
      ),
2726215
    ),
2727215
  );
27280
}
2729
2730
/**
2731
 * Implementation of a Drupal action.
2732
 * Sets the status of a node to 1, meaning published.
2733
 */
27342366
function node_publish_action(&$node, $context = array()) {
27351
  $node->status = 1;
27361
  watchdog('action', 'Set @type %title to published.', array('@type' =>
node_get_types('name', $node), '%title' => $node->title));
27371
}
2738
2739
/**
2740
 * Implementation of a Drupal action.
2741
 * Sets the status of a node to 0, meaning unpublished.
2742
 */
27432366
function node_unpublish_action(&$node, $context = array()) {
27441
  $node->status = 0;
27451
  watchdog('action', 'Set @type %title to unpublished.', array('@type' =>
node_get_types('name', $node), '%title' => $node->title));
27461
}
2747
2748
/**
2749
 * Implementation of a Drupal action.
2750
 * Sets the sticky-at-top-of-list property of a node to 1.
2751
 */
27522366
function node_make_sticky_action(&$node, $context = array()) {
27531
  $node->sticky = 1;
27541
  watchdog('action', 'Set @type %title to sticky.', array('@type' =>
node_get_types('name', $node), '%title' => $node->title));
27551
}
2756
2757
/**
2758
 * Implementation of a Drupal action.
2759
 * Sets the sticky-at-top-of-list property of a node to 0.
2760
 */
27612366
function node_make_unsticky_action(&$node, $context = array()) {
27621
  $node->sticky = 0;
27631
  watchdog('action', 'Set @type %title to unsticky.', array('@type' =>
node_get_types('name', $node), '%title' => $node->title));
27641
}
2765
2766
/**
2767
 * Implementation of a Drupal action.
2768
 * Sets the promote property of a node to 1.
2769
 */
27702366
function node_promote_action(&$node, $context = array()) {
27711
  $node->promote = 1;
27721
  watchdog('action', 'Promoted @type %title to front page.', array('@type'
=> node_get_types('name', $node), '%title' => $node->title));
27731
}
2774
2775
/**
2776
 * Implementation of a Drupal action.
2777
 * Sets the promote property of a node to 0.
2778
 */
27792366
function node_unpromote_action(&$node, $context = array()) {
27801
  $node->promote = 0;
27811
  watchdog('action', 'Removed @type %title from front page.', array('@type'
=> node_get_types('name', $node), '%title' => $node->title));
27821
}
2783
2784
/**
2785
 * Implementation of a Drupal action.
2786
 * Saves a node.
2787
 */
27882366
function node_save_action($node) {
27890
  node_save($node);
27900
  watchdog('action', 'Saved @type %title', array('@type' =>
node_get_types('name', $node), '%title' => $node->title));
27910
}
2792
2793
/**
2794
 * Implementation of a configurable Drupal action.
2795
 * Assigns ownership of a node to a user.
2796
 */
27972366
function node_assign_owner_action(&$node, $context) {
27980
  $node->uid = $context['owner_uid'];
27990
  $owner_name = db_result(db_query("SELECT name FROM {users} WHERE uid =
%d", $context['owner_uid']));
28000
  watchdog('action', 'Changed owner of @type %title to uid %name.',
array('@type' => node_get_types('type', $node), '%title' => $node->title,
'%name' => $owner_name));
28010
}
2802
28032366
function node_assign_owner_action_form($context) {
28040
  $description = t('The username of the user to which you would like to
assign ownership.');
28050
  $count = db_result(db_query("SELECT COUNT(*) FROM {users}"));
28060
  $owner_name = '';
28070
  if (isset($context['owner_uid'])) {
28080
    $owner_name = db_result(db_query("SELECT name FROM {users} WHERE uid =
%d", $context['owner_uid']));
28090
  }
2810
2811
  // Use dropdown for fewer than 200 users; textbox for more than that.
28120
  if (intval($count) < 200) {
28130
    $options = array();
28140
    $result = db_query("SELECT uid, name FROM {users} WHERE uid > 0 ORDER
BY name");
28150
    while ($data = db_fetch_object($result)) {
28160
      $options[$data->name] = $data->name;
28170
    }
28180
    $form['owner_name'] = array(
28190
      '#type' => 'select',
28200
      '#title' => t('Username'),
28210
      '#default_value' => $owner_name,
28220
      '#options' => $options,
28230
      '#description' => $description,
2824
    );
28250
  }
2826
  else {
28270
    $form['owner_name'] = array(
28280
      '#type' => 'textfield',
28290
      '#title' => t('Username'),
28300
      '#default_value' => $owner_name,
28310
      '#autocomplete_path' => 'user/autocomplete',
28320
      '#size' => '6',
28330
      '#maxlength' => '7',
28340
      '#description' => $description,
2835
    );
2836
  }
28370
  return $form;
28380
}
2839
28402366
function node_assign_owner_action_validate($form, $form_state) {
28410
  $count = db_result(db_query("SELECT COUNT(*) FROM {users} WHERE name =
'%s'", $form_state['values']['owner_name']));
28420
  if (intval($count) != 1) {
28430
    form_set_error('owner_name', t('Please enter a valid username.'));
28440
  }
28450
}
2846
28472366
function node_assign_owner_action_submit($form, $form_state) {
2848
  // Username can change, so we need to store the ID, not the username.
28490
  $uid = db_result(db_query("SELECT uid from {users} WHERE name = '%s'",
$form_state['values']['owner_name']));
28500
  return array('owner_uid' => $uid);
28510
}
2852
28532366
function node_unpublish_by_keyword_action_form($context) {
28540
  $form['keywords'] = array(
28550
    '#title' => t('Keywords'),
28560
    '#type' => 'textarea',
28570
    '#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.'),
28580
    '#default_value' => isset($context['keywords']) ?
drupal_implode_tags($context['keywords']) : '',
2859
  );
28600
  return $form;
28610
}
2862
28632366
function node_unpublish_by_keyword_action_submit($form, $form_state) {
28640
  return array('keywords' =>
drupal_explode_tags($form_state['values']['keywords']));
28650
}
2866
2867
/**
2868
 * Implementation of a configurable Drupal action.
2869
 * Unpublish a node if it contains a certain string.
2870
 *
2871
 * @param $context
2872
 *   An array providing more information about the context of the call to
this action.
2873
 * @param $comment
2874
 *   A node object.
2875
 */
28762366
function node_unpublish_by_keyword_action($node, $context) {
28770
  foreach ($context['keywords'] as $keyword) {
28780
    if (strstr(node_view(clone $node), $keyword) || strstr($node->title,
$keyword)) {
28790
      $node->status = 0;
28800
      watchdog('action', 'Set @type %title to unpublished.', array('@type'
=> node_get_types('name', $node), '%title' => $node->title));
28810
      break;
28820
    }
28830
  }
28840
}
2885
2886
/**
2887
 * Helper function to generate standard node permission list for a given
type.
2888
 *
2889
 * @param $type
2890
 *   The machine-readable name of the node type.
2891
 * @return array
2892
 *   An array of permission names and descriptions.
2893
 */
28942366
function node_list_permissions($type) {
2895172
  $info = node_get_types('type', $type);
2896172
  $type = check_plain($info->type);
2897
2898
  // Build standard list of node permissions for this type.
2899
  $perms = array(
2900172
    "create $type content" => array(
2901172
      'title' => t('Create %type_name content', array('%type_name' =>
$info->name)),
2902172
      'description' => t('Create new %type_name content.',
array('%type_name' => $info->name)),
2903172
    ),
2904172
    "edit own $type content" => array(
2905172
      'title' => t('Edit own %type_name content', array('%type_name' =>
$info->name)),
2906172
      'description' => t('Edit %type_name content created by the user.',
array('%type_name' => $info->name)),
2907172
    ),
2908172
    "edit any $type content" => array(
2909172
      'title' => t('Edit any %type_name content', array('%type_name' =>
$info->name)),
2910172
      'description' => t('Edit any %type_name content, regardless of its
author.', array('%type_name' => $info->name)),
2911172
    ),
2912172
    "delete own $type content" => array(
2913172
      'title' => t('Delete own %type_name content', array('%type_name' =>
$info->name)),
2914172
      'description' => t('Delete %type_name content created by the user.',
array('%type_name' => $info->name)),
2915172
    ),
2916172
    "delete any $type content" => array(
2917172
      'title' => t('Delete any %type_name content', array('%type_name' =>
$info->name)),
2918172
      'description' => t('Delete any %type_name content, regardless of its
author.', array('%type_name' => $info->name)),
2919172
    ),
2920172
  );
2921
2922172
  return $perms;
29230
}
29242366