[ Index ]

PHP Cross Reference of Drupal 6 (yi-drupal)

title

Body

[close]

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

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


Generated: Mon Jul 9 18:01:44 2012 Cross-referenced by PHPXref 0.7