[ Index ]
 

Code source de Drupal 5.3

Accédez au Source d'autres logiciels libres

Classes | Fonctions | Variables | Constantes | Tables

title

Body

[fermer]

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

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


Généré le : Fri Nov 30 16:20:15 2007 par Balluche grâce à PHPXref 0.7
  Clicky Web Analytics