[ Index ]

PHP Cross Reference of Drupal 6 (gatewave)

title

Body

[close]

/modules/node/ -> node.module (source)

   1  <?php
   2  // $Id: node.module,v 1.947.2.29 2010/12/15 12:53:33 goba Exp $
   3  
   4  /**
   5   * @file
   6   * The core that allows content to be submitted to the site. Modules and scripts may
   7   * 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   */
  16  define('NODE_NEW_LIMIT', time() - 30 * 24 * 60 * 60);
  17  
  18  define('NODE_BUILD_NORMAL', 0);
  19  define('NODE_BUILD_PREVIEW', 1);
  20  define('NODE_BUILD_SEARCH_INDEX', 2);
  21  define('NODE_BUILD_SEARCH_RESULT', 3);
  22  define('NODE_BUILD_RSS', 4);
  23  define('NODE_BUILD_PRINT', 5);
  24  
  25  /**
  26   * Implementation of hook_help().
  27   */
  28  function node_help($path, $arg) {
  29    // Remind site administrators about the {node_access} table being flagged
  30    // for rebuild. We don't need to issue the message on the confirm form, or
  31    // while the rebuild is being processed.
  32    if ($path != 'admin/content/node-settings/rebuild' && $path != 'batch' && strpos($path, '#') === FALSE
  33        && user_access('access administration pages') && node_access_needs_rebuild()) {
  34      if ($path == 'admin/content/node-settings') {
  35        $message = t('The content access permissions need to be rebuilt.');
  36      }
  37      else {
  38        $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')));
  39      }
  40      drupal_set_message($message, 'error');
  41    }
  42  
  43    switch ($path) {
  44      case 'admin/help#node':
  45        $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>';
  46        $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/content/types'))) .'</p>';
  47        $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>';
  48        $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>';
  49        return $output;
  50      case 'admin/content/node':
  51        return ' '; // Return a non-null value so that the 'more help' link is shown.
  52      case 'admin/content/types':
  53        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>';
  54      case 'admin/content/types/add':
  55        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>';
  56      case 'node/%/revisions':
  57        return '<p>'. t('The revisions let you track differences between multiple versions of a post.') .'</p>';
  58      case 'node/%/edit':
  59        $node = node_load($arg[1]);
  60        $type = node_get_types('type', $node->type);
  61        return (!empty($type->help) ? '<p>'. filter_xss_admin($type->help) .'</p>' : '');
  62    }
  63  
  64    if ($arg[0] == 'node' && $arg[1] == 'add' && $arg[2]) {
  65      $type = node_get_types('type', str_replace('-', '_', $arg[2]));
  66      return (!empty($type->help) ? '<p>'. filter_xss_admin($type->help) .'</p>' : '');
  67    }
  68  }
  69  
  70  /**
  71   * Implementation of hook_theme()
  72   */
  73  function node_theme() {
  74    return array(
  75      'node' => array(
  76        'arguments' => array('node' => NULL, 'teaser' => FALSE, 'page' => FALSE),
  77        'template' => 'node',
  78      ),
  79      'node_list' => array(
  80        'arguments' => array('items' => NULL, 'title' => NULL),
  81      ),
  82      'node_search_admin' => array(
  83        'arguments' => array('form' => NULL),
  84      ),
  85      'node_filter_form' => array(
  86        'arguments' => array('form' => NULL),
  87        'file' => 'node.admin.inc',
  88      ),
  89      'node_filters' => array(
  90        'arguments' => array('form' => NULL),
  91        'file' => 'node.admin.inc',
  92      ),
  93      'node_admin_nodes' => array(
  94        'arguments' => array('form' => NULL),
  95        'file' => 'node.admin.inc',
  96      ),
  97      'node_add_list' => array(
  98        'arguments' => array('content' => NULL),
  99        'file' => 'node.pages.inc',
 100      ),
 101      'node_form' => array(
 102        'arguments' => array('form' => NULL),
 103        'file' => 'node.pages.inc',
 104      ),
 105      'node_preview' => array(
 106        'arguments' => array('node' => NULL),
 107        'file' => 'node.pages.inc',
 108      ),
 109      'node_log_message' => array(
 110        'arguments' => array('log' => NULL),
 111      ),
 112      'node_submitted' => array(
 113        'arguments' => array('node' => NULL),
 114      ),
 115    );
 116  }
 117  
 118  /**
 119   * Implementation of hook_cron().
 120   */
 121  function node_cron() {
 122    db_query('DELETE FROM {history} WHERE timestamp < %d', NODE_NEW_LIMIT);
 123  }
 124  
 125  /**
 126   * Gather a listing of links to nodes.
 127   *
 128   * @param $result
 129   *   A DB result object from a query to fetch node objects. If your query
 130   *   joins the <code>node_comment_statistics</code> table so that the
 131   *   <code>comment_count</code> field is available, a title attribute will
 132   *   be added to show the number of comments.
 133   * @param $title
 134   *   A heading for the resulting list.
 135   *
 136   * @return
 137   *   An HTML list suitable as content for a block, or FALSE if no result can
 138   *   fetch from DB result object.
 139   */
 140  function node_title_list($result, $title = NULL) {
 141    $items = array();
 142    $num_rows = FALSE;
 143    while ($node = db_fetch_object($result)) {
 144      $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());
 145      $num_rows = TRUE;
 146    }
 147  
 148    return $num_rows ? theme('node_list', $items, $title) : FALSE;
 149  }
 150  
 151  /**
 152   * Format a listing of links to nodes.
 153   *
 154   * @ingroup themeable
 155   */
 156  function theme_node_list($items, $title = NULL) {
 157    return theme('item_list', $items, $title);
 158  }
 159  
 160  /**
 161   * Update the 'last viewed' timestamp of the specified node for current user.
 162   */
 163  function node_tag_new($nid) {
 164    global $user;
 165  
 166    if ($user->uid) {
 167      if (node_last_viewed($nid)) {
 168        db_query('UPDATE {history} SET timestamp = %d WHERE uid = %d AND nid = %d', time(), $user->uid, $nid);
 169      }
 170      else {
 171        @db_query('INSERT INTO {history} (uid, nid, timestamp) VALUES (%d, %d, %d)', $user->uid, $nid, time());
 172      }
 173    }
 174  }
 175  
 176  /**
 177   * Retrieves the timestamp at which the current user last viewed the
 178   * specified node.
 179   */
 180  function node_last_viewed($nid) {
 181    global $user;
 182    static $history;
 183  
 184    if (!isset($history[$nid])) {
 185      $history[$nid] = db_fetch_object(db_query("SELECT timestamp FROM {history} WHERE uid = %d AND nid = %d", $user->uid, $nid));
 186    }
 187  
 188    return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp : 0);
 189  }
 190  
 191  /**
 192   * Decide on the type of marker to be displayed for a given node.
 193   *
 194   * @param $nid
 195   *   Node ID whose history supplies the "last viewed" timestamp.
 196   * @param $timestamp
 197   *   Time which is compared against node's "last viewed" timestamp.
 198   * @return
 199   *   One of the MARK constants.
 200   */
 201  function node_mark($nid, $timestamp) {
 202    global $user;
 203    static $cache;
 204  
 205    if (!$user->uid) {
 206      return MARK_READ;
 207    }
 208    if (!isset($cache[$nid])) {
 209      $cache[$nid] = node_last_viewed($nid);
 210    }
 211    if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) {
 212      return MARK_NEW;
 213    }
 214    elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) {
 215      return MARK_UPDATED;
 216    }
 217    return MARK_READ;
 218  }
 219  
 220  /**
 221   * See if the user used JS to submit a teaser.
 222   */
 223  function node_teaser_js(&$form, &$form_state) {
 224    if (isset($form['#post']['teaser_js'])) {
 225      // Glue the teaser to the body.
 226      if (trim($form_state['values']['teaser_js'])) {
 227        // Space the teaser from the body
 228        $body = trim($form_state['values']['teaser_js']) ."\r\n<!--break-->\r\n". trim($form_state['values']['body']);
 229      }
 230      else {
 231        // Empty teaser, no spaces.
 232        $body = '<!--break-->'. $form_state['values']['body'];
 233      }
 234      // Pass updated body value on to preview/submit form processing.
 235      form_set_value($form['body'], $body, $form_state);
 236      // Pass updated body value back onto form for those cases
 237      // in which the form is redisplayed.
 238      $form['body']['#value'] = $body;
 239    }
 240    return $form;
 241  }
 242  
 243  /**
 244   * Ensure value of "teaser_include" checkbox is consistent with other form data.
 245   *
 246   * This handles two situations in which an unchecked checkbox is rejected:
 247   *
 248   *   1. The user defines a teaser (summary) but it is empty;
 249   *   2. The user does not define a teaser (summary) (in this case an
 250   *      unchecked checkbox would cause the body to be empty, or missing
 251   *      the auto-generated teaser).
 252   *
 253   * If JavaScript is active then it is used to force the checkbox to be
 254   * checked when hidden, and so the second case will not arise.
 255   *
 256   * In either case a warning message is output.
 257   */
 258  function node_teaser_include_verify(&$form, &$form_state) {
 259    $message = '';
 260  
 261    // $form['#post'] is set only when the form is built for preview/submit.
 262    if (isset($form['#post']['body']) && isset($form_state['values']['teaser_include']) && !$form_state['values']['teaser_include']) {
 263      // "teaser_include" checkbox is present and unchecked.
 264      if (strpos($form_state['values']['body'], '<!--break-->') === 0) {
 265        // Teaser is empty string.
 266        $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.');
 267      }
 268      elseif (strpos($form_state['values']['body'], '<!--break-->') === FALSE) {
 269        // Teaser delimiter is not present in the body.
 270        $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.)');
 271      }
 272  
 273      if (!empty($message)) {
 274        drupal_set_message($message, 'warning');
 275        // Pass new checkbox value on to preview/submit form processing.
 276        form_set_value($form['teaser_include'], 1, $form_state);
 277        // Pass new checkbox value back onto form for those cases
 278        // in which form is redisplayed.
 279        $form['teaser_include']['#value'] = 1;
 280      }
 281    }
 282  
 283    return $form;
 284  }
 285  
 286  /**
 287   * Generate a teaser for a node body.
 288   *
 289   * If the end of the teaser is not indicated using the <!--break--> delimiter
 290   * then we generate the teaser automatically, trying to end it at a sensible
 291   * place such as the end of a paragraph, a line break, or the end of a
 292   * sentence (in that order of preference).
 293   *
 294   * @param $body
 295   *   The content for which a teaser will be generated.
 296   * @param $format
 297   *   The format of the content. If the content contains PHP code, we do not
 298   *   split it up to prevent parse errors. If the line break filter is present
 299   *   then we treat newlines embedded in $body as line breaks.
 300   * @param $size
 301   *   The desired character length of the teaser. If omitted, the default
 302   *   value will be used. Ignored if the special delimiter is present
 303   *   in $body.
 304   * @return
 305   *   The generated teaser.
 306   */
 307  function node_teaser($body, $format = NULL, $size = NULL) {
 308  
 309    if (!isset($size)) {
 310      $size = variable_get('teaser_length', 600);
 311    }
 312  
 313    // Find where the delimiter is in the body
 314    $delimiter = strpos($body, '<!--break-->');
 315  
 316    // If the size is zero, and there is no delimiter, the entire body is the teaser.
 317    if ($size == 0 && $delimiter === FALSE) {
 318      return $body;
 319    }
 320  
 321    // If a valid delimiter has been specified, use it to chop off the teaser.
 322    if ($delimiter !== FALSE) {
 323      return substr($body, 0, $delimiter);
 324    }
 325  
 326    // We check for the presence of the PHP evaluator filter in the current
 327    // format. If the body contains PHP code, we do not split it up to prevent
 328    // parse errors.
 329    if (isset($format)) {
 330      $filters = filter_list_format($format);
 331      if (isset($filters['php/0']) && strpos($body, '<?') !== FALSE) {
 332        return $body;
 333      }
 334    }
 335  
 336    // If we have a short body, the entire body is the teaser.
 337    if (drupal_strlen($body) <= $size) {
 338      return $body;
 339    }
 340  
 341    // If the delimiter has not been specified, try to split at paragraph or
 342    // sentence boundaries.
 343  
 344    // The teaser may not be longer than maximum length specified. Initial slice.
 345    $teaser = truncate_utf8($body, $size);
 346  
 347    // Store the actual length of the UTF8 string -- which might not be the same
 348    // as $size.
 349    $max_rpos = strlen($teaser);
 350  
 351    // How much to cut off the end of the teaser so that it doesn't end in the
 352    // middle of a paragraph, sentence, or word.
 353    // Initialize it to maximum in order to find the minimum.
 354    $min_rpos = $max_rpos;
 355  
 356    // Store the reverse of the teaser.  We use strpos on the reversed needle and
 357    // haystack for speed and convenience.
 358    $reversed = strrev($teaser);
 359  
 360    // Build an array of arrays of break points grouped by preference.
 361    $break_points = array();
 362  
 363    // A paragraph near the end of sliced teaser is most preferable.
 364    $break_points[] = array('</p>' => 0);
 365  
 366    // If no complete paragraph then treat line breaks as paragraphs.
 367    $line_breaks = array('<br />' => 6, '<br>' => 4);
 368    // Newline only indicates a line break if line break converter
 369    // filter is present.
 370    if (isset($filters['filter/1'])) {
 371      $line_breaks["\n"] = 1;
 372    }
 373    $break_points[] = $line_breaks;
 374  
 375    // If the first paragraph is too long, split at the end of a sentence.
 376    $break_points[] = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟ ' => 1);
 377  
 378    // Iterate over the groups of break points until a break point is found.
 379    foreach ($break_points as $points) {
 380      // Look for each break point, starting at the end of the teaser.
 381      foreach ($points as $point => $offset) {
 382        // The teaser is already reversed, but the break point isn't.
 383        $rpos = strpos($reversed, strrev($point));
 384        if ($rpos !== FALSE) {
 385          $min_rpos = min($rpos + $offset, $min_rpos);
 386        }
 387      }
 388  
 389      // If a break point was found in this group, slice and return the teaser.
 390      if ($min_rpos !== $max_rpos) {
 391        // Don't slice with length 0.  Length must be <0 to slice from RHS.
 392        return ($min_rpos === 0) ? $teaser : substr($teaser, 0, 0 - $min_rpos);
 393      }
 394    }
 395  
 396    // If a break point was not found, still return a teaser.
 397    return $teaser;
 398  }
 399  
 400  /**
 401   * Builds a list of available node types, and returns all of part of this list
 402   * in the specified format.
 403   *
 404   * @param $op
 405   *   The format in which to return the list. When this is set to 'type',
 406   *   'module', or 'name', only the specified node type is returned. When set to
 407   *   'types' or 'names', all node types are returned.
 408   * @param $node
 409   *   A node object, array, or string that indicates the node type to return.
 410   *   Leave at default value (NULL) to return a list of all node types.
 411   * @param $reset
 412   *   Whether or not to reset this function's internal cache (defaults to
 413   *   FALSE).
 414   *
 415   * @return
 416   *   Either an array of all available node types, or a single node type, in a
 417   *   variable format. Returns FALSE if the node type is not found.
 418   */
 419  function node_get_types($op = 'types', $node = NULL, $reset = FALSE) {
 420    static $_node_types, $_node_names;
 421  
 422    if ($reset || !isset($_node_types)) {
 423      list($_node_types, $_node_names) = _node_types_build();
 424    }
 425  
 426    if ($node) {
 427      if (is_array($node)) {
 428        $type = $node['type'];
 429      }
 430      elseif (is_object($node)) {
 431        $type = $node->type;
 432      }
 433      elseif (is_string($node)) {
 434        $type = $node;
 435      }
 436      if (!isset($_node_types[$type])) {
 437        return FALSE;
 438      }
 439    }
 440    switch ($op) {
 441      case 'types':
 442        return $_node_types;
 443      case 'type':
 444        return isset($_node_types[$type]) ? $_node_types[$type] : FALSE;
 445      case 'module':
 446        return isset($_node_types[$type]->module) ? $_node_types[$type]->module : FALSE;
 447      case 'names':
 448        return $_node_names;
 449      case 'name':
 450        return isset($_node_names[$type]) ? $_node_names[$type] : FALSE;
 451    }
 452  }
 453  
 454  /**
 455   * Resets the database cache of node types, and saves all new or non-modified
 456   * module-defined node types to the database.
 457   */
 458  function node_types_rebuild() {
 459    _node_types_build();
 460  
 461    $node_types = node_get_types('types', NULL, TRUE);
 462  
 463    foreach ($node_types as $type => $info) {
 464      if (!empty($info->is_new)) {
 465        node_type_save($info);
 466      }
 467      if (!empty($info->disabled)) {
 468        node_type_delete($info->type);
 469      }
 470    }
 471  
 472    _node_types_build();
 473  }
 474  
 475  /**
 476   * Saves a node type to the database.
 477   *
 478   * @param $info
 479   *   The node type to save, as an object.
 480   *
 481   * @return
 482   *   Status flag indicating outcome of the operation.
 483   */
 484  function node_type_save($info) {
 485    $is_existing = FALSE;
 486    $existing_type = !empty($info->old_type) ? $info->old_type : $info->type;
 487    $is_existing = db_result(db_query("SELECT COUNT(*) FROM {node_type} WHERE type = '%s'", $existing_type));
 488    if (!isset($info->help)) {
 489      $info->help = '';
 490    }
 491    if (!isset($info->min_word_count)) {
 492      $info->min_word_count = 0;
 493    }
 494    if (!isset($info->body_label)) {
 495      $info->body_label = '';
 496    }
 497  
 498    if ($is_existing) {
 499      db_query("UPDATE {node_type} SET type = '%s', name = '%s', module = '%s', has_title = %d, title_label = '%s', has_body = %d, body_label = '%s', description = '%s', help = '%s', min_word_count = %d, custom = %d, modified = %d, locked = %d WHERE type = '%s'", $info->type, $info->name, $info->module, $info->has_title, $info->title_label, $info->has_body, $info->body_label, $info->description, $info->help, $info->min_word_count, $info->custom, $info->modified, $info->locked, $existing_type);
 500  
 501      module_invoke_all('node_type', 'update', $info);
 502      return SAVED_UPDATED;
 503    }
 504    else {
 505      db_query("INSERT INTO {node_type} (type, name, module, has_title, title_label, has_body, body_label, description, help, min_word_count, custom, modified, locked, orig_type) VALUES ('%s', '%s', '%s', %d, '%s', %d, '%s', '%s', '%s', %d, %d, %d, %d, '%s')", $info->type, $info->name, $info->module, $info->has_title, $info->title_label, $info->has_body, $info->body_label, $info->description, $info->help, $info->min_word_count, $info->custom, $info->modified, $info->locked, $info->orig_type);
 506  
 507      module_invoke_all('node_type', 'insert', $info);
 508      return SAVED_NEW;
 509    }
 510  }
 511  
 512  /**
 513   * Deletes a node type from the database.
 514   *
 515   * @param $type
 516   *   The machine-readable name of the node type to be deleted.
 517   */
 518  function node_type_delete($type) {
 519    $info = node_get_types('type', $type);
 520    db_query("DELETE FROM {node_type} WHERE type = '%s'", $type);
 521    module_invoke_all('node_type', 'delete', $info);
 522  }
 523  
 524  /**
 525   * Updates all nodes of one type to be of another type.
 526   *
 527   * @param $old_type
 528   *   The current node type of the nodes.
 529   * @param $type
 530   *   The new node type of the nodes.
 531   *
 532   * @return
 533   *   The number of nodes whose node type field was modified.
 534   */
 535  function node_type_update_nodes($old_type, $type) {
 536    db_query("UPDATE {node} SET type = '%s' WHERE type = '%s'", $type, $old_type);
 537    return db_affected_rows();
 538  }
 539  
 540  /**
 541   * Builds and returns the list of available node types.
 542   *
 543   * The list of types is built by querying hook_node_info() in all modules, and
 544   * by comparing this information with the node types in the {node_type} table.
 545   *
 546   */
 547  function _node_types_build() {
 548    $_node_types = array();
 549    $_node_names = array();
 550  
 551    $info_array = module_invoke_all('node_info');
 552    foreach ($info_array as $type => $info) {
 553      $info['type'] = $type;
 554      $_node_types[$type] = (object) _node_type_set_defaults($info);
 555      $_node_names[$type] = $info['name'];
 556    }
 557  
 558    $type_result = db_query(db_rewrite_sql('SELECT nt.type, nt.* FROM {node_type} nt ORDER BY nt.type ASC', 'nt', 'type'));
 559    while ($type_object = db_fetch_object($type_result)) {
 560      // Check for node types from disabled modules and mark their types for removal.
 561      // Types defined by the node module in the database (rather than by a separate
 562      // module using hook_node_info) have a module value of 'node'.
 563      if ($type_object->module != 'node' && empty($info_array[$type_object->type])) {
 564        $type_object->disabled = TRUE;
 565      }
 566      if (!isset($_node_types[$type_object->type]) || $type_object->modified) {
 567        $_node_types[$type_object->type] = $type_object;
 568        $_node_names[$type_object->type] = $type_object->name;
 569  
 570        if ($type_object->type != $type_object->orig_type) {
 571          unset($_node_types[$type_object->orig_type]);
 572          unset($_node_names[$type_object->orig_type]);
 573        }
 574      }
 575    }
 576  
 577    asort($_node_names);
 578  
 579    return array($_node_types, $_node_names);
 580  }
 581  
 582  /**
 583   * Set default values for a node type defined through hook_node_info().
 584   */
 585  function _node_type_set_defaults($info) {
 586    if (!isset($info['has_title'])) {
 587      $info['has_title'] = TRUE;
 588    }
 589    if ($info['has_title'] && !isset($info['title_label'])) {
 590      $info['title_label'] = t('Title');
 591    }
 592  
 593    if (!isset($info['has_body'])) {
 594      $info['has_body'] = TRUE;
 595    }
 596    if ($info['has_body'] && !isset($info['body_label'])) {
 597      $info['body_label'] = t('Body');
 598    }
 599  
 600    if (!isset($info['help'])) {
 601      $info['help'] = '';
 602    }
 603    if (!isset($info['min_word_count'])) {
 604      $info['min_word_count'] = 0;
 605    }
 606    if (!isset($info['custom'])) {
 607      $info['custom'] = FALSE;
 608    }
 609    if (!isset($info['modified'])) {
 610      $info['modified'] = FALSE;
 611    }
 612    if (!isset($info['locked'])) {
 613      $info['locked'] = TRUE;
 614    }
 615  
 616    $info['orig_type'] = $info['type'];
 617    $info['is_new'] = TRUE;
 618  
 619    return $info;
 620  }
 621  
 622  /**
 623   * Determine whether a node hook exists.
 624   *
 625   * @param &$node
 626   *   Either a node object, node array, or a string containing the node type.
 627   * @param $hook
 628   *   A string containing the name of the hook.
 629   * @return
 630   *   TRUE iff the $hook exists in the node type of $node.
 631   */
 632  function node_hook(&$node, $hook) {
 633    $module = node_get_types('module', $node);
 634    if ($module == 'node') {
 635      $module = 'node_content'; // Avoid function name collisions.
 636    }
 637    return module_hook($module, $hook);
 638  }
 639  
 640  /**
 641   * Invoke a node hook.
 642   *
 643   * @param &$node
 644   *   Either a node object, node array, or a string containing the node type.
 645   * @param $hook
 646   *   A string containing the name of the hook.
 647   * @param $a2, $a3, $a4
 648   *   Arguments to pass on to the hook, after the $node argument.
 649   * @return
 650   *   The returned value of the invoked hook.
 651   */
 652  function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
 653    if (node_hook($node, $hook)) {
 654      $module = node_get_types('module', $node);
 655      if ($module == 'node') {
 656        $module = 'node_content'; // Avoid function name collisions.
 657      }
 658      $function = $module .'_'. $hook;
 659      return ($function($node, $a2, $a3, $a4));
 660    }
 661  }
 662  
 663  /**
 664   * Invoke a hook_nodeapi() operation in all modules.
 665   *
 666   * @param &$node
 667   *   A node object.
 668   * @param $op
 669   *   A string containing the name of the nodeapi operation.
 670   * @param $a3, $a4
 671   *   Arguments to pass on to the hook, after the $node and $op arguments.
 672   * @return
 673   *   The returned value of the invoked hooks.
 674   */
 675  function node_invoke_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
 676    $return = array();
 677    foreach (module_implements('nodeapi') as $name) {
 678      $function = $name .'_nodeapi';
 679      $result = $function($node, $op, $a3, $a4);
 680      if (isset($result) && is_array($result)) {
 681        $return = array_merge($return, $result);
 682      }
 683      else if (isset($result)) {
 684        $return[] = $result;
 685      }
 686    }
 687    return $return;
 688  }
 689  
 690  /**
 691   * Load a node object from the database.
 692   *
 693   * @param $param
 694   *   Either the nid of the node or an array of conditions to match against in the database query
 695   * @param $revision
 696   *   Which numbered revision to load. Defaults to the current version.
 697   * @param $reset
 698   *   Whether to reset the internal node_load cache.
 699   *
 700   * @return
 701   *   A fully-populated node object.
 702   */
 703  function node_load($param = array(), $revision = NULL, $reset = NULL) {
 704    static $nodes = array();
 705  
 706    if ($reset) {
 707      $nodes = array();
 708    }
 709  
 710    $cachable = ($revision == NULL);
 711    $arguments = array();
 712    if (is_numeric($param)) {
 713      if ($cachable) {
 714        // Is the node statically cached?
 715        if (isset($nodes[$param])) {
 716          return is_object($nodes[$param]) ? drupal_clone($nodes[$param]) : $nodes[$param];
 717        }
 718      }
 719      $cond = 'n.nid = %d';
 720      $arguments[] = $param;
 721    }
 722    elseif (is_array($param)) {
 723      // Turn the conditions into a query.
 724      foreach ($param as $key => $value) {
 725        $cond[] = 'n.'. db_escape_table($key) ." = '%s'";
 726        $arguments[] = $value;
 727      }
 728      $cond = implode(' AND ', $cond);
 729    }
 730    else {
 731      return FALSE;
 732    }
 733  
 734    // Retrieve a field list based on the site's schema.
 735    $fields = drupal_schema_fields_sql('node', 'n');
 736    $fields = array_merge($fields, drupal_schema_fields_sql('node_revisions', 'r'));
 737    $fields = array_merge($fields, array('u.name', 'u.picture', 'u.data'));
 738    // Remove fields not needed in the query: n.vid and r.nid are redundant,
 739    // n.title is unnecessary because the node title comes from the
 740    // node_revisions table.  We'll keep r.vid, r.title, and n.nid.
 741    $fields = array_diff($fields, array('n.vid', 'n.title', 'r.nid'));
 742    $fields = implode(', ', $fields);
 743    // Rename timestamp field for clarity.
 744    $fields = str_replace('r.timestamp', 'r.timestamp AS revision_timestamp', $fields);
 745    // Change name of revision uid so it doesn't conflict with n.uid.
 746    $fields = str_replace('r.uid', 'r.uid AS revision_uid', $fields);
 747  
 748    // Retrieve the node.
 749    // No db_rewrite_sql is applied so as to get complete indexing for search.
 750    if ($revision) {
 751      array_unshift($arguments, $revision);
 752      $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));
 753    }
 754    else {
 755      $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));
 756    }
 757  
 758    if ($node && $node->nid) {
 759      // Call the node specific callback (if any) and piggy-back the
 760      // results to the node or overwrite some values.
 761      if ($extra = node_invoke($node, 'load')) {
 762        foreach ($extra as $key => $value) {
 763          $node->$key = $value;
 764        }
 765      }
 766  
 767      if ($extra = node_invoke_nodeapi($node, 'load')) {
 768        foreach ($extra as $key => $value) {
 769          $node->$key = $value;
 770        }
 771      }
 772      if ($cachable) {
 773        $nodes[$node->nid] = is_object($node) ? drupal_clone($node) : $node;
 774      }
 775    }
 776  
 777    return $node;
 778  }
 779  
 780  /**
 781   * Perform validation checks on the given node.
 782   */
 783  function node_validate($node, $form = array()) {
 784    // Convert the node to an object, if necessary.
 785    $node = (object)$node;
 786    $type = node_get_types('type', $node);
 787  
 788    // Make sure the body has the minimum number of words.
 789    // TODO : use a better word counting algorithm that will work in other languages
 790    if (!empty($type->min_word_count) && isset($node->body) && count(explode(' ', $node->body)) < $type->min_word_count) {
 791      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)));
 792    }
 793  
 794    if (isset($node->nid) && (node_last_changed($node->nid) > $node->changed)) {
 795      form_set_error('changed', t('This content has been modified by another user, changes cannot be saved.'));
 796    }
 797  
 798    if (user_access('administer nodes')) {
 799      // Validate the "authored by" field.
 800      if (!empty($node->name) && !($account = user_load(array('name' => $node->name)))) {
 801        // The use of empty() is mandatory in the context of usernames
 802        // as the empty string denotes the anonymous user. In case we
 803        // are dealing with an anonymous user we set the user ID to 0.
 804        form_set_error('name', t('The username %name does not exist.', array('%name' => $node->name)));
 805      }
 806  
 807      // Validate the "authored on" field. As of PHP 5.1.0, strtotime returns FALSE instead of -1 upon failure.
 808      if (!empty($node->date) && strtotime($node->date) <= 0) {
 809        form_set_error('date', t('You have to specify a valid date.'));
 810      }
 811    }
 812  
 813    // Do node-type-specific validation checks.
 814    node_invoke($node, 'validate', $form);
 815    node_invoke_nodeapi($node, 'validate', $form);
 816  }
 817  
 818  /**
 819   * Prepares a node for saving by populating teaser, author, and creation date.
 820   *
 821   * @param object|array $node
 822   *   A node object or array.
 823   *
 824   * @return
 825   *   A validated node object with a populated teaser, author, and creation date.
 826   */
 827  function node_submit($node) {
 828    global $user;
 829  
 830    // Convert the node to an object, if necessary.
 831    $node = (object)$node;
 832  
 833    // Generate the teaser, but only if it hasn't been set (e.g. by a
 834    // module-provided 'teaser' form item).
 835    if (!isset($node->teaser)) {
 836      if (isset($node->body)) {
 837        $node->teaser = node_teaser($node->body, isset($node->format) ? $node->format : NULL);
 838        // Chop off the teaser from the body if needed. The teaser_include
 839        // property might not be set (eg. in Blog API postings), so only act on
 840        // it, if it was set with a given value.
 841        if (isset($node->teaser_include) && !$node->teaser_include && $node->teaser == substr($node->body, 0, strlen($node->teaser))) {
 842          $node->body = substr($node->body, strlen($node->teaser));
 843        }
 844      }
 845      else {
 846        $node->teaser = '';
 847        $node->format = 0;
 848      }
 849    }
 850  
 851    if (user_access('administer nodes')) {
 852      // Populate the "authored by" field.
 853      if ($account = user_load(array('name' => $node->name))) {
 854        $node->uid = $account->uid;
 855      }
 856      else {
 857        $node->uid = 0;
 858      }
 859    }
 860    $node->created = !empty($node->date) ? strtotime($node->date) : time();
 861    $node->validated = TRUE;
 862  
 863    return $node;
 864  }
 865  
 866  /**
 867   * Save a node object into the database.
 868   */
 869  function node_save(&$node) {
 870    // Let modules modify the node before it is saved to the database.
 871    node_invoke_nodeapi($node, 'presave');
 872    global $user;
 873  
 874    // Insert a new node.
 875    $node->is_new = empty($node->nid);
 876  
 877    if ($node->is_new || !empty($node->revision)) {
 878      // When inserting a node, $node->log must be set because
 879      // {node_revisions}.log does not (and cannot) have a default
 880      // value.  If the user does not have permission to create
 881      // revisions, however, the form will not contain an element for
 882      // log so $node->log will be unset at this point.
 883      if (!isset($node->log)) {
 884        $node->log = '';
 885      }
 886    }
 887    elseif (empty($node->log)) {
 888      // When updating a node, however, avoid clobbering an existing
 889      // log entry with an empty one.
 890      unset($node->log);
 891    }
 892  
 893    // For the same reasons, make sure we have $node->teaser and
 894    // $node->body set.
 895    if (!isset($node->teaser)) {
 896      $node->teaser = '';
 897    }
 898    if (!isset($node->body)) {
 899      $node->body = '';
 900    }
 901  
 902    // Save the old revision if needed.
 903    if (!$node->is_new && !empty($node->revision) && $node->vid) {
 904      $node->old_vid = $node->vid;
 905    }
 906  
 907    $time = time();
 908    if (empty($node->created)) {
 909      $node->created = $time;
 910    }
 911    // The changed timestamp is always updated for bookkeeping purposes (revisions, searching, ...)
 912    $node->changed = $time;
 913  
 914    $node->timestamp = $time;
 915    $node->format = isset($node->format) ? $node->format : FILTER_FORMAT_DEFAULT;
 916  
 917    // Generate the node table query and the node_revisions table query.
 918    if ($node->is_new) {
 919      _node_save_revision($node, $user->uid);
 920      drupal_write_record('node', $node);
 921      db_query('UPDATE {node_revisions} SET nid = %d WHERE vid = %d', $node->nid, $node->vid);
 922      $op = 'insert';
 923    }
 924    else {
 925      drupal_write_record('node', $node, 'nid');
 926      if (!empty($node->revision)) {
 927        _node_save_revision($node, $user->uid);
 928        db_query('UPDATE {node} SET vid = %d WHERE nid = %d', $node->vid, $node->nid);
 929      }
 930      else {
 931        _node_save_revision($node, $user->uid, 'vid');
 932      }
 933      $op = 'update';
 934    }
 935  
 936    // Call the node specific callback (if any).
 937    node_invoke($node, $op);
 938    node_invoke_nodeapi($node, $op);
 939  
 940    // Update the node access table for this node.
 941    node_access_acquire_grants($node);
 942  
 943    // Clear the page and block caches.
 944    cache_clear_all();
 945  }
 946  
 947  /**
 948   * Helper function to save a revision with the uid of the current user.
 949   *
 950   * Node is taken by reference, becuse drupal_write_record() updates the
 951   * $node with the revision id, and we need to pass that back to the caller.
 952   */
 953  function _node_save_revision(&$node, $uid, $update = NULL) {
 954    $temp_uid = $node->uid;
 955    $node->uid = $uid;
 956    if (isset($update)) {
 957      drupal_write_record('node_revisions', $node, $update);
 958    }
 959    else {
 960      drupal_write_record('node_revisions', $node);
 961    }
 962    $node->uid = $temp_uid;
 963  }
 964  
 965  /**
 966   * Delete a node.
 967   */
 968  function node_delete($nid) {
 969  
 970    // Clear the cache before the load, so if multiple nodes are deleted, the
 971    // memory will not fill up with nodes (possibly) already removed.
 972    $node = node_load($nid, NULL, TRUE);
 973  
 974    if (node_access('delete', $node)) {
 975      db_query('DELETE FROM {node} WHERE nid = %d', $node->nid);
 976      db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);
 977  
 978      // Call the node-specific callback (if any):
 979      node_invoke($node, 'delete');
 980      node_invoke_nodeapi($node, 'delete');
 981  
 982      // Clear the page and block caches.
 983      cache_clear_all();
 984  
 985      // Remove this node from the search index if needed.
 986      if (function_exists('search_wipe')) {
 987        search_wipe($node->nid, 'node');
 988      }
 989      watchdog('content', '@type: deleted %title.', array('@type' => $node->type, '%title' => $node->title));
 990      drupal_set_message(t('@type %title has been deleted.', array('@type' => node_get_types('name', $node), '%title' => $node->title)));
 991    }
 992  }
 993  
 994  /**
 995   * Generate a display of the given node.
 996   *
 997   * @param $node
 998   *   A node array or node object.
 999   * @param $teaser
1000   *   Whether to display the teaser only or the full form.
1001   * @param $page
1002   *   Whether the node is being displayed by itself as a page.
1003   * @param $links
1004   *   Whether or not to display node links. Links are omitted for node previews.
1005   *
1006   * @return
1007   *   An HTML representation of the themed node.
1008   */
1009  function node_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) {
1010    $node = (object)$node;
1011  
1012    $node = node_build_content($node, $teaser, $page);
1013  
1014    if ($links) {
1015      $node->links = module_invoke_all('link', 'node', $node, $teaser);
1016      drupal_alter('link', $node->links, $node);
1017    }
1018  
1019    // Set the proper node part, then unset unused $node part so that a bad
1020    // theme can not open a security hole.
1021    $content = drupal_render($node->content);
1022    if ($teaser) {
1023      $node->teaser = $content;
1024      unset($node->body);
1025    }
1026    else {
1027      $node->body = $content;
1028      unset($node->teaser);
1029    }
1030  
1031    // Allow modules to modify the fully-built node.
1032    node_invoke_nodeapi($node, 'alter', $teaser, $page);
1033  
1034    return theme('node', $node, $teaser, $page);
1035  }
1036  
1037  /**
1038   * Apply filters and build the node's standard elements.
1039   */
1040  function node_prepare($node, $teaser = FALSE) {
1041    // First we'll overwrite the existing node teaser and body with
1042    // the filtered copies! Then, we'll stick those into the content
1043    // array and set the read more flag if appropriate.
1044    $node->readmore = $node->teaser != $node->body;
1045  
1046    if ($teaser == FALSE) {
1047      $node->body = check_markup($node->body, $node->format, FALSE);
1048    }
1049    else {
1050      $node->teaser = check_markup($node->teaser, $node->format, FALSE);
1051    }
1052  
1053    $node->content['body'] = array(
1054      '#value' => $teaser ? $node->teaser : $node->body,
1055      '#weight' => 0,
1056    );
1057  
1058    return $node;
1059  }
1060  
1061  /**
1062   * Builds a structured array representing the node's content.
1063   *
1064   * @param $node
1065   *   A node object.
1066   * @param $teaser
1067   *   Whether to display the teaser only, as on the main page.
1068   * @param $page
1069   *   Whether the node is being displayed by itself as a page.
1070   *
1071   * @return
1072   *   An structured array containing the individual elements
1073   *   of the node's body.
1074   */
1075  function node_build_content($node, $teaser = FALSE, $page = FALSE) {
1076  
1077    // The build mode identifies the target for which the node is built.
1078    if (!isset($node->build_mode)) {
1079      $node->build_mode = NODE_BUILD_NORMAL;
1080    }
1081  
1082    // Remove the delimiter (if any) that separates the teaser from the body.
1083    $node->body = isset($node->body) ? str_replace('<!--break-->', '', $node->body) : '';
1084  
1085    // The 'view' hook can be implemented to overwrite the default function
1086    // to display nodes.
1087    if (node_hook($node, 'view')) {
1088      $node = node_invoke($node, 'view', $teaser, $page);
1089    }
1090    else {
1091      $node = node_prepare($node, $teaser);
1092    }
1093  
1094    // Allow modules to make their own additions to the node.
1095    node_invoke_nodeapi($node, 'view', $teaser, $page);
1096  
1097    return $node;
1098  }
1099  
1100  /**
1101   * Generate a page displaying a single node, along with its comments.
1102   */
1103  function node_show($node, $cid, $message = FALSE) {
1104    if ($message) {
1105      drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp))));
1106    }
1107    $output = node_view($node, FALSE, TRUE);
1108  
1109    if (function_exists('comment_render') && $node->comment) {
1110      $output .= comment_render($node, $cid);
1111    }
1112  
1113    // Update the history table, stating that this user viewed this node.
1114    node_tag_new($node->nid);
1115  
1116    return $output;
1117  }
1118  
1119  /**
1120   * Theme a log message.
1121   *
1122   * @ingroup themeable
1123   */
1124  function theme_node_log_message($log) {
1125    return '<div class="log"><div class="title">'. t('Log') .':</div>'. $log .'</div>';
1126  }
1127  
1128  /**
1129   * Implementation of hook_perm().
1130   */
1131  function node_perm() {
1132    $perms = array('administer content types', 'administer nodes', 'access content', 'view revisions', 'revert revisions', 'delete revisions');
1133  
1134    foreach (node_get_types() as $type) {
1135      if ($type->module == 'node') {
1136        $name = check_plain($type->type);
1137        $perms[] = 'create '. $name .' content';
1138        $perms[] = 'delete own '. $name .' content';
1139        $perms[] = 'delete any '. $name .' content';
1140        $perms[] = 'edit own '. $name .' content';
1141        $perms[] = 'edit any '. $name .' content';
1142      }
1143    }
1144  
1145    return $perms;
1146  }
1147  
1148  /**
1149   * Implementation of hook_search().
1150   */
1151  function node_search($op = 'search', $keys = NULL) {
1152    switch ($op) {
1153      case 'name':
1154        return t('Content');
1155  
1156      case 'reset':
1157        db_query("UPDATE {search_dataset} SET reindex = %d WHERE type = 'node'", time());
1158        return;
1159  
1160      case 'status':
1161        $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1'));
1162        $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)"));
1163        return array('remaining' => $remaining, 'total' => $total);
1164  
1165      case 'admin':
1166        $form = array();
1167        // Output form for defining rank factor weights.
1168        $form['content_ranking'] = array(
1169          '#type' => 'fieldset',
1170          '#title' => t('Content ranking'),
1171        );
1172        $form['content_ranking']['#theme'] = 'node_search_admin';
1173        $form['content_ranking']['info'] = array(
1174          '#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>'
1175        );
1176  
1177        $ranking = array('node_rank_relevance' => t('Keyword relevance'),
1178                         'node_rank_recent' => t('Recently posted'));
1179        if (module_exists('comment')) {
1180          $ranking['node_rank_comments'] = t('Number of comments');
1181        }
1182        if (module_exists('statistics') && variable_get('statistics_count_content_views', 0)) {
1183          $ranking['node_rank_views'] = t('Number of views');
1184        }
1185  
1186        // Note: reversed to reflect that higher number = higher ranking.
1187        $options = drupal_map_assoc(range(0, 10));
1188        foreach ($ranking as $var => $title) {
1189          $form['content_ranking']['factors'][$var] = array(
1190            '#title' => $title,
1191            '#type' => 'select',
1192            '#options' => $options,
1193            '#default_value' => variable_get($var, 5),
1194          );
1195        }
1196        return $form;
1197  
1198      case 'search':
1199        // Build matching conditions
1200        list($join1, $where1) = _db_rewrite_sql();
1201        $arguments1 = array();
1202        $conditions1 = 'n.status = 1';
1203  
1204        if ($type = search_query_extract($keys, 'type')) {
1205          $types = array();
1206          foreach (explode(',', $type) as $t) {
1207            $types[] = "n.type = '%s'";
1208            $arguments1[] = $t;
1209          }
1210          $conditions1 .= ' AND ('. implode(' OR ', $types) .')';
1211          $keys = search_query_insert($keys, 'type');
1212        }
1213  
1214        if ($category = search_query_extract($keys, 'category')) {
1215          $categories = array();
1216          foreach (explode(',', $category) as $c) {
1217            $categories[] = "tn.tid = %d";
1218            $arguments1[] = $c;
1219          }
1220          $conditions1 .= ' AND ('. implode(' OR ', $categories) .')';
1221          $join1 .= ' INNER JOIN {term_node} tn ON n.vid = tn.vid';
1222          $keys = search_query_insert($keys, 'category');
1223        }
1224  
1225        // Build ranking expression (we try to map each parameter to a
1226        // uniform distribution in the range 0..1).
1227        $ranking = array();
1228        $arguments2 = array();
1229        $join2 = '';
1230        // Used to avoid joining on node_comment_statistics twice
1231        $stats_join = FALSE;
1232        $total = 0;
1233        if ($weight = (int)variable_get('node_rank_relevance', 5)) {
1234          // Average relevance values hover around 0.15
1235          $ranking[] = '%d * i.relevance';
1236          $arguments2[] = $weight;
1237          $total += $weight;
1238        }
1239        if ($weight = (int)variable_get('node_rank_recent', 5)) {
1240          // Exponential decay with half-life of 6 months, starting at last indexed node
1241          // c.last_comment_timestamp may be NULL. Since both MAX(anynumber, NULL) and
1242          // GREATEST(anynumber, NULL) return NULL, we use COALESCE(MAX(c.last_comment_timestamp), 0)
1243          // to prevent it from being NULL.
1244          $ranking[] = '%d * POW(2, (GREATEST(MAX(n.created), MAX(n.changed), COALESCE(MAX(c.last_comment_timestamp), 0)) - %d) * 6.43e-8)';
1245          $arguments2[] = $weight;
1246          $arguments2[] = (int)variable_get('node_cron_last', 0);
1247          $join2 .= ' LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
1248          $stats_join = TRUE;
1249          $total += $weight;
1250        }
1251        if (module_exists('comment') && $weight = (int)variable_get('node_rank_comments', 5)) {
1252          // Inverse law that maps the highest reply count on the site to 1 and 0 to 0.
1253          $scale = variable_get('node_cron_comments_scale', 0.0);
1254          $ranking[] = '%d * (2.0 - 2.0 / (1.0 + MAX(c.comment_count) * %f))';
1255          $arguments2[] = $weight;
1256          $arguments2[] = $scale;
1257          if (!$stats_join) {
1258            $join2 .= ' LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
1259          }
1260          $total += $weight;
1261        }
1262        if (module_exists('statistics') && variable_get('statistics_count_content_views', 0) &&
1263            $weight = (int)variable_get('node_rank_views', 5)) {
1264          // Inverse law that maps the highest view count on the site to 1 and 0 to 0.
1265          $scale = variable_get('node_cron_views_scale', 0.0);
1266          $ranking[] = '%d * (2.0 - 2.0 / (1.0 + MAX(nc.totalcount) * %f))';
1267          $arguments2[] = $weight;
1268          $arguments2[] = $scale;
1269          $join2 .= ' LEFT JOIN {node_counter} nc ON nc.nid = i.sid';
1270          $total += $weight;
1271        }
1272        
1273        // When all search factors are disabled (ie they have a weight of zero), 
1274        // the default score is based only on keyword relevance and there is no need to 
1275        // adjust the score of each item. 
1276        if ($total == 0) {
1277          $select2 = 'i.relevance AS score';
1278          $total = 1;
1279        }
1280        else {
1281          $select2 = implode(' + ', $ranking) . ' AS score';
1282        }
1283        
1284        // Do search.
1285        $find = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid = i.sid '. $join1, $conditions1 . (empty($where1) ? '' : ' AND '. $where1), $arguments1, $select2, $join2, $arguments2);
1286  
1287        // Load results.
1288        $results = array();
1289        foreach ($find as $item) {
1290          // Build the node body.
1291          $node = node_load($item->sid);
1292          $node->build_mode = NODE_BUILD_SEARCH_RESULT;
1293          $node = node_build_content($node, FALSE, FALSE);
1294          $node->body = drupal_render($node->content);
1295  
1296          // Fetch comments for snippet.
1297          if (module_exists('comment')) {
1298            $node->body .= comment_nodeapi($node, 'update index');
1299          }
1300          // Fetch terms for snippet.
1301          if (module_exists('taxonomy')) {
1302            $node->body .= taxonomy_nodeapi($node, 'update index');
1303          }
1304  
1305          $extra = node_invoke_nodeapi($node, 'search result');
1306          $results[] = array(
1307            'link' => url('node/'. $item->sid, array('absolute' => TRUE)),
1308            'type' => check_plain(node_get_types('name', $node)),
1309            'title' => $node->title,
1310            'user' => theme('username', $node),
1311            'date' => $node->changed,
1312            'node' => $node,
1313            'extra' => $extra,
1314            'score' => $item->score / $total,
1315            'snippet' => search_excerpt($keys, $node->body),
1316          );
1317        }
1318        return $results;
1319    }
1320  }
1321  
1322  /**
1323   * Implementation of hook_user().
1324   */
1325  function node_user($op, &$edit, &$user) {
1326    if ($op == 'delete') {
1327      db_query('UPDATE {node} SET uid = 0 WHERE uid = %d', $user->uid);
1328      db_query('UPDATE {node_revisions} SET uid = 0 WHERE uid = %d', $user->uid);
1329    }
1330  }
1331  
1332  /**
1333   * Theme the content ranking part of the search settings admin page.
1334   *
1335   * @ingroup themeable
1336   */
1337  function theme_node_search_admin($form) {
1338    $output = drupal_render($form['info']);
1339  
1340    $header = array(t('Factor'), t('Weight'));
1341    foreach (element_children($form['factors']) as $key) {
1342      $row = array();
1343      $row[] = $form['factors'][$key]['#title'];
1344      unset($form['factors'][$key]['#title']);
1345      $row[] = drupal_render($form['factors'][$key]);
1346      $rows[] = $row;
1347    }
1348    $output .= theme('table', $header, $rows);
1349  
1350    $output .= drupal_render($form);
1351    return $output;
1352  }
1353  
1354  /**
1355   * Retrieve the comment mode for the given node ID (none, read, or read/write).
1356   */
1357  function node_comment_mode($nid) {
1358    static $comment_mode;
1359    if (!isset($comment_mode[$nid])) {
1360      $comment_mode[$nid] = db_result(db_query('SELECT comment FROM {node} WHERE nid = %d', $nid));
1361    }
1362    return $comment_mode[$nid];
1363  }
1364  
1365  /**
1366   * Implementation of hook_link().
1367   */
1368  function node_link($type, $node = NULL, $teaser = FALSE) {
1369    $links = array();
1370  
1371    if ($type == 'node') {
1372      if ($teaser == 1 && $node->teaser && !empty($node->readmore)) {
1373        $links['node_read_more'] = array(
1374          'title' => t('Read more'),
1375          'href' => "node/$node->nid",
1376          // The title attribute gets escaped when the links are processed, so
1377          // there is no need to escape here.
1378          'attributes' => array('title' => t('Read the rest of !title.', array('!title' => $node->title)))
1379        );
1380      }
1381    }
1382  
1383    return $links;
1384  }
1385  
1386  function _node_revision_access($node, $op = 'view') {
1387    static $access = array();
1388    if (!isset($access[$node->vid])) {
1389      $node_current_revision = node_load($node->nid);
1390      $is_current_revision = $node_current_revision->vid == $node->vid;
1391      // There should be at least two revisions. If the vid of the given node
1392      // and the vid of the current revision differs, then we already have two
1393      // different revisions so there is no need for a separate database check.
1394      // Also, if you try to revert to or delete the current revision, that's
1395      // not good.
1396      if ($is_current_revision && (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $node->nid)) == 1 || $op == 'update' || $op == 'delete')) {
1397        $access[$node->vid] = FALSE;
1398      }
1399      elseif (user_access('administer nodes')) {
1400        $access[$node->vid] = TRUE;
1401      }
1402      else {
1403        $map = array('view' => 'view revisions', 'update' => 'revert revisions', 'delete' => 'delete revisions');
1404        // First check the user permission, second check the access to the
1405        // current revision and finally, if the node passed in is not the current
1406        // revision then access to that, too.
1407        $access[$node->vid] = isset($map[$op]) && user_access($map[$op]) && node_access($op, $node_current_revision) && ($is_current_revision || node_access($op, $node));
1408      }
1409    }
1410    return $access[$node->vid];
1411  }
1412  
1413  function _node_add_access() {
1414    $types = node_get_types();
1415    foreach ($types as $type) {
1416      if (node_hook($type->type, 'form') && node_access('create', $type->type)) {
1417        return TRUE;
1418      }
1419    }
1420    return FALSE;
1421  }
1422  
1423  /**
1424   * Implementation of hook_menu().
1425   */
1426  function node_menu() {
1427    $items['admin/content/node'] = array(
1428      'title' => 'Content',
1429      'description' => "View, edit, and delete your site's content.",
1430      'page callback' => 'drupal_get_form',
1431      'page arguments' => array('node_admin_content'),
1432      'access arguments' => array('administer nodes'),
1433      'file' => 'node.admin.inc',
1434    );
1435  
1436    $items['admin/content/node/overview'] = array(
1437      'title' => 'List',
1438      'type' => MENU_DEFAULT_LOCAL_TASK,
1439      'weight' => -10,
1440    );
1441  
1442    $items['admin/content/node-settings'] = array(
1443      'title' => 'Post settings',
1444      'description' => 'Control posting behavior, such as teaser length, requiring previews before posting, and the number of posts on the front page.',
1445      'page callback' => 'drupal_get_form',
1446      'page arguments' => array('node_configure'),
1447      'access arguments' => array('administer nodes'),
1448      'file' => 'node.admin.inc',
1449    );
1450    $items['admin/content/node-settings/rebuild'] = array(
1451      'title' => 'Rebuild permissions',
1452      'page arguments' => array('node_configure_rebuild_confirm'),
1453      'file' => 'node.admin.inc',
1454      // Any user than can potentially trigger a node_acess_needs_rebuild(TRUE)
1455      // has to be allowed access to the 'node access rebuild' confirm form.
1456      'access arguments' => array('access administration pages'),
1457      'type' => MENU_CALLBACK,
1458    );
1459  
1460    $items['admin/content/types'] = array(
1461      'title' => 'Content types',
1462      'description' => 'Manage posts by content type, including default status, front page promotion, etc.',
1463      'page callback' => 'node_overview_types',
1464      'access arguments' => array('administer content types'),
1465      'file' => 'content_types.inc',
1466    );
1467    $items['admin/content/types/list'] = array(
1468      'title' => 'List',
1469      'type' => MENU_DEFAULT_LOCAL_TASK,
1470      'weight' => -10,
1471    );
1472    $items['admin/content/types/add'] = array(
1473      'title' => 'Add content type',
1474      'page callback' => 'drupal_get_form',
1475      'page arguments' => array('node_type_form'),
1476      'access arguments' => array('administer content types'),
1477      'file' => 'content_types.inc',
1478      'type' => MENU_LOCAL_TASK,
1479    );
1480    $items['node'] = array(
1481      'title' => 'Content',
1482      'page callback' => 'node_page_default',
1483      'access arguments' => array('access content'),
1484      'type' => MENU_CALLBACK,
1485    );
1486    $items['node/add'] = array(
1487      'title' => 'Create content',
1488      'page callback' => 'node_add_page',
1489      'access callback' => '_node_add_access',
1490      'weight' => 1,
1491      'file' => 'node.pages.inc',
1492    );
1493    $items['rss.xml'] = array(
1494      'title' => 'RSS feed',
1495      'page callback' => 'node_feed',
1496      'access arguments' => array('access content'),
1497      'type' => MENU_CALLBACK,
1498    );
1499    foreach (node_get_types('types', NULL, TRUE) as $type) {
1500      $type_url_str = str_replace('_', '-', $type->type);
1501      $items['node/add/'. $type_url_str] = array(
1502        'title' => drupal_ucfirst($type->name),
1503        'title callback' => 'check_plain',
1504        'page callback' => 'node_add',
1505        'page arguments' => array(2),
1506        'access callback' => 'node_access',
1507        'access arguments' => array('create', $type->type),
1508        'description' => $type->description,
1509        'file' => 'node.pages.inc',
1510      );
1511      $items['admin/content/node-type/'. $type_url_str] = array(
1512        'title' => $type->name,
1513        'page callback' => 'drupal_get_form',
1514        'page arguments' => array('node_type_form', $type),
1515        'access arguments' => array('administer content types'),
1516        'file' => 'content_types.inc',
1517        'type' => MENU_CALLBACK,
1518      );
1519      $items['admin/content/node-type/'. $type_url_str .'/edit'] = array(
1520        'title' => 'Edit',
1521        'type' => MENU_DEFAULT_LOCAL_TASK,
1522      );
1523      $items['admin/content/node-type/'. $type_url_str .'/delete'] = array(
1524        'title' => 'Delete',
1525        'page arguments' => array('node_type_delete_confirm', $type),
1526        'access arguments' => array('administer content types'),
1527        'file' => 'content_types.inc',
1528        'type' => MENU_CALLBACK,
1529      );
1530    }
1531    $items['node/%node'] = array(
1532      'title callback' => 'node_page_title',
1533      'title arguments' => array(1),
1534      'page callback' => 'node_page_view',
1535      'page arguments' => array(1),
1536      'access callback' => 'node_access',
1537      'access arguments' => array('view', 1),
1538      'type' => MENU_CALLBACK);
1539    $items['node/%node/view'] = array(
1540      'title' => 'View',
1541      'type' => MENU_DEFAULT_LOCAL_TASK,
1542      'weight' => -10);
1543    $items['node/%node/edit'] = array(
1544      'title' => 'Edit',
1545      'page callback' => 'node_page_edit',
1546      'page arguments' => array(1),
1547      'access callback' => 'node_access',
1548      'access arguments' => array('update', 1),
1549      'weight' => 1,
1550      'file' => 'node.pages.inc',
1551      'type' => MENU_LOCAL_TASK,
1552    );
1553    $items['node/%node/delete'] = array(
1554      'title' => 'Delete',
1555      'page callback' => 'drupal_get_form',
1556      'page arguments' => array('node_delete_confirm', 1),
1557      'access callback' => 'node_access',
1558      'access arguments' => array('delete', 1),
1559      'file' => 'node.pages.inc',
1560      'weight' => 1,
1561      'type' => MENU_CALLBACK);
1562    $items['node/%node/revisions'] = array(
1563      'title' => 'Revisions',
1564      'page callback' => 'node_revision_overview',
1565      'page arguments' => array(1),
1566      'access callback' => '_node_revision_access',
1567      'access arguments' => array(1),
1568      'weight' => 2,
1569      'file' => 'node.pages.inc',
1570      'type' => MENU_LOCAL_TASK,
1571    );
1572    $items['node/%node/revisions/%/view'] = array(
1573      'title' => 'Revisions',
1574      'load arguments' => array(3),
1575      'page callback' => 'node_show',
1576      'page arguments' => array(1, NULL, TRUE),
1577      'access callback' => '_node_revision_access',
1578      'access arguments' => array(1),
1579      'type' => MENU_CALLBACK,
1580    );
1581    $items['node/%node/revisions/%/revert'] = array(
1582      'title' => 'Revert to earlier revision',
1583      'load arguments' => array(3),
1584      'page callback' => 'drupal_get_form',
1585      'page arguments' => array('node_revision_revert_confirm', 1),
1586      'access callback' => '_node_revision_access',
1587      'access arguments' => array(1, 'update'),
1588      'file' => 'node.pages.inc',
1589      'type' => MENU_CALLBACK,
1590    );
1591    $items['node/%node/revisions/%/delete'] = array(
1592      'title' => 'Delete earlier revision',
1593      'load arguments' => array(3),
1594      'page callback' => 'drupal_get_form',
1595      'page arguments' => array('node_revision_delete_confirm', 1),
1596      'access callback' => '_node_revision_access',
1597      'access arguments' => array(1, 'delete'),
1598      'file' => 'node.pages.inc',
1599      'type' => MENU_CALLBACK,
1600    );
1601    return $items;
1602  }
1603  
1604  /**
1605   * Title callback.
1606   */
1607  function node_page_title($node) {
1608    return $node->title;
1609  }
1610  
1611  /**
1612   * Implementation of hook_init().
1613   */
1614  function node_init() {
1615    drupal_add_css(drupal_get_path('module', 'node') .'/node.css');
1616  }
1617  
1618  function node_last_changed($nid) {
1619    $node = db_fetch_object(db_query('SELECT changed FROM {node} WHERE nid = %d', $nid));
1620    return ($node->changed);
1621  }
1622  
1623  /**
1624   * Return a list of all the existing revision numbers.
1625   */
1626  function node_revision_list($node) {
1627    $revisions = array();
1628    $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);
1629    while ($revision = db_fetch_object($result)) {
1630      $revisions[$revision->vid] = $revision;
1631    }
1632  
1633    return $revisions;
1634  }
1635  
1636  /**
1637   * Implementation of hook_block().
1638   */
1639  function node_block($op = 'list', $delta = 0) {
1640    if ($op == 'list') {
1641      $blocks[0]['info'] = t('Syndicate');
1642      // Not worth caching.
1643      $blocks[0]['cache'] = BLOCK_NO_CACHE;
1644      return $blocks;
1645    }
1646    else if ($op == 'view') {
1647      $block['subject'] = t('Syndicate');
1648      $block['content'] = theme('feed_icon', url('rss.xml'), t('Syndicate'));
1649  
1650      return $block;
1651    }
1652  }
1653  
1654  /**
1655   * A generic function for generating RSS feeds from a set of nodes.
1656   *
1657   * @param $nids
1658   *   An array of node IDs (nid). Defaults to FALSE so empty feeds can be
1659   *   generated with passing an empty array, if no items are to be added
1660   *   to the feed.
1661   * @param $channel
1662   *   An associative array containing title, link, description and other keys.
1663   *   The link should be an absolute URL.
1664   */
1665  function node_feed($nids = FALSE, $channel = array()) {
1666    global $base_url, $language;
1667  
1668    if ($nids === FALSE) {
1669      $nids = array();
1670      $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));
1671      while ($row = db_fetch_object($result)) {
1672        $nids[] = $row->nid;
1673      }
1674    }
1675  
1676    $item_length = variable_get('feed_item_length', 'teaser');
1677    $namespaces = array('xmlns:dc' => 'http://purl.org/dc/elements/1.1/');
1678  
1679    $items = '';
1680    foreach ($nids as $nid) {
1681      // Load the specified node:
1682      $item = node_load($nid);
1683      $item->build_mode = NODE_BUILD_RSS;
1684      $item->link = url("node/$nid", array('absolute' => TRUE));
1685  
1686      if ($item_length != 'title') {
1687        $teaser = ($item_length == 'teaser') ? TRUE : FALSE;
1688  
1689        // Filter and prepare node teaser
1690        if (node_hook($item, 'view')) {
1691          $item = node_invoke($item, 'view', $teaser, FALSE);
1692        }
1693        else {
1694          $item = node_prepare($item, $teaser);
1695        }
1696  
1697        // Allow modules to change $node->content before the node is rendered.
1698        node_invoke_nodeapi($item, 'view', $teaser, FALSE);
1699  
1700        // Set the proper node property, then unset unused $node property so that a
1701        // bad theme can not open a security hole.
1702        $content = drupal_render($item->content);
1703        if ($teaser) {
1704          $item->teaser = $content;
1705          unset($item->body);
1706        }
1707        else {
1708          $item->body = $content;
1709          unset($item->teaser);
1710        }
1711      
1712        // Allow modules to modify the fully-built node.
1713        node_invoke_nodeapi($item, 'alter', $teaser, FALSE);
1714      }
1715  
1716      // Allow modules to add additional item fields and/or modify $item
1717      $extra = node_invoke_nodeapi($item, 'rss item');
1718      $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'))));
1719      foreach ($extra as $element) {
1720        if (isset($element['namespace'])) {
1721          $namespaces = array_merge($namespaces, $element['namespace']);
1722        }
1723      }
1724  
1725      // Prepare the item description
1726      switch ($item_length) {
1727        case 'fulltext':
1728          $item_text = $item->body;
1729          break;
1730        case 'teaser':
1731          $item_text = $item->teaser;
1732          if (!empty($item->readmore)) {
1733            $item_text .= '<p>'. l(t('read more'), 'node/'. $item->nid, array('absolute' => TRUE, 'attributes' => array('target' => '_blank'))) .'</p>';
1734          }
1735          break;
1736        case 'title':
1737          $item_text = '';
1738          break;
1739      }
1740  
1741      $items .= format_rss_item($item->title, $item->link, $item_text, $extra);
1742    }
1743  
1744    $channel_defaults = array(
1745      'version'     => '2.0',
1746      'title'       => variable_get('site_name', 'Drupal'),
1747      'link'        => $base_url,
1748      'description' => variable_get('site_mission', ''),
1749      'language'    => $language->language
1750    );
1751    $channel = array_merge($channel_defaults, $channel);
1752  
1753    $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1754    $output .= "<rss version=\"". $channel["version"] ."\" xml:base=\"". $base_url ."\" ". drupal_attributes($namespaces) .">\n";
1755    $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language']);
1756    $output .= "</rss>\n";
1757  
1758    drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
1759    print $output;
1760  }
1761  
1762  /**
1763   * Menu callback; Generate a listing of promoted nodes.
1764   */
1765  function node_page_default() {
1766    $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));
1767  
1768    $output = '';
1769    $num_rows = FALSE;
1770    while ($node = db_fetch_object($result)) {
1771      $output .= node_view(node_load($node->nid), 1);
1772      $num_rows = TRUE;
1773    }
1774  
1775    if ($num_rows) {
1776      $feed_url = url('rss.xml', array('absolute' => TRUE));
1777      drupal_add_feed($feed_url, variable_get('site_name', 'Drupal') .' '. t('RSS'));
1778      $output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
1779    }
1780    else {
1781      $default_message = t('<h1 class="title">Welcome to your new Drupal website!</h1><p>Please follow these steps to set up and start using your website:</p>');
1782      $default_message .= '<ol>';
1783  
1784      $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>';
1785      $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>';
1786      $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>';
1787      $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>';
1788      $default_message .= '</ol>';
1789      $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>';
1790  
1791      $output = '<div id="first-time">'. $default_message .'</div>';
1792    }
1793    drupal_set_title('');
1794  
1795    return $output;
1796  }
1797  
1798  /**
1799   * Menu callback; view a single node.
1800   */
1801  function node_page_view($node, $cid = NULL) {
1802    drupal_set_title(check_plain($node->title));
1803    return node_show($node, $cid);
1804  }
1805  
1806  /**
1807   * Implementation of hook_update_index().
1808   */
1809  function node_update_index() {
1810    $limit = (int)variable_get('search_cron_limit', 100);
1811  
1812    // Store the maximum possible comments per thread (used for ranking by reply count)
1813    variable_set('node_cron_comments_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(comment_count) FROM {node_comment_statistics}'))));
1814    variable_set('node_cron_views_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(totalcount) FROM {node_counter}'))));
1815  
1816    $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);
1817  
1818    while ($node = db_fetch_object($result)) {
1819      _node_index_node($node);
1820    }
1821  }
1822  
1823  /**
1824   * Index a single node.
1825   *
1826   * @param $node
1827   *   The node to index.
1828   */
1829  function _node_index_node($node) {
1830    $node = node_load($node->nid);
1831  
1832    // save the changed time of the most recent indexed node, for the search results half-life calculation
1833    variable_set('node_cron_last', $node->changed);
1834  
1835    // Build the node body.
1836    $node->build_mode = NODE_BUILD_SEARCH_INDEX;
1837    $node = node_build_content($node, FALSE, FALSE);
1838    $node->body = drupal_render($node->content);
1839  
1840    $text = '<h1>'. check_plain($node->title) .'</h1>'. $node->body;
1841  
1842    // Fetch extra data normally not visible
1843    $extra = node_invoke_nodeapi($node, 'update index');
1844    foreach ($extra as $t) {
1845      $text .= $t;
1846    }
1847  
1848    // Update index
1849    search_index($node->nid, 'node', $text);
1850  }
1851  
1852  /**
1853   * Implementation of hook_form_alter().
1854   */
1855  function node_form_alter(&$form, $form_state, $form_id) {
1856    // Advanced node search form
1857    if ($form_id == 'search_form' && $form['module']['#value'] == 'node' && user_access('use advanced search')) {
1858      // Keyword boxes:
1859      $form['advanced'] = array(
1860        '#type' => 'fieldset',
1861        '#title' => t('Advanced search'),
1862        '#collapsible' => TRUE,
1863        '#collapsed' => TRUE,
1864        '#attributes' => array('class' => 'search-advanced'),
1865      );
1866      $form['advanced']['keywords'] = array(
1867        '#prefix' => '<div class="criterion">',
1868        '#suffix' => '</div>',
1869      );
1870      $form['advanced']['keywords']['or'] = array(
1871        '#type' => 'textfield',
1872        '#title' => t('Containing any of the words'),
1873        '#size' => 30,
1874        '#maxlength' => 255,
1875      );
1876      $form['advanced']['keywords']['phrase'] = array(
1877        '#type' => 'textfield',
1878        '#title' => t('Containing the phrase'),
1879        '#size' => 30,
1880        '#maxlength' => 255,
1881      );
1882      $form['advanced']['keywords']['negative'] = array(
1883        '#type' => 'textfield',
1884        '#title' => t('Containing none of the words'),
1885        '#size' => 30,
1886        '#maxlength' => 255,
1887      );
1888  
1889      // Taxonomy box:
1890      if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
1891        $form['advanced']['category'] = array(
1892          '#type' => 'select',
1893          '#title' => t('Only in the category(s)'),
1894          '#prefix' => '<div class="criterion">',
1895          '#size' => 10,
1896          '#suffix' => '</div>',
1897          '#options' => $taxonomy,
1898          '#multiple' => TRUE,
1899        );
1900      }
1901  
1902      // Node types:
1903      $types = array_map('check_plain', node_get_types('names'));
1904      $form['advanced']['type'] = array(
1905        '#type' => 'checkboxes',
1906        '#title' => t('Only of the type(s)'),
1907        '#prefix' => '<div class="criterion">',
1908        '#suffix' => '</div>',
1909        '#options' => $types,
1910      );
1911      $form['advanced']['submit'] = array(
1912        '#type' => 'submit',
1913        '#value' => t('Advanced search'),
1914        '#prefix' => '<div class="action">',
1915        '#suffix' => '</div>',
1916      );
1917  
1918      $form['#validate'][] = 'node_search_validate';
1919    }
1920  }
1921  
1922  /**
1923   * Form API callback for the search form. Registered in node_form_alter().
1924   */
1925  function node_search_validate($form, &$form_state) {
1926    // Initialise using any existing basic search keywords.
1927    $keys = $form_state['values']['processed_keys'];
1928  
1929    // Insert extra restrictions into the search keywords string.
1930    if (isset($form_state['values']['type']) && is_array($form_state['values']['type'])) {
1931      // Retrieve selected types - Forms API sets the value of unselected checkboxes to 0.
1932      $form_state['values']['type'] = array_filter($form_state['values']['type']);
1933      if (count($form_state['values']['type'])) {
1934        $keys = search_query_insert($keys, 'type', implode(',', array_keys($form_state['values']['type'])));
1935      }
1936    }
1937  
1938    if (isset($form_state['values']['category']) && is_array($form_state['values']['category'])) {
1939      $keys = search_query_insert($keys, 'category', implode(',', $form_state['values']['category']));
1940    }
1941    if ($form_state['values']['or'] != '') {
1942      if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $form_state['values']['or'], $matches)) {
1943        $keys .= ' '. implode(' OR ', $matches[1]);
1944      }
1945    }
1946    if ($form_state['values']['negative'] != '') {
1947      if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $form_state['values']['negative'], $matches)) {
1948        $keys .= ' -'. implode(' -', $matches[1]);
1949      }
1950    }
1951    if ($form_state['values']['phrase'] != '') {
1952      $keys .= ' "'. str_replace('"', ' ', $form_state['values']['phrase']) .'"';
1953    }
1954    if (!empty($keys)) {
1955      form_set_value($form['basic']['inline']['processed_keys'], trim($keys), $form_state);
1956    }
1957  }
1958  
1959  /**
1960   * @defgroup node_access Node access rights
1961   * @{
1962   * The node access system determines who can do what to which nodes.
1963   *
1964   * In determining access rights for a node, node_access() first checks
1965   * whether the user has the "administer nodes" permission. Such users have
1966   * unrestricted access to all nodes. Then the node module's hook_access()
1967   * is called, and a TRUE or FALSE return value will grant or deny access.
1968   * This allows, for example, the blog module to always grant access to the
1969   * blog author, and for the book module to always deny editing access to
1970   * PHP pages.
1971   *
1972   * If node module does not intervene (returns NULL), then the
1973   * node_access table is used to determine access. All node access
1974   * modules are queried using hook_node_grants() to assemble a list of
1975   * "grant IDs" for the user. This list is compared against the table.
1976   * If any row contains the node ID in question (or 0, which stands for "all
1977   * nodes"), one of the grant IDs returned, and a value of TRUE for the
1978   * operation in question, then access is granted. Note that this table is a
1979   * list of grants; any matching row is sufficient to grant access to the
1980   * node.
1981   *
1982   * In node listings, the process above is followed except that
1983   * hook_access() is not called on each node for performance reasons and for
1984   * proper functioning of the pager system. When adding a node listing to your
1985   * module, be sure to use db_rewrite_sql() to add
1986   * the appropriate clauses to your query for access checks.
1987   *
1988   * To see how to write a node access module of your own, see
1989   * node_access_example.module.
1990   */
1991  
1992  /**
1993   * Determine whether the current user may perform the given operation on the
1994   * specified node.
1995   *
1996   * @param $op
1997   *   The operation to be performed on the node. Possible values are:
1998   *   - "view"
1999   *   - "update"
2000   *   - "delete"
2001   *   - "create"
2002   * @param $node
2003   *   The node object (or node array) on which the operation is to be performed,
2004   *   or node type (e.g. 'forum') for "create" operation.
2005   * @param $account
2006   *   Optional, a user object representing the user for whom the operation is to
2007   *   be performed. Determines access for a user other than the current user.
2008   * @return
2009   *   TRUE if the operation may be performed, or FALSE otherwise.
2010   */
2011  function node_access($op, $node, $account = NULL) {
2012    global $user;
2013  
2014    if (!$node || !in_array($op, array('view', 'update', 'delete', 'create'), TRUE)) {
2015      // If there was no node to check against, or the $op was not one of the
2016      // supported ones, we return access denied.
2017      return FALSE;
2018    }
2019    // Convert the node to an object if necessary:
2020    if ($op != 'create') {
2021      $node = (object)$node;
2022    }
2023    // If no user object is supplied, the access check is for the current user.
2024    if (empty($account)) {
2025      $account = $user;
2026    }
2027    // If the node is in a restricted format, disallow editing.
2028    if ($op == 'update' && !filter_access($node->format)) {
2029      return FALSE;
2030    }
2031  
2032    if (user_access('administer nodes', $account)) {
2033      return TRUE;
2034    }
2035  
2036    if (!user_access('access content', $account)) {
2037      return FALSE;
2038    }
2039  
2040    // Can't use node_invoke(), because the access hook takes the $op parameter
2041    // before the $node parameter.
2042    $module = node_get_types('module', $node);
2043    if ($module == 'node') {
2044      $module = 'node_content'; // Avoid function name collisions.
2045    }
2046    $access = module_invoke($module, 'access', $op, $node, $account);
2047    if (!is_null($access)) {
2048      return $access;
2049    }
2050  
2051    // If the module did not override the access rights, use those set in the
2052    // node_access table.
2053    if ($op != 'create' && $node->nid && $node->status) {
2054      $grants = array();
2055      foreach (node_access_grants($op, $account) as $realm => $gids) {
2056        foreach ($gids as $gid) {
2057          $grants[] = "(gid = $gid AND realm = '$realm')";
2058        }
2059      }
2060  
2061      $grants_sql = '';
2062      if (count($grants)) {
2063        $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
2064      }
2065  
2066      $sql = "SELECT 1 FROM {node_access} WHERE (nid = 0 OR nid = %d) $grants_sql AND grant_$op >= 1";
2067      $result = db_query_range($sql, $node->nid, 0, 1);
2068      return (bool) db_result($result);
2069    }
2070  
2071    // Let authors view their own nodes.
2072    if ($op == 'view' && $account->uid == $node->uid && $account->uid != 0) {
2073      return TRUE;
2074    }
2075  
2076    return FALSE;
2077  }
2078  
2079  /**
2080   * Generate an SQL join clause for use in fetching a node listing.
2081   *
2082   * @param $node_alias
2083   *   If the node table has been given an SQL alias other than the default
2084   *   "n", that must be passed here.
2085   * @param $node_access_alias
2086   *   If the node_access table has been given an SQL alias other than the default
2087   *   "na", that must be passed here.
2088   * @return
2089   *   An SQL join clause.
2090   */
2091  function _node_access_join_sql($node_alias = 'n', $node_access_alias = 'na') {
2092    if (user_access('administer nodes')) {
2093      return '';
2094    }
2095  
2096    return 'INNER JOIN {node_access} '. $node_access_alias .' ON '. $node_access_alias .'.nid = '. $node_alias .'.nid';
2097  }
2098  
2099  /**
2100   * Generate an SQL where clause for use in fetching a node listing.
2101   *
2102   * @param $op
2103   *   The operation that must be allowed to return a node.
2104   * @param $node_access_alias
2105   *   If the node_access table has been given an SQL alias other than the default
2106   *   "na", that must be passed here.
2107   * @param $account
2108   *   The user object for the user performing the operation. If omitted, the
2109   *   current user is used.
2110   * @return
2111   *   An SQL where clause.
2112   */
2113  function _node_access_where_sql($op = 'view', $node_access_alias = 'na', $account = NULL) {
2114    if (user_access('administer nodes')) {
2115      return;
2116    }
2117  
2118    $grants = array();
2119    foreach (node_access_grants($op, $account) as $realm => $gids) {
2120      foreach ($gids as $gid) {
2121        $grants[] = "($node_access_alias.gid = $gid AND $node_access_alias.realm = '$realm')";
2122      }
2123    }
2124  
2125    $grants_sql = '';
2126    if (count($grants)) {
2127      $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
2128    }
2129  
2130    $sql = "$node_access_alias.grant_$op >= 1 $grants_sql";
2131    return $sql;
2132  }
2133  
2134  /**
2135   * Fetch an array of permission IDs granted to the given user ID.
2136   *
2137   * The implementation here provides only the universal "all" grant. A node
2138   * access module should implement hook_node_grants() to provide a grant
2139   * list for the user.
2140   *
2141   * @param $op
2142   *   The operation that the user is trying to perform.
2143   * @param $account
2144   *   The user object for the user performing the operation. If omitted, the
2145   *   current user is used.
2146   * @return
2147   *   An associative array in which the keys are realms, and the values are
2148   *   arrays of grants for those realms.
2149   */
2150  function node_access_grants($op, $account = NULL) {
2151  
2152    if (!isset($account)) {
2153      $account = $GLOBALS['user'];
2154    }
2155  
2156    return array_merge(array('all' => array(0)), module_invoke_all('node_grants', $account, $op));
2157  }
2158  
2159  /**
2160   * Determine whether the user has a global viewing grant for all nodes.
2161   */
2162  function node_access_view_all_nodes() {
2163    static $access;
2164  
2165    if (!isset($access)) {
2166      $grants = array();
2167      foreach (node_access_grants('view') as $realm => $gids) {
2168        foreach ($gids as $gid) {
2169          $grants[] = "(gid = $gid AND realm = '$realm')";
2170        }
2171      }
2172  
2173      $grants_sql = '';
2174      if (count($grants)) {
2175        $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
2176      }
2177  
2178      $sql = "SELECT COUNT(*) FROM {node_access} WHERE nid = 0 $grants_sql AND grant_view >= 1";
2179      $result = db_query($sql);
2180      $access = db_result($result);
2181    }
2182  
2183    return $access;
2184  }
2185  
2186  /**
2187   * Implementation of hook_db_rewrite_sql
2188   */
2189  function node_db_rewrite_sql($query, $primary_table, $primary_field) {
2190    if ($primary_field == 'nid' && !node_access_view_all_nodes()) {
2191      $return['join'] = _node_access_join_sql($primary_table);
2192      $return['where'] = _node_access_where_sql();
2193      $return['distinct'] = 1;
2194      return $return;
2195    }
2196  }
2197  
2198  /**
2199   * Gets the list of node access grants and writes them to the database.
2200   *
2201   * This function is called when a node is saved, and can also be called by
2202   * modules if something other than a node save causes node access permissions
2203   * to change. It collects all node access grants for the node from
2204   * hook_node_access_records() implementations and saves the collected
2205   * grants to the database.
2206   *
2207   * @param $node
2208   *   The $node to acquire grants for.
2209   */
2210  function node_access_acquire_grants($node) {
2211    $grants = module_invoke_all('node_access_records', $node);
2212    if (empty($grants)) {
2213      $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0);
2214    }
2215    else {
2216      // retain grants by highest priority
2217      $grant_by_priority = array();
2218      foreach ($grants as $g) {
2219        $grant_by_priority[intval($g['priority'])][] = $g;
2220      }
2221      krsort($grant_by_priority);
2222      $grants = array_shift($grant_by_priority);
2223    }
2224  
2225    node_access_write_grants($node, $grants);
2226  }
2227  
2228  /**
2229   * Writes a list of grants to the database, deleting any previously saved ones.
2230   *
2231   * If a realm is provided, it will only delete grants from that realm, but it
2232   * will always delete a grant from the 'all' realm. Modules that utilize
2233   * node_access can use this function when doing mass updates due to widespread
2234   * permission changes.
2235   *
2236   * @param $node
2237   *   The $node being written to. All that is necessary is that it contain a nid.
2238   * @param $grants
2239   *   A list of grants to write. Each grant is an array that must contain the
2240   *   following keys: realm, gid, grant_view, grant_update, grant_delete.
2241   *   The realm is specified by a particular module; the gid is as well, and
2242   *   is a module-defined id to define grant privileges. each grant_* field
2243   *   is a boolean value.
2244   * @param $realm
2245   *   If provided, only read/write grants for that realm.
2246   * @param $delete
2247   *   If false, do not delete records. This is only for optimization purposes,
2248   *   and assumes the caller has already performed a mass delete of some form.
2249   */
2250  function node_access_write_grants($node, $grants, $realm = NULL, $delete = TRUE) {
2251    if ($delete) {
2252      $query = 'DELETE FROM {node_access} WHERE nid = %d';
2253      if ($realm) {
2254        $query .= " AND realm in ('%s', 'all')";
2255      }
2256      db_query($query, $node->nid, $realm);
2257    }
2258  
2259    // Only perform work when node_access modules are active.
2260    if (count(module_implements('node_grants'))) {
2261      foreach ($grants as $grant) {
2262        if ($realm && $realm != $grant['realm']) {
2263          continue;
2264        }
2265        // Only write grants; denies are implicit.
2266        if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
2267          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']);
2268        }
2269      }
2270    }
2271  }
2272  
2273  /**
2274   * Flag / unflag the node access grants for rebuilding, or read the current
2275   * value of the flag.
2276   *
2277   * When the flag is set, a message is displayed to users with 'access
2278   * administration pages' permission, pointing to the 'rebuild' confirm form.
2279   * This can be used as an alternative to direct node_access_rebuild calls,
2280   * allowing administrators to decide when they want to perform the actual
2281   * (possibly time consuming) rebuild.
2282   * When unsure the current user is an adminisrator, node_access_rebuild
2283   * should be used instead.
2284   *
2285   * @param $rebuild
2286   *   (Optional) The boolean value to be written.
2287    * @return
2288   *   (If no value was provided for $rebuild) The current value of the flag.
2289   */
2290  function node_access_needs_rebuild($rebuild = NULL) {
2291    if (!isset($rebuild)) {
2292      return variable_get('node_access_needs_rebuild', FALSE);
2293    }
2294    elseif ($rebuild) {
2295      variable_set('node_access_needs_rebuild', TRUE);
2296    }
2297    else {
2298      variable_del('node_access_needs_rebuild');
2299    }
2300  }
2301  
2302  /**
2303   * Rebuild the node access database. This is occasionally needed by modules
2304   * that make system-wide changes to access levels.
2305   *
2306   * When the rebuild is required by an admin-triggered action (e.g module
2307   * settings form), calling node_access_needs_rebuild(TRUE) instead of
2308   * node_access_rebuild() lets the user perform his changes and actually
2309   * rebuild only once he is done.
2310   *
2311   * Note : As of Drupal 6, node access modules are not required to (and actually
2312   * should not) call node_access_rebuild() in hook_enable/disable anymore.
2313   *
2314   * @see node_access_needs_rebuild()
2315   *
2316   * @param $batch_mode
2317   *   Set to TRUE to process in 'batch' mode, spawning processing over several
2318   *   HTTP requests (thus avoiding the risk of PHP timeout if the site has a
2319   *   large number of nodes).
2320   *   hook_update_N and any form submit handler are safe contexts to use the
2321   *   'batch mode'. Less decidable cases (such as calls from hook_user,
2322   *   hook_taxonomy, hook_node_type...) might consider using the non-batch mode.
2323   */
2324  function node_access_rebuild($batch_mode = FALSE) {
2325    db_query("DELETE FROM {node_access}");
2326    // Only recalculate if the site is using a node_access module.
2327    if (count(module_implements('node_grants'))) {
2328      if ($batch_mode) {
2329        $batch = array(
2330          'title' => t('Rebuilding content access permissions'),
2331          'operations' => array(
2332            array('_node_access_rebuild_batch_operation', array()),
2333          ),
2334          'finished' => '_node_access_rebuild_batch_finished'
2335        );
2336        batch_set($batch);
2337      }
2338      else {
2339        // Try to allocate enough time to rebuild node grants
2340        if (function_exists('set_time_limit')) {
2341          @set_time_limit(240);
2342        }
2343        $result = db_query("SELECT nid FROM {node}");
2344        while ($node = db_fetch_object($result)) {
2345          $loaded_node = node_load($node->nid, NULL, TRUE);
2346          // To preserve database integrity, only aquire grants if the node
2347          // loads successfully.
2348          if (!empty($loaded_node)) {
2349            node_access_acquire_grants($loaded_node);
2350          }
2351        }
2352      }
2353    }
2354    else {
2355      // Not using any node_access modules. Add the default grant.
2356      db_query("INSERT INTO {node_access} VALUES (0, 0, 'all', 1, 0, 0)");
2357    }
2358  
2359    if (!isset($batch)) {
2360      drupal_set_message(t('Content permissions have been rebuilt.'));
2361      node_access_needs_rebuild(FALSE);
2362      cache_clear_all();
2363    }
2364  }
2365  
2366  /**
2367   * Batch operation for node_access_rebuild_batch.
2368   *
2369   * This is a mutlistep operation : we go through all nodes by packs of 20.
2370   * The batch processing engine interrupts processing and sends progress
2371   * feedback after 1 second execution time.
2372   */
2373  function _node_access_rebuild_batch_operation(&$context) {
2374    if (empty($context['sandbox'])) {
2375      // Initiate multistep processing.
2376      $context['sandbox']['progress'] = 0;
2377      $context['sandbox']['current_node'] = 0;
2378      $context['sandbox']['max'] = db_result(db_query('SELECT COUNT(DISTINCT nid) FROM {node}'));
2379    }
2380  
2381    // Process the next 20 nodes.
2382    $limit = 20;
2383    $result = db_query_range("SELECT nid FROM {node} WHERE nid > %d ORDER BY nid ASC", $context['sandbox']['current_node'], 0, $limit);
2384    while ($row = db_fetch_array($result)) {
2385      $loaded_node = node_load($row['nid'], NULL, TRUE);
2386      // To preserve database integrity, only aquire grants if the node
2387      // loads successfully.
2388      if (!empty($loaded_node)) {
2389        node_access_acquire_grants($loaded_node);
2390      }
2391      $context['sandbox']['progress']++;
2392      $context['sandbox']['current_node'] = $row['nid'];
2393    }
2394  
2395    // Multistep processing : report progress.
2396    if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
2397      $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
2398    }
2399  }
2400  
2401  /**
2402   * Post-processing for node_access_rebuild_batch.
2403   */
2404  function _node_access_rebuild_batch_finished($success, $results, $operations) {
2405    if ($success) {
2406      drupal_set_message(t('The content access permissions have been rebuilt.'));
2407      node_access_needs_rebuild(FALSE);
2408    }
2409    else {
2410      drupal_set_message(t('The content access permissions have not been properly rebuilt.'), 'error');
2411    }
2412    cache_clear_all();
2413  }
2414  
2415  /**
2416   * @} End of "defgroup node_access".
2417   */
2418  
2419  
2420  /**
2421   * @defgroup node_content Hook implementations for user-created content types.
2422   * @{
2423   */
2424  
2425  /**
2426   * Implementation of hook_access().
2427   *
2428   * Named so as not to conflict with node_access()
2429   */
2430  function node_content_access($op, $node, $account) {
2431    $type = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
2432  
2433    if ($op == 'create') {
2434      return user_access('create '. $type .' content', $account);
2435    }
2436  
2437    if ($op == 'update') {
2438      if (user_access('edit any '. $type .' content', $account) || (user_access('edit own '. $type .' content', $account) && ($account->uid == $node->uid))) {
2439        return TRUE;
2440      }
2441    }
2442  
2443    if ($op == 'delete') {
2444      if (user_access('delete any '. $type .' content', $account) || (user_access('delete own '. $type .' content', $account) && ($account->uid == $node->uid))) {
2445        return TRUE;
2446      }
2447    }
2448  }
2449  
2450  /**
2451   * Implementation of hook_form().
2452   */
2453  function node_content_form($node, $form_state) {
2454    $type = node_get_types('type', $node);
2455    $form = array();
2456  
2457    if ($type->has_title) {
2458      $form['title'] = array(
2459        '#type' => 'textfield',
2460        '#title' => check_plain($type->title_label),
2461        '#required' => TRUE,
2462        '#default_value' => $node->title,
2463        '#maxlength' => 255,
2464        '#weight' => -5,
2465      );
2466    }
2467  
2468    if ($type->has_body) {
2469      $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count);
2470    }
2471  
2472    return $form;
2473  }
2474  
2475  /**
2476   * @} End of "defgroup node_content".
2477   */
2478  
2479  /**
2480   * Implementation of hook_forms(). All node forms share the same form handler
2481   */
2482  function node_forms() {
2483    $forms = array();
2484    if ($types = node_get_types()) {
2485      foreach (array_keys($types) as $type) {
2486        $forms[$type .'_node_form']['callback'] = 'node_form';
2487      }
2488    }
2489    return $forms;
2490  }
2491  
2492  /**
2493   * Format the "Submitted by username on date/time" for each node
2494   *
2495   * @ingroup themeable
2496   */
2497  function theme_node_submitted($node) {
2498    return t('Submitted by !username on @datetime',
2499      array(
2500        '!username' => theme('username', $node),
2501        '@datetime' => format_date($node->created),
2502      ));
2503  }
2504  
2505  /**
2506   * Implementation of hook_hook_info().
2507   */
2508  function node_hook_info() {
2509    return array(
2510      'node' => array(
2511        'nodeapi' => array(
2512          'presave' => array(
2513            'runs when' => t('When either saving a new post or updating an existing post'),
2514          ),
2515          'insert' => array(
2516            'runs when' => t('After saving a new post'),
2517          ),
2518          'update' => array(
2519            'runs when' => t('After saving an updated post'),
2520          ),
2521          'delete' => array(
2522            'runs when' => t('After deleting a post')
2523          ),
2524          'view' => array(
2525            'runs when' => t('When content is viewed by an authenticated user')
2526          ),
2527        ),
2528      ),
2529    );
2530  }
2531  
2532  /**
2533   * Implementation of hook_action_info().
2534   */
2535  function node_action_info() {
2536    return array(
2537      'node_publish_action' => array(
2538        'type' => 'node',
2539        'description' => t('Publish post'),
2540        'configurable' => FALSE,
2541        'behavior' => array('changes_node_property'),
2542        'hooks' => array(
2543          'nodeapi' => array('presave'),
2544          'comment' => array('insert', 'update'),
2545        ),
2546      ),
2547      'node_unpublish_action' => array(
2548        'type' => 'node',
2549        'description' => t('Unpublish post'),
2550        'configurable' => FALSE,
2551        'behavior' => array('changes_node_property'),
2552        'hooks' => array(
2553          'nodeapi' => array('presave'),
2554          'comment' => array('delete', 'insert', 'update'),
2555        ),
2556      ),
2557      'node_make_sticky_action' => array(
2558        'type' => 'node',
2559        'description' => t('Make post sticky'),
2560        'configurable' => FALSE,
2561        'behavior' => array('changes_node_property'),
2562        'hooks' => array(
2563          'nodeapi' => array('presave'),
2564          'comment' => array('insert', 'update'),
2565        ),
2566      ),
2567      'node_make_unsticky_action' => array(
2568        'type' => 'node',
2569        'description' => t('Make post unsticky'),
2570        'configurable' => FALSE,
2571        'behavior' => array('changes_node_property'),
2572        'hooks' => array(
2573          'nodeapi' => array('presave'),
2574          'comment' => array('delete', 'insert', 'update'),
2575        ),
2576      ),
2577      'node_promote_action' => array(
2578        'type' => 'node',
2579        'description' => t('Promote post to front page'),
2580        'configurable' => FALSE,
2581        'behavior' => array('changes_node_property'),
2582        'hooks' => array(
2583          'nodeapi' => array('presave'),
2584          'comment' => array('insert', 'update'),
2585        ),
2586      ),
2587      'node_unpromote_action' => array(
2588        'type' => 'node',
2589        'description' => t('Remove post from front page'),
2590        'configurable' => FALSE,
2591        'behavior' => array('changes_node_property'),
2592        'hooks' => array(
2593          'nodeapi' => array('presave'),
2594          'comment' => array('delete', 'insert', 'update'),
2595        ),
2596      ),
2597      'node_assign_owner_action' => array(
2598        'type' => 'node',
2599        'description' => t('Change the author of a post'),
2600        'configurable' => TRUE,
2601        'behavior' => array('changes_node_property'),
2602        'hooks' => array(
2603          'any' => TRUE,
2604          'nodeapi' => array('presave'),
2605          'comment' => array('delete', 'insert', 'update'),
2606        ),
2607      ),
2608      'node_save_action' => array(
2609        'type' => 'node',
2610        'description' => t('Save post'),
2611        'configurable' => FALSE,
2612        'hooks' => array(
2613          'comment' => array('delete', 'insert', 'update'),
2614        ),
2615      ),
2616      'node_unpublish_by_keyword_action' => array(
2617        'type' => 'node',
2618        'description' => t('Unpublish post containing keyword(s)'),
2619        'configurable' => TRUE,
2620        'hooks' => array(
2621          'nodeapi' => array('presave', 'insert', 'update'),
2622        ),
2623      ),
2624    );
2625  }
2626  
2627  /**
2628   * Implementation of a Drupal action.
2629   * Sets the status of a node to 1, meaning published.
2630   */
2631  function node_publish_action(&$node, $context = array()) {
2632    $node->status = 1;
2633    watchdog('action', 'Set @type %title to published.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
2634  }
2635  
2636  /**
2637   * Implementation of a Drupal action.
2638   * Sets the status of a node to 0, meaning unpublished.
2639   */
2640  function node_unpublish_action(&$node, $context = array()) {
2641    $node->status = 0;
2642    watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
2643  }
2644  
2645  /**
2646   * Implementation of a Drupal action.
2647   * Sets the sticky-at-top-of-list property of a node to 1.
2648   */
2649  function node_make_sticky_action(&$node, $context = array()) {
2650    $node->sticky = 1;
2651    watchdog('action', 'Set @type %title to sticky.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
2652  }
2653  
2654  /**
2655   * Implementation of a Drupal action.
2656   * Sets the sticky-at-top-of-list property of a node to 0.
2657   */
2658  function node_make_unsticky_action(&$node, $context = array()) {
2659    $node->sticky = 0;
2660    watchdog('action', 'Set @type %title to unsticky.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
2661  }
2662  
2663  /**
2664   * Implementation of a Drupal action.
2665   * Sets the promote property of a node to 1.
2666   */
2667  function node_promote_action(&$node, $context = array()) {
2668    $node->promote = 1;
2669    watchdog('action', 'Promoted @type %title to front page.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
2670  }
2671  
2672  /**
2673   * Implementation of a Drupal action.
2674   * Sets the promote property of a node to 0.
2675   */
2676  function node_unpromote_action(&$node, $context = array()) {
2677    $node->promote = 0;
2678    watchdog('action', 'Removed @type %title from front page.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
2679  }
2680  
2681  /**
2682   * Implementation of a Drupal action.
2683   * Saves a node.
2684   */
2685  function node_save_action($node) {
2686    node_save($node);
2687    watchdog('action', 'Saved @type %title', array('@type' => node_get_types('name', $node), '%title' => $node->title));
2688  }
2689  
2690  /**
2691   * Implementation of a configurable Drupal action.
2692   * Assigns ownership of a node to a user.
2693   */
2694  function node_assign_owner_action(&$node, $context) {
2695    $node->uid = $context['owner_uid'];
2696    $owner_name = db_result(db_query("SELECT name FROM {users} WHERE uid = %d", $context['owner_uid']));
2697    watchdog('action', 'Changed owner of @type %title to uid %name.', array('@type' => node_get_types('name', $node), '%title' => $node->title, '%name' => $owner_name));
2698  }
2699  
2700  function node_assign_owner_action_form($context) {
2701    $description = t('The username of the user to which you would like to assign ownership.');
2702    $count = db_result(db_query("SELECT COUNT(*) FROM {users}"));
2703    $owner_name = '';
2704    if (isset($context['owner_uid'])) {
2705      $owner_name = db_result(db_query("SELECT name FROM {users} WHERE uid = %d", $context['owner_uid']));
2706    }
2707  
2708    // Use dropdown for fewer than 200 users; textbox for more than that.
2709    if (intval($count) < 200) {
2710      $options = array();
2711      $result = db_query("SELECT uid, name FROM {users} WHERE uid > 0 ORDER BY name");
2712      while ($data = db_fetch_object($result)) {
2713        $options[$data->name] = $data->name;
2714      }
2715      $form['owner_name'] = array(
2716        '#type' => 'select',
2717        '#title' => t('Username'),
2718        '#default_value' => $owner_name,
2719        '#options' => $options,
2720        '#description' => $description,
2721      );
2722    }
2723    else {
2724      $form['owner_name'] = array(
2725        '#type' => 'textfield',
2726        '#title' => t('Username'),
2727        '#default_value' => $owner_name,
2728        '#autocomplete_path' => 'user/autocomplete',
2729        '#size' => '6',
2730        '#maxlength' => '60',
2731        '#description' => $description,
2732      );
2733    }
2734    return $form;
2735  }
2736  
2737  function node_assign_owner_action_validate($form, $form_state) {
2738    $count = db_result(db_query("SELECT COUNT(*) FROM {users} WHERE name = '%s'", $form_state['values']['owner_name']));
2739    if (intval($count) != 1) {
2740      form_set_error('owner_name', t('Please enter a valid username.'));
2741    }
2742  }
2743  
2744  function node_assign_owner_action_submit($form, $form_state) {
2745    // Username can change, so we need to store the ID, not the username.
2746    $uid = db_result(db_query("SELECT uid from {users} WHERE name = '%s'", $form_state['values']['owner_name']));
2747    return array('owner_uid' => $uid);
2748  }
2749  
2750  function node_unpublish_by_keyword_action_form($context) {
2751    $form['keywords'] = array(
2752      '#title' => t('Keywords'),
2753      '#type' => 'textarea',
2754      '#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.'),
2755      '#default_value' => isset($context['keywords']) ? drupal_implode_tags($context['keywords']) : '',
2756    );
2757    return $form;
2758  }
2759  
2760  function node_unpublish_by_keyword_action_submit($form, $form_state) {
2761    return array('keywords' => drupal_explode_tags($form_state['values']['keywords']));
2762  }
2763  
2764  /**
2765   * Implementation of a configurable Drupal action.
2766   * Unpublish a node if it contains a certain string.
2767   *
2768   * @param $context
2769   *   An array providing more information about the context of the call to this action.
2770   * @param $comment
2771   *   A node object.
2772   */
2773  function node_unpublish_by_keyword_action($node, $context) {
2774    foreach ($context['keywords'] as $keyword) {
2775      if (strstr(node_view(drupal_clone($node)), $keyword) || strstr($node->title, $keyword)) {
2776        $node->status = 0;
2777        watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
2778        break;
2779      }
2780    }
2781  }


Generated: Thu Mar 24 11:18:33 2011 Cross-referenced by PHPXref 0.7