Drupal PHP Cross Reference Content Management Systems

Source: /modules/node/node.module - 3992 lines - 132746 bytes - Summary - Text - Print

   1  <?php
   2  
   3  /**
   4   * @file
   5   * The core that allows content to be submitted to the site. Modules and
   6   * scripts may programmatically submit nodes using the usual form API pattern.
   7   */
   8  
   9  /**
  10   * Node is not published.
  11   */
  12  define('NODE_NOT_PUBLISHED', 0);
  13  
  14  /**
  15   * Node is published.
  16   */
  17  define('NODE_PUBLISHED', 1);
  18  
  19  /**
  20   * Node is not promoted to front page.
  21   */
  22  define('NODE_NOT_PROMOTED', 0);
  23  
  24  /**
  25   * Node is promoted to front page.
  26   */
  27  define('NODE_PROMOTED', 1);
  28  
  29  /**
  30   * Node is not sticky at top of the page.
  31   */
  32  define('NODE_NOT_STICKY', 0);
  33  
  34  /**
  35   * Node is sticky at top of the page.
  36   */
  37  define('NODE_STICKY', 1);
  38  
  39  /**
  40   * Nodes changed before this time are always marked as read.
  41   *
  42   * Nodes changed after this time may be marked new, updated, or read, depending
  43   * on their state for the current user. Defaults to 30 days ago.
  44   */
  45  define('NODE_NEW_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60);
  46  
  47  /**
  48   * Modules should return this value from hook_node_access() to allow access to a node.
  49   */
  50  define('NODE_ACCESS_ALLOW', 'allow');
  51  
  52  /**
  53   * Modules should return this value from hook_node_access() to deny access to a node.
  54   */
  55  define('NODE_ACCESS_DENY', 'deny');
  56  
  57  /**
  58   * Modules should return this value from hook_node_access() to not affect node access.
  59   */
  60  define('NODE_ACCESS_IGNORE', NULL);
  61  
  62  /**
  63   * Implements hook_help().
  64   */
  65  function node_help($path, $arg) {
  66    // Remind site administrators about the {node_access} table being flagged
  67    // for rebuild. We don't need to issue the message on the confirm form, or
  68    // while the rebuild is being processed.
  69    if ($path != 'admin/reports/status/rebuild' && $path != 'batch' && strpos($path, '#') === FALSE
  70        && user_access('access administration pages') && node_access_needs_rebuild()) {
  71      if ($path == 'admin/reports/status') {
  72        $message = t('The content access permissions need to be rebuilt.');
  73      }
  74      else {
  75        $message = t('The content access permissions need to be rebuilt. <a href="@node_access_rebuild">Rebuild permissions</a>.', array('@node_access_rebuild' => url('admin/reports/status/rebuild')));
  76      }
  77      drupal_set_message($message, 'error');
  78    }
  79  
  80    switch ($path) {
  81      case 'admin/help#node':
  82        $output = '';
  83        $output .= '<h3>' . t('About') . '</h3>';
  84        $output .= '<p>' . t('The Node module manages the creation, editing, deletion, settings, and display of the main site content. Content items managed by the Node module are typically displayed as pages on your site, and include a title, some meta-data (author, creation time, content type, etc.), and optional fields containing text or other data (fields are managed by the <a href="@field">Field module</a>). For more information, see the online handbook entry for <a href="@node">Node module</a>.', array('@node' => 'http://drupal.org/documentation/modules/node', '@field' => url('admin/help/field'))) . '</p>';
  85        $output .= '<h3>' . t('Uses') . '</h3>';
  86        $output .= '<dl>';
  87        $output .= '<dt>' . t('Creating content') . '</dt>';
  88        $output .= '<dd>' . t('When new content is created, the Node module records basic information about the content, including the author, date of creation, and the <a href="@content-type">Content type</a>. It also manages the <em>publishing options</em>, which define whether or not the content is published, promoted to the front page of the site, and/or sticky at the top of content lists. Default settings can be configured for each <a href="@content-type">type of content</a> on your site.', array('@content-type' => url('admin/structure/types'))) . '</dd>';
  89        $output .= '<dt>' . t('Creating custom content types') . '</dt>';
  90        $output .= '<dd>' . t('The Node module gives users with the <em>Administer content types</em> permission the ability to <a href="@content-new">create new content types</a> in addition to the default ones already configured. Creating custom content types allows you the flexibility to add <a href="@field">fields</a> and configure default settings that suit the differing needs of various site content.', array('@content-new' => url('admin/structure/types/add'), '@field' => url('admin/help/field'))) . '</dd>';
  91        $output .= '<dt>' . t('Administering content') . '</dt>';
  92        $output .= '<dd>' . t('The <a href="@content">Content administration page</a> allows you to review and bulk manage your site content.', array('@content' => url('admin/content'))) . '</dd>';
  93        $output .= '<dt>' . t('Creating revisions') . '</dt>';
  94        $output .= '<dd>' . t('The Node module also enables you to create multiple versions of any content, and revert to older versions using the <em>Revision information</em> settings.') . '</dd>';
  95        $output .= '<dt>' . t('User permissions') . '</dt>';
  96        $output .= '<dd>' . t('The Node module makes a number of permissions available for each content type, which can be set by role on the <a href="@permissions">permissions page</a>.', array('@permissions' => url('admin/people/permissions', array('fragment' => 'module-node')))) . '</dd>';
  97        $output .= '</dl>';
  98        return $output;
  99  
 100      case 'admin/structure/types/add':
 101        return '<p>' . t('Individual content types can have different fields, behaviors, and permissions assigned to them.') . '</p>';
 102  
 103      case 'admin/structure/types/manage/%/display':
 104        return '<p>' . t('Content items can be displayed using different view modes: Teaser, Full content, Print, RSS, etc. <em>Teaser</em> is a short format that is typically used in lists of multiple content items. <em>Full content</em> is typically used when the content is displayed on its own page.') . '</p>' .
 105          '<p>' . t('Here, you can define which fields are shown and hidden when %type content is displayed in each view mode, and define how the fields are displayed in each view mode.', array('%type' => node_type_get_name($arg[4]))) . '</p>';
 106  
 107      case 'node/%/revisions':
 108        return '<p>' . t('Revisions allow you to track differences between multiple versions of your content, and revert back to older versions.') . '</p>';
 109  
 110      case 'node/%/edit':
 111        $node = node_load($arg[1]);
 112        $type = node_type_get_type($node);
 113        return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) . '</p>' : '');
 114    }
 115  
 116    if ($arg[0] == 'node' && $arg[1] == 'add' && $arg[2]) {
 117      $type = node_type_get_type(str_replace('-', '_', $arg[2]));
 118      return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) . '</p>' : '');
 119    }
 120  }
 121  
 122  /**
 123   * Implements hook_theme().
 124   */
 125  function node_theme() {
 126    return array(
 127      'node' => array(
 128        'render element' => 'elements',
 129        'template' => 'node',
 130      ),
 131      'node_search_admin' => array(
 132        'render element' => 'form',
 133      ),
 134      'node_add_list' => array(
 135        'variables' => array('content' => NULL),
 136        'file' => 'node.pages.inc',
 137      ),
 138      'node_preview' => array(
 139        'variables' => array('node' => NULL),
 140        'file' => 'node.pages.inc',
 141      ),
 142      'node_admin_overview' => array(
 143        'variables' => array('name' => NULL, 'type' => NULL),
 144      ),
 145      'node_recent_block' => array(
 146        'variables' => array('nodes' => NULL),
 147      ),
 148      'node_recent_content' => array(
 149        'variables' => array('node' => NULL),
 150      ),
 151    );
 152  }
 153  
 154  /**
 155   * Implements hook_cron().
 156   */
 157  function node_cron() {
 158    db_delete('history')
 159      ->condition('timestamp', NODE_NEW_LIMIT, '<')
 160      ->execute();
 161  }
 162  
 163  /**
 164   * Implements hook_entity_info().
 165   */
 166  function node_entity_info() {
 167    $return = array(
 168      'node' => array(
 169        'label' => t('Node'),
 170        'controller class' => 'NodeController',
 171        'base table' => 'node',
 172        'revision table' => 'node_revision',
 173        'uri callback' => 'node_uri',
 174        'fieldable' => TRUE,
 175        'entity keys' => array(
 176          'id' => 'nid',
 177          'revision' => 'vid',
 178          'bundle' => 'type',
 179          'label' => 'title',
 180          'language' => 'language',
 181        ),
 182        'bundle keys' => array(
 183          'bundle' => 'type',
 184        ),
 185        'bundles' => array(),
 186        'view modes' => array(
 187          'full' => array(
 188            'label' => t('Full content'),
 189            'custom settings' => FALSE,
 190          ),
 191          'teaser' => array(
 192            'label' => t('Teaser'),
 193            'custom settings' => TRUE,
 194          ),
 195          'rss' => array(
 196            'label' => t('RSS'),
 197            'custom settings' => FALSE,
 198          ),
 199        ),
 200      ),
 201    );
 202  
 203    // Search integration is provided by node.module, so search-related
 204    // view modes for nodes are defined here and not in search.module.
 205    if (module_exists('search')) {
 206      $return['node']['view modes'] += array(
 207        'search_index' => array(
 208          'label' => t('Search index'),
 209          'custom settings' => FALSE,
 210        ),
 211        'search_result' => array(
 212          'label' => t('Search result'),
 213          'custom settings' => FALSE,
 214        ),
 215      );
 216    }
 217  
 218    // Bundles must provide a human readable name so we can create help and error
 219    // messages, and the path to attach Field admin pages to.
 220    foreach (node_type_get_names() as $type => $name) {
 221      $return['node']['bundles'][$type] = array(
 222        'label' => $name,
 223        'admin' => array(
 224          'path' => 'admin/structure/types/manage/%node_type',
 225          'real path' => 'admin/structure/types/manage/' . str_replace('_', '-', $type),
 226          'bundle argument' => 4,
 227          'access arguments' => array('administer content types'),
 228        ),
 229      );
 230    }
 231  
 232    return $return;
 233  }
 234  
 235  /**
 236   * Implements hook_field_display_ENTITY_TYPE_alter().
 237   */
 238  function node_field_display_node_alter(&$display, $context) {
 239    // Hide field labels in search index.
 240    if ($context['view_mode'] == 'search_index') {
 241      $display['label'] = 'hidden';
 242    }
 243  }
 244  
 245  /**
 246   * Entity URI callback.
 247   */
 248  function node_uri($node) {
 249    return array(
 250      'path' => 'node/' . $node->nid,
 251    );
 252  }
 253  
 254  /**
 255   * Implements hook_admin_paths().
 256   */
 257  function node_admin_paths() {
 258    if (variable_get('node_admin_theme')) {
 259      $paths = array(
 260        'node/*/edit' => TRUE,
 261        'node/*/delete' => TRUE,
 262        'node/*/revisions' => TRUE,
 263        'node/*/revisions/*/revert' => TRUE,
 264        'node/*/revisions/*/delete' => TRUE,
 265        'node/add' => TRUE,
 266        'node/add/*' => TRUE,
 267      );
 268      return $paths;
 269    }
 270  }
 271  
 272  /**
 273   * Gathers a listing of links to nodes.
 274   *
 275   * @param $result
 276   *   A database result object from a query to fetch node entities. If your
 277   *   query joins the {node_comment_statistics} table so that the comment_count
 278   *   field is available, a title attribute will be added to show the number of
 279   *   comments.
 280   * @param $title
 281   *   A heading for the resulting list.
 282   *
 283   * @return
 284   *   A renderable array containing a list of linked node titles fetched from
 285   *   $result, or FALSE if there are no rows in $result.
 286   */
 287  function node_title_list($result, $title = NULL) {
 288    $items = array();
 289    $num_rows = FALSE;
 290    foreach ($result as $node) {
 291      $items[] = l($node->title, 'node/' . $node->nid, !empty($node->comment_count) ? array('attributes' => array('title' => format_plural($node->comment_count, '1 comment', '@count comments'))) : array());
 292      $num_rows = TRUE;
 293    }
 294  
 295    return $num_rows ? array('#theme' => 'item_list__node', '#items' => $items, '#title' => $title) : FALSE;
 296  }
 297  
 298  /**
 299   * Update the 'last viewed' timestamp of the specified node for current user.
 300   *
 301   * @param $node
 302   *   A node object.
 303   */
 304  function node_tag_new($node) {
 305    global $user;
 306    if ($user->uid) {
 307      db_merge('history')
 308        ->key(array(
 309          'uid' => $user->uid,
 310          'nid' => $node->nid,
 311        ))
 312        ->fields(array('timestamp' => REQUEST_TIME))
 313        ->execute();
 314     }
 315  }
 316  
 317  /**
 318   * Retrieves the timestamp at which the current user last viewed the
 319   * specified node.
 320   */
 321  function node_last_viewed($nid) {
 322    global $user;
 323    $history = &drupal_static(__FUNCTION__, array());
 324  
 325    if (!isset($history[$nid])) {
 326      $history[$nid] = db_query("SELECT timestamp FROM {history} WHERE uid = :uid AND nid = :nid", array(':uid' => $user->uid, ':nid' => $nid))->fetchObject();
 327    }
 328  
 329    return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp : 0);
 330  }
 331  
 332  /**
 333   * Decide on the type of marker to be displayed for a given node.
 334   *
 335   * @param $nid
 336   *   Node ID whose history supplies the "last viewed" timestamp.
 337   * @param $timestamp
 338   *   Time which is compared against node's "last viewed" timestamp.
 339   * @return
 340   *   One of the MARK constants.
 341   */
 342  function node_mark($nid, $timestamp) {
 343    global $user;
 344    $cache = &drupal_static(__FUNCTION__, array());
 345  
 346    if (!$user->uid) {
 347      return MARK_READ;
 348    }
 349    if (!isset($cache[$nid])) {
 350      $cache[$nid] = node_last_viewed($nid);
 351    }
 352    if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) {
 353      return MARK_NEW;
 354    }
 355    elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) {
 356      return MARK_UPDATED;
 357    }
 358    return MARK_READ;
 359  }
 360  
 361  /**
 362   * Extract the type name.
 363   *
 364   * @param $node
 365   *   Either a string or object, containing the node type information.
 366   *
 367   * @return
 368   *   Node type of the passed-in data.
 369   */
 370  function _node_extract_type($node) {
 371    return is_object($node) ? $node->type : $node;
 372  }
 373  
 374  /**
 375   * Returns a list of all the available node types.
 376   *
 377   * This list can include types that are queued for addition or deletion.
 378   * See _node_types_build() for details.
 379   *
 380   * @return
 381   *   An array of node types, as objects, keyed by the type.
 382   *
 383   * @see node_type_get_type()
 384   */
 385  function node_type_get_types() {
 386    return _node_types_build()->types;
 387  }
 388  
 389  /**
 390   * Returns the node type of the passed node or node type string.
 391   *
 392   * @param $node
 393   *   A node object or string that indicates the node type to return.
 394   *
 395   * @return
 396   *   A single node type, as an object, or FALSE if the node type is not found.
 397   *   The node type is an object containing fields from hook_node_info() return
 398   *   values, as well as the field 'type' (the machine-readable type) and other
 399   *   fields used internally and defined in _node_types_build(),
 400   *   hook_node_info(), and node_type_set_defaults().
 401   */
 402  function node_type_get_type($node) {
 403    $type = _node_extract_type($node);
 404    $types = _node_types_build()->types;
 405    return isset($types[$type]) ? $types[$type] : FALSE;
 406  }
 407  
 408  /**
 409   * Returns the node type base of the passed node or node type string.
 410   *
 411   * The base indicates which module implements this node type and is used to
 412   * execute node-type-specific hooks. For types defined in the user interface
 413   * and managed by node.module, the base is 'node_content'.
 414   *
 415   * @param $node
 416   *   A node object or string that indicates the node type to return.
 417   *
 418   * @return
 419   *   The node type base or FALSE if the node type is not found.
 420   *
 421   * @see node_invoke()
 422   */
 423  function node_type_get_base($node) {
 424    $type = _node_extract_type($node);
 425    $types = _node_types_build()->types;
 426    return isset($types[$type]) && isset($types[$type]->base) ? $types[$type]->base : FALSE;
 427  }
 428  
 429  /**
 430   * Returns a list of available node type names.
 431   *
 432   * This list can include types that are queued for addition or deletion.
 433   * See _node_types_build() for details.
 434   *
 435   * @return
 436   *   An array of node type names, keyed by the type.
 437   */
 438  function node_type_get_names() {
 439    return _node_types_build()->names;
 440  }
 441  
 442  /**
 443   * Returns the node type name of the passed node or node type string.
 444   *
 445   * @param $node
 446   *   A node object or string that indicates the node type to return.
 447   *
 448   * @return
 449   *   The node type name or FALSE if the node type is not found.
 450   */
 451  function node_type_get_name($node) {
 452    $type = _node_extract_type($node);
 453    $types = _node_types_build()->names;
 454    return isset($types[$type]) ? $types[$type] : FALSE;
 455  }
 456  
 457  /**
 458   * Updates the database cache of node types.
 459   *
 460   * All new module-defined node types are saved to the database via a call to
 461   * node_type_save(), and obsolete ones are deleted via a call to
 462   * node_type_delete(). See _node_types_build() for an explanation of the new
 463   * and obsolete types.
 464   */
 465  function node_types_rebuild() {
 466    _node_types_build(TRUE);
 467  }
 468  
 469  /**
 470   * Menu argument loader: loads a node type by string.
 471   *
 472   * @param $name
 473   *   The machine-readable name of a node type to load, where '_' is replaced
 474   *   with '-'.
 475   *
 476   * @return
 477   *   A node type object or FALSE if $name does not exist.
 478   */
 479  function node_type_load($name) {
 480    return node_type_get_type(strtr($name, array('-' => '_')));
 481  }
 482  
 483  /**
 484   * Saves a node type to the database.
 485   *
 486   * @param $info
 487   *   The node type to save, as an object.
 488   *
 489   * @return
 490   *   Status flag indicating outcome of the operation.
 491   */
 492  function node_type_save($info) {
 493    $existing_type = !empty($info->old_type) ? $info->old_type : $info->type;
 494    $is_existing = (bool) db_query_range('SELECT 1 FROM {node_type} WHERE type = :type', 0, 1, array(':type' => $existing_type))->fetchField();
 495    $type = node_type_set_defaults($info);
 496  
 497    $fields = array(
 498      'type' => (string) $type->type,
 499      'name' => (string) $type->name,
 500      'base' => (string) $type->base,
 501      'has_title' => (int) $type->has_title,
 502      'title_label' => (string) $type->title_label,
 503      'description' => (string) $type->description,
 504      'help' => (string) $type->help,
 505      'custom' => (int) $type->custom,
 506      'modified' => (int) $type->modified,
 507      'locked' => (int) $type->locked,
 508      'disabled' => (int) $type->disabled,
 509      'module' => $type->module,
 510    );
 511  
 512    if ($is_existing) {
 513      db_update('node_type')
 514        ->fields($fields)
 515        ->condition('type', $existing_type)
 516        ->execute();
 517  
 518      if (!empty($type->old_type) && $type->old_type != $type->type) {
 519        field_attach_rename_bundle('node', $type->old_type, $type->type);
 520      }
 521      module_invoke_all('node_type_update', $type);
 522      $status = SAVED_UPDATED;
 523    }
 524    else {
 525      $fields['orig_type'] = (string) $type->orig_type;
 526      db_insert('node_type')
 527        ->fields($fields)
 528        ->execute();
 529  
 530      field_attach_create_bundle('node', $type->type);
 531  
 532      module_invoke_all('node_type_insert', $type);
 533      $status = SAVED_NEW;
 534    }
 535  
 536    // Clear the node type cache.
 537    node_type_cache_reset();
 538  
 539    return $status;
 540  }
 541  
 542  /**
 543   * Add default body field to a node type.
 544   *
 545   * @param $type
 546   *   A node type object.
 547   * @param $label
 548   *   The label for the body instance.
 549   *
 550   * @return
 551   *   Body field instance.
 552   */
 553  function node_add_body_field($type, $label = 'Body') {
 554     // Add or remove the body field, as needed.
 555    $field = field_info_field('body');
 556    $instance = field_info_instance('node', 'body', $type->type);
 557    if (empty($field)) {
 558      $field = array(
 559        'field_name' => 'body',
 560        'type' => 'text_with_summary',
 561        'entity_types' => array('node'),
 562      );
 563      $field = field_create_field($field);
 564    }
 565    if (empty($instance)) {
 566      $instance = array(
 567        'field_name' => 'body',
 568        'entity_type' => 'node',
 569        'bundle' => $type->type,
 570        'label' => $label,
 571        'widget' => array('type' => 'text_textarea_with_summary'),
 572        'settings' => array('display_summary' => TRUE),
 573        'display' => array(
 574          'default' => array(
 575            'label' => 'hidden',
 576            'type' => 'text_default',
 577          ),
 578          'teaser' => array(
 579            'label' => 'hidden',
 580            'type' => 'text_summary_or_trimmed',
 581          ),
 582        ),
 583      );
 584      $instance = field_create_instance($instance);
 585    }
 586    return $instance;
 587  }
 588  
 589  /**
 590   * Implements hook_field_extra_fields().
 591   */
 592  function node_field_extra_fields() {
 593    $extra = array();
 594  
 595    foreach (node_type_get_types() as $type) {
 596      if ($type->has_title) {
 597        $extra['node'][$type->type] = array(
 598          'form' => array(
 599            'title' => array(
 600              'label' => $type->title_label,
 601              'description' => t('Node module element'),
 602              'weight' => -5,
 603            ),
 604          ),
 605        );
 606      }
 607    }
 608  
 609    return $extra;
 610  }
 611  
 612  /**
 613   * Deletes a node type from the database.
 614   *
 615   * @param $type
 616   *   The machine-readable name of the node type to be deleted.
 617   */
 618  function node_type_delete($type) {
 619    $info = node_type_get_type($type);
 620    db_delete('node_type')
 621      ->condition('type', $type)
 622      ->execute();
 623    field_attach_delete_bundle('node', $type);
 624    module_invoke_all('node_type_delete', $info);
 625  
 626    // Clear the node type cache.
 627    node_type_cache_reset();
 628  }
 629  
 630  /**
 631   * Updates all nodes of one type to be of another type.
 632   *
 633   * @param $old_type
 634   *   The current node type of the nodes.
 635   * @param $type
 636   *   The new node type of the nodes.
 637   *
 638   * @return
 639   *   The number of nodes whose node type field was modified.
 640   */
 641  function node_type_update_nodes($old_type, $type) {
 642    return db_update('node')
 643      ->fields(array('type' => $type))
 644      ->condition('type', $old_type)
 645      ->execute();
 646  }
 647  
 648  /**
 649   * Builds and returns the list of available node types.
 650   *
 651   * The list of types is built by invoking hook_node_info() on all modules and
 652   * comparing this information with the node types in the {node_type} table.
 653   * These two information sources are not synchronized during module installation
 654   * until node_types_rebuild() is called.
 655   *
 656   * @param $rebuild
 657   *  TRUE to rebuild node types. Equivalent to calling node_types_rebuild().
 658   * @return
 659   *   An object with two properties:
 660   *   - names: Associative array of the names of node types, keyed by the type.
 661   *   - types: Associative array of node type objects, keyed by the type.
 662   *   Both of these arrays will include new types that have been defined by
 663   *   hook_node_info() implementations but not yet saved in the {node_type}
 664   *   table. These are indicated in the type object by $type->is_new being set
 665   *   to the value 1. These arrays will also include obsolete types: types that
 666   *   were previously defined by modules that have now been disabled, or for
 667   *   whatever reason are no longer being defined in hook_node_info()
 668   *   implementations, but are still in the database. These are indicated in the
 669   *   type object by $type->disabled being set to TRUE.
 670   */
 671  function _node_types_build($rebuild = FALSE) {
 672    $cid = 'node_types:' . $GLOBALS['language']->language;
 673  
 674    if (!$rebuild) {
 675      $_node_types = &drupal_static(__FUNCTION__);
 676      if (isset($_node_types)) {
 677        return $_node_types;
 678      }
 679      if ($cache = cache_get($cid)) {
 680        $_node_types = $cache->data;
 681        return $_node_types;
 682      }
 683    }
 684  
 685    $_node_types = (object) array('types' => array(), 'names' => array());
 686  
 687    foreach (module_implements('node_info') as $module) {
 688      $info_array = module_invoke($module, 'node_info');
 689      foreach ($info_array as $type => $info) {
 690        $info['type'] = $type;
 691        $_node_types->types[$type] = node_type_set_defaults($info);
 692        $_node_types->types[$type]->module = $module;
 693        $_node_types->names[$type] = $info['name'];
 694      }
 695    }
 696    $query = db_select('node_type', 'nt')
 697      ->addTag('translatable')
 698      ->addTag('node_type_access')
 699      ->fields('nt')
 700      ->orderBy('nt.type', 'ASC');
 701    if (!$rebuild) {
 702      $query->condition('disabled', 0);
 703    }
 704    foreach ($query->execute() as $type_object) {
 705      $type_db = $type_object->type;
 706      // Original disabled value.
 707      $disabled = $type_object->disabled;
 708      // Check for node types from disabled modules and mark their types for removal.
 709      // Types defined by the node module in the database (rather than by a separate
 710      // module using hook_node_info) have a base value of 'node_content'. The isset()
 711      // check prevents errors on old (pre-Drupal 7) databases.
 712      if (isset($type_object->base) && $type_object->base != 'node_content' && empty($_node_types->types[$type_db])) {
 713        $type_object->disabled = TRUE;
 714      }
 715      if (isset($_node_types->types[$type_db])) {
 716        $type_object->disabled = FALSE;
 717      }
 718      if (!isset($_node_types->types[$type_db]) || $type_object->modified) {
 719        $_node_types->types[$type_db] = $type_object;
 720        $_node_types->names[$type_db] = $type_object->name;
 721  
 722        if ($type_db != $type_object->orig_type) {
 723          unset($_node_types->types[$type_object->orig_type]);
 724          unset($_node_types->names[$type_object->orig_type]);
 725        }
 726      }
 727      $_node_types->types[$type_db]->disabled = $type_object->disabled;
 728      $_node_types->types[$type_db]->disabled_changed = $disabled != $type_object->disabled;
 729    }
 730  
 731    if ($rebuild) {
 732      foreach ($_node_types->types as $type => $type_object) {
 733        if (!empty($type_object->is_new) || !empty($type_object->disabled_changed)) {
 734          node_type_save($type_object);
 735        }
 736      }
 737    }
 738  
 739    asort($_node_types->names);
 740  
 741    cache_set($cid, $_node_types);
 742  
 743    return $_node_types;
 744  }
 745  
 746  /**
 747   * Clears the node type cache.
 748   */
 749  function node_type_cache_reset() {
 750    cache_clear_all('node_types:', 'cache', TRUE);
 751    drupal_static_reset('_node_types_build');
 752  }
 753  
 754  /**
 755   * Sets the default values for a node type.
 756   *
 757   * The defaults are appropriate for a type defined through hook_node_info(),
 758   * since 'custom' is TRUE for types defined in the user interface, and FALSE
 759   * for types defined by modules. (The 'custom' flag prevents types from being
 760   * deleted through the user interface.) Also, the default for 'locked' is TRUE,
 761   * which prevents users from changing the machine name of the type.
 762   *
 763   * @param $info
 764   *   An object or array containing values to override the defaults. See
 765   *   hook_node_info() for details on what the array elements mean.
 766   *
 767   * @return
 768   *   A node type object, with missing values in $info set to their defaults.
 769   */
 770  function node_type_set_defaults($info = array()) {
 771    $info = (array) $info;
 772    $new_type = $info + array(
 773      'type' => '',
 774      'name' => '',
 775      'base' => '',
 776      'description' => '',
 777      'help' => '',
 778      'custom' => 0,
 779      'modified' => 0,
 780      'locked' => 1,
 781      'disabled' => 0,
 782      'is_new' => 1,
 783      'has_title' => 1,
 784      'title_label' => 'Title',
 785    );
 786    $new_type = (object) $new_type;
 787  
 788    // If the type has no title, set an empty label.
 789    if (!$new_type->has_title) {
 790      $new_type->title_label = '';
 791    }
 792    if (empty($new_type->module)) {
 793      $new_type->module = $new_type->base == 'node_content' ? 'node' : '';
 794    }
 795    $new_type->orig_type = isset($info['type']) ? $info['type'] : '';
 796  
 797    return $new_type;
 798  }
 799  
 800  /**
 801   * Implements hook_rdf_mapping().
 802   */
 803  function node_rdf_mapping() {
 804    return array(
 805      array(
 806        'type' => 'node',
 807        'bundle' => RDF_DEFAULT_BUNDLE,
 808        'mapping' => array(
 809          'rdftype' => array('sioc:Item', 'foaf:Document'),
 810          'title' => array(
 811            'predicates' => array('dc:title'),
 812          ),
 813          'created' => array(
 814            'predicates' => array('dc:date', 'dc:created'),
 815            'datatype' => 'xsd:dateTime',
 816            'callback' => 'date_iso8601',
 817          ),
 818          'changed' => array(
 819            'predicates' => array('dc:modified'),
 820            'datatype' => 'xsd:dateTime',
 821            'callback' => 'date_iso8601',
 822          ),
 823          'body' => array(
 824            'predicates' => array('content:encoded'),
 825          ),
 826          'uid' => array(
 827            'predicates' => array('sioc:has_creator'),
 828            'type' => 'rel',
 829          ),
 830          'name' => array(
 831            'predicates' => array('foaf:name'),
 832          ),
 833          'comment_count' => array(
 834            'predicates' => array('sioc:num_replies'),
 835            'datatype' => 'xsd:integer',
 836          ),
 837          'last_activity' => array(
 838            'predicates' => array('sioc:last_activity_date'),
 839            'datatype' => 'xsd:dateTime',
 840            'callback' => 'date_iso8601',
 841          ),
 842        ),
 843      ),
 844    );
 845  }
 846  
 847  /**
 848   * Determine whether a node hook exists.
 849   *
 850   * @param $node
 851   *   A node object or a string containing the node type.
 852   * @param $hook
 853   *   A string containing the name of the hook.
 854   * @return
 855   *   TRUE if the $hook exists in the node type of $node.
 856   */
 857  function node_hook($node, $hook) {
 858    $base = node_type_get_base($node);
 859    return module_hook($base, $hook);
 860  }
 861  
 862  /**
 863   * Invoke a node hook.
 864   *
 865   * @param $node
 866   *   A node object or a string containing the node type.
 867   * @param $hook
 868   *   A string containing the name of the hook.
 869   * @param $a2, $a3, $a4
 870   *   Arguments to pass on to the hook, after the $node argument.
 871   * @return
 872   *   The returned value of the invoked hook.
 873   */
 874  function node_invoke($node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
 875    if (node_hook($node, $hook)) {
 876      $base = node_type_get_base($node);
 877      $function = $base . '_' . $hook;
 878      return ($function($node, $a2, $a3, $a4));
 879    }
 880  }
 881  
 882  /**
 883   * Load node entities from the database.
 884   *
 885   * This function should be used whenever you need to load more than one node
 886   * from the database. Nodes are loaded into memory and will not require
 887   * database access if loaded again during the same page request.
 888   *
 889   * @see entity_load()
 890   * @see EntityFieldQuery
 891   *
 892   * @param $nids
 893   *   An array of node IDs.
 894   * @param $conditions
 895   *   (deprecated) An associative array of conditions on the {node}
 896   *   table, where the keys are the database fields and the values are the
 897   *   values those fields must have. Instead, it is preferable to use
 898   *   EntityFieldQuery to retrieve a list of entity IDs loadable by
 899   *   this function.
 900   * @param $reset
 901   *   Whether to reset the internal node_load cache.
 902   *
 903   * @return
 904   *   An array of node objects indexed by nid.
 905   *
 906   * @todo Remove $conditions in Drupal 8.
 907   */
 908  function node_load_multiple($nids = array(), $conditions = array(), $reset = FALSE) {
 909    return entity_load('node', $nids, $conditions, $reset);
 910  }
 911  
 912  /**
 913   * Load a node object from the database.
 914   *
 915   * @param $nid
 916   *   The node ID.
 917   * @param $vid
 918   *   The revision ID.
 919   * @param $reset
 920   *   Whether to reset the node_load_multiple cache.
 921   *
 922   * @return
 923   *   A fully-populated node object, or FALSE if the node is not found.
 924   */
 925  function node_load($nid = NULL, $vid = NULL, $reset = FALSE) {
 926    $nids = (isset($nid) ? array($nid) : array());
 927    $conditions = (isset($vid) ? array('vid' => $vid) : array());
 928    $node = node_load_multiple($nids, $conditions, $reset);
 929    return $node ? reset($node) : FALSE;
 930  }
 931  
 932  /**
 933   * Prepares a node object for editing.
 934   *
 935   * Fills in a few default values, and then invokes hook_prepare() on the node
 936   * type module, and hook_node_prepare() on all modules.
 937   */
 938  function node_object_prepare($node) {
 939    // Set up default values, if required.
 940    $node_options = variable_get('node_options_' . $node->type, array('status', 'promote'));
 941    // If this is a new node, fill in the default values.
 942    if (!isset($node->nid) || isset($node->is_new)) {
 943      foreach (array('status', 'promote', 'sticky') as $key) {
 944        // Multistep node forms might have filled in something already.
 945        if (!isset($node->$key)) {
 946          $node->$key = (int) in_array($key, $node_options);
 947        }
 948      }
 949      global $user;
 950      $node->uid = $user->uid;
 951      $node->created = REQUEST_TIME;
 952    }
 953    else {
 954      $node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O');
 955      // Remove the log message from the original node object.
 956      $node->log = NULL;
 957    }
 958    // Always use the default revision setting.
 959    $node->revision = in_array('revision', $node_options);
 960  
 961    node_invoke($node, 'prepare');
 962    module_invoke_all('node_prepare', $node);
 963  }
 964  
 965  /**
 966   * Perform validation checks on the given node.
 967   */
 968  function node_validate($node, $form, &$form_state) {
 969    $type = node_type_get_type($node);
 970  
 971    if (isset($node->nid) && (node_last_changed($node->nid) > $node->changed)) {
 972      form_set_error('changed', t('The content on this page has either been modified by another user, or you have already submitted modifications using this form. As a result, your changes cannot be saved.'));
 973    }
 974  
 975    // Validate the "authored by" field.
 976    if (!empty($node->name) && !($account = user_load_by_name($node->name))) {
 977      // The use of empty() is mandatory in the context of usernames
 978      // as the empty string denotes the anonymous user. In case we
 979      // are dealing with an anonymous user we set the user ID to 0.
 980      form_set_error('name', t('The username %name does not exist.', array('%name' => $node->name)));
 981    }
 982  
 983    // Validate the "authored on" field.
 984    if (!empty($node->date) && strtotime($node->date) === FALSE) {
 985      form_set_error('date', t('You have to specify a valid date.'));
 986    }
 987  
 988    // Invoke hook_validate() for node type specific validation and
 989    // hook_node_validate() for miscellaneous validation needed by modules. Can't
 990    // use node_invoke() or module_invoke_all(), because $form_state must be
 991    // receivable by reference.
 992    $function = node_type_get_base($node) . '_validate';
 993    if (function_exists($function)) {
 994      $function($node, $form, $form_state);
 995    }
 996    foreach (module_implements('node_validate') as $module) {
 997      $function = $module . '_node_validate';
 998      $function($node, $form, $form_state);
 999    }
1000  }
1001  
1002  /**
1003   * Prepare node for saving by populating author and creation date.
1004   */
1005  function node_submit($node) {
1006    // A user might assign the node author by entering a user name in the node
1007    // form, which we then need to translate to a user ID.
1008    if (isset($node->name)) {
1009      if ($account = user_load_by_name($node->name)) {
1010        $node->uid = $account->uid;
1011      }
1012      else {
1013        $node->uid = 0;
1014      }
1015    }
1016  
1017    $node->created = !empty($node->date) ? strtotime($node->date) : REQUEST_TIME;
1018    $node->validated = TRUE;
1019  
1020    return $node;
1021  }
1022  
1023  /**
1024   * Save changes to a node or add a new node.
1025   *
1026   * @param $node
1027   *   The $node object to be saved. If $node->nid is
1028   *   omitted (or $node->is_new is TRUE), a new node will be added.
1029   */
1030  function node_save($node) {
1031    $transaction = db_transaction();
1032  
1033    try {
1034      // Load the stored entity, if any.
1035      if (!empty($node->nid) && !isset($node->original)) {
1036        $node->original = entity_load_unchanged('node', $node->nid);
1037      }
1038  
1039      field_attach_presave('node', $node);
1040      global $user;
1041  
1042      // Determine if we will be inserting a new node.
1043      if (!isset($node->is_new)) {
1044        $node->is_new = empty($node->nid);
1045      }
1046  
1047      // Set the timestamp fields.
1048      if (empty($node->created)) {
1049        $node->created = REQUEST_TIME;
1050      }
1051      // The changed timestamp is always updated for bookkeeping purposes,
1052      // for example: revisions, searching, etc.
1053      $node->changed = REQUEST_TIME;
1054  
1055      $node->timestamp = REQUEST_TIME;
1056      $update_node = TRUE;
1057  
1058      // Let modules modify the node before it is saved to the database.
1059      module_invoke_all('node_presave', $node);
1060      module_invoke_all('entity_presave', $node, 'node');
1061  
1062      if ($node->is_new || !empty($node->revision)) {
1063        // When inserting either a new node or a new node revision, $node->log
1064        // must be set because {node_revision}.log is a text column and therefore
1065        // cannot have a default value. However, it might not be set at this
1066        // point (for example, if the user submitting a node form does not have
1067        // permission to create revisions), so we ensure that it is at least an
1068        // empty string in that case.
1069        // @todo: Make the {node_revision}.log column nullable so that we can
1070        // remove this check.
1071        if (!isset($node->log)) {
1072          $node->log = '';
1073        }
1074      }
1075      elseif (!isset($node->log) || $node->log === '') {
1076        // If we are updating an existing node without adding a new revision, we
1077        // need to make sure $node->log is unset whenever it is empty. As long as
1078        // $node->log is unset, drupal_write_record() will not attempt to update
1079        // the existing database column when re-saving the revision; therefore,
1080        // this code allows us to avoid clobbering an existing log entry with an
1081        // empty one.
1082        unset($node->log);
1083      }
1084  
1085      // When saving a new node revision, unset any existing $node->vid so as to
1086      // ensure that a new revision will actually be created, then store the old
1087      // revision ID in a separate property for use by node hook implementations.
1088      if (!$node->is_new && !empty($node->revision) && $node->vid) {
1089        $node->old_vid = $node->vid;
1090        unset($node->vid);
1091      }
1092  
1093      // Save the node and node revision.
1094      if ($node->is_new) {
1095        // For new nodes, save new records for both the node itself and the node
1096        // revision.
1097        drupal_write_record('node', $node);
1098        _node_save_revision($node, $user->uid);
1099        $op = 'insert';
1100      }
1101      else {
1102        // For existing nodes, update the node record which matches the value of
1103        // $node->nid.
1104        drupal_write_record('node', $node, 'nid');
1105        // Then, if a new node revision was requested, save a new record for
1106        // that; otherwise, update the node revision record which matches the
1107        // value of $node->vid.
1108        if (!empty($node->revision)) {
1109          _node_save_revision($node, $user->uid);
1110        }
1111        else {
1112          _node_save_revision($node, $user->uid, 'vid');
1113          $update_node = FALSE;
1114        }
1115        $op = 'update';
1116      }
1117      if ($update_node) {
1118        db_update('node')
1119          ->fields(array('vid' => $node->vid))
1120          ->condition('nid', $node->nid)
1121          ->execute();
1122      }
1123  
1124      // Call the node specific callback (if any). This can be
1125      // node_invoke($node, 'insert') or
1126      // node_invoke($node, 'update').
1127      node_invoke($node, $op);
1128  
1129      // Save fields.
1130      $function = "field_attach_$op";
1131      $function('node', $node);
1132  
1133      module_invoke_all('node_' . $op, $node);
1134      module_invoke_all('entity_' . $op, $node, 'node');
1135  
1136      // Update the node access table for this node. There's no need to delete
1137      // existing records if the node is new.
1138      $delete = $op == 'update';
1139      node_access_acquire_grants($node, $delete);
1140  
1141      // Clear internal properties.
1142      unset($node->is_new);
1143      unset($node->original);
1144      // Clear the static loading cache.
1145      entity_get_controller('node')->resetCache(array($node->nid));
1146  
1147      // Ignore slave server temporarily to give time for the
1148      // saved node to be propagated to the slave.
1149      db_ignore_slave();
1150    }
1151    catch (Exception $e) {
1152      $transaction->rollback();
1153      watchdog_exception('node', $e);
1154      throw $e;
1155    }
1156  }
1157  
1158  /**
1159   * Helper function to save a revision with the uid of the current user.
1160   *
1161   * The resulting revision ID is available afterward in $node->vid.
1162   */
1163  function _node_save_revision($node, $uid, $update = NULL) {
1164    $temp_uid = $node->uid;
1165    $node->uid = $uid;
1166    if (isset($update)) {
1167      drupal_write_record('node_revision', $node, $update);
1168    }
1169    else {
1170      drupal_write_record('node_revision', $node);
1171    }
1172    // Have node object still show node owner's uid, not revision author's.
1173    $node->uid = $temp_uid;
1174  }
1175  
1176  /**
1177   * Delete a node.
1178   *
1179   * @param $nid
1180   *   A node ID.
1181   */
1182  function node_delete($nid) {
1183    node_delete_multiple(array($nid));
1184  }
1185  
1186  /**
1187   * Delete multiple nodes.
1188   *
1189   * @param $nids
1190   *   An array of node IDs.
1191   */
1192  function node_delete_multiple($nids) {
1193    $transaction = db_transaction();
1194    if (!empty($nids)) {
1195      $nodes = node_load_multiple($nids, array());
1196  
1197      try {
1198        foreach ($nodes as $nid => $node) {
1199          // Call the node-specific callback (if any):
1200          node_invoke($node, 'delete');
1201          module_invoke_all('node_delete', $node);
1202          module_invoke_all('entity_delete', $node, 'node');
1203          field_attach_delete('node', $node);
1204  
1205          // Remove this node from the search index if needed.
1206          // This code is implemented in node module rather than in search module,
1207          // because node module is implementing search module's API, not the other
1208          // way around.
1209          if (module_exists('search')) {
1210            search_reindex($nid, 'node');
1211          }
1212        }
1213  
1214        // Delete after calling hooks so that they can query node tables as needed.
1215        db_delete('node')
1216          ->condition('nid', $nids, 'IN')
1217          ->execute();
1218        db_delete('node_revision')
1219          ->condition('nid', $nids, 'IN')
1220          ->execute();
1221        db_delete('history')
1222          ->condition('nid', $nids, 'IN')
1223          ->execute();
1224        db_delete('node_access')
1225         ->condition('nid', $nids, 'IN')
1226         ->execute();
1227      }
1228      catch (Exception $e) {
1229        $transaction->rollback();
1230        watchdog_exception('node', $e);
1231        throw $e;
1232      }
1233  
1234      // Clear the page and block and node_load_multiple caches.
1235      entity_get_controller('node')->resetCache();
1236    }
1237  }
1238  
1239  /**
1240   * Delete a node revision.
1241   *
1242   * @param $revision_id
1243   *   The revision ID to delete.
1244   */
1245  function node_revision_delete($revision_id) {
1246    if ($revision = node_load(NULL, $revision_id)) {
1247      // Prevent deleting the current revision.
1248      $node = node_load($revision->nid);
1249      if ($revision_id == $node->vid) {
1250        return FALSE;
1251      }
1252  
1253      db_delete('node_revision')
1254        ->condition('nid', $revision->nid)
1255        ->condition('vid', $revision->vid)
1256        ->execute();
1257      module_invoke_all('node_revision_delete', $revision);
1258      field_attach_delete_revision('node', $revision);
1259      return TRUE;
1260    }
1261    return FALSE;
1262  }
1263  
1264  /**
1265   * Generate an array for rendering the given node.
1266   *
1267   * @param $node
1268   *   A node object.
1269   * @param $view_mode
1270   *   View mode, e.g. 'full', 'teaser'...
1271   * @param $langcode
1272   *   (optional) A language code to use for rendering. Defaults to the global
1273   *   content language of the current request.
1274   *
1275   * @return
1276   *   An array as expected by drupal_render().
1277   */
1278  function node_view($node, $view_mode = 'full', $langcode = NULL) {
1279    if (!isset($langcode)) {
1280      $langcode = $GLOBALS['language_content']->language;
1281    }
1282  
1283    // Populate $node->content with a render() array.
1284    node_build_content($node, $view_mode, $langcode);
1285  
1286    $build = $node->content;
1287    // We don't need duplicate rendering info in node->content.
1288    unset($node->content);
1289  
1290    $build += array(
1291      '#theme' => 'node',
1292      '#node' => $node,
1293      '#view_mode' => $view_mode,
1294      '#language' => $langcode,
1295    );
1296  
1297    // Add contextual links for this node, except when the node is already being
1298    // displayed on its own page. Modules may alter this behavior (for example,
1299    // to restrict contextual links to certain view modes) by implementing
1300    // hook_node_view_alter().
1301    if (!empty($node->nid) && !($view_mode == 'full' && node_is_page($node))) {
1302      $build['#contextual_links']['node'] = array('node', array($node->nid));
1303    }
1304  
1305    // Allow modules to modify the structured node.
1306    $type = 'node';
1307    drupal_alter(array('node_view', 'entity_view'), $build, $type);
1308  
1309    return $build;
1310  }
1311  
1312  /**
1313   * Builds a structured array representing the node's content.
1314   *
1315   * The content built for the node (field values, comments, file attachments or
1316   * other node components) will vary depending on the $view_mode parameter.
1317   *
1318   * Drupal core defines the following view modes for nodes, with the following
1319   * default use cases:
1320   *   - full (default): node is being displayed on its own page (node/123)
1321   *   - teaser: node is being displayed on the default home page listing, on
1322   *     taxonomy listing pages, or on blog listing pages.
1323   *   - rss: node displayed in an RSS feed.
1324   *   If search.module is enabled:
1325   *   - search_index: node is being indexed for search.
1326   *   - search_result: node is being displayed as a search result.
1327   *   If book.module is enabled:
1328   *   - print: node is being displayed in print-friendly mode.
1329   * Contributed modules might define additional view modes, or use existing
1330   * view modes in additional contexts.
1331   *
1332   * @param $node
1333   *   A node object.
1334   * @param $view_mode
1335   *   View mode, e.g. 'full', 'teaser'...
1336   * @param $langcode
1337   *   (optional) A language code to use for rendering. Defaults to the global
1338   *   content language of the current request.
1339   */
1340  function node_build_content($node, $view_mode = 'full', $langcode = NULL) {
1341    if (!isset($langcode)) {
1342      $langcode = $GLOBALS['language_content']->language;
1343    }
1344  
1345    // Remove previously built content, if exists.
1346    $node->content = array();
1347  
1348    // Allow modules to change the view mode.
1349    $context = array(
1350      'entity_type' => 'node',
1351      'entity' => $node,
1352      'langcode' => $langcode,
1353    );
1354    drupal_alter('entity_view_mode', $view_mode, $context);
1355  
1356    // The 'view' hook can be implemented to overwrite the default function
1357    // to display nodes.
1358    if (node_hook($node, 'view')) {
1359      $node = node_invoke($node, 'view', $view_mode, $langcode);
1360    }
1361  
1362    // Build fields content.
1363    // In case of a multiple view, node_view_multiple() already ran the
1364    // 'prepare_view' step. An internal flag prevents the operation from running
1365    // twice.
1366    field_attach_prepare_view('node', array($node->nid => $node), $view_mode, $langcode);
1367    entity_prepare_view('node', array($node->nid => $node), $langcode);
1368    $node->content += field_attach_view('node', $node, $view_mode, $langcode);
1369  
1370    // Always display a read more link on teasers because we have no way
1371    // to know when a teaser view is different than a full view.
1372    $links = array();
1373    $node->content['links'] = array(
1374      '#theme' => 'links__node',
1375      '#pre_render' => array('drupal_pre_render_links'),
1376      '#attributes' => array('class' => array('links', 'inline')),
1377    );
1378    if ($view_mode == 'teaser') {
1379      $node_title_stripped = strip_tags($node->title);
1380      $links['node-readmore'] = array(
1381        'title' => t('Read more<span class="element-invisible"> about @title</span>', array('@title' => $node_title_stripped)),
1382        'href' => 'node/' . $node->nid,
1383        'html' => TRUE,
1384        'attributes' => array('rel' => 'tag', 'title' => $node_title_stripped),
1385      );
1386    }
1387    $node->content['links']['node'] = array(
1388      '#theme' => 'links__node__node',
1389      '#links' => $links,
1390      '#attributes' => array('class' => array('links', 'inline')),
1391    );
1392  
1393    // Allow modules to make their own additions to the node.
1394    module_invoke_all('node_view', $node, $view_mode, $langcode);
1395    module_invoke_all('entity_view', $node, 'node', $view_mode, $langcode);
1396  
1397    // Make sure the current view mode is stored if no module has already
1398    // populated the related key.
1399    $node->content += array('#view_mode' => $view_mode);
1400  }
1401  
1402  /**
1403   * Generate an array which displays a node detail page.
1404   *
1405   * @param $node
1406   *   A node object.
1407   * @param $message
1408   *   A flag which sets a page title relevant to the revision being viewed.
1409   * @return
1410   *   A $page element suitable for use by drupal_render().
1411   */
1412  function node_show($node, $message = FALSE) {
1413    if ($message) {
1414      drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp))), PASS_THROUGH);
1415    }
1416  
1417    // For markup consistency with other pages, use node_view_multiple() rather than node_view().
1418    $nodes = node_view_multiple(array($node->nid => $node), 'full');
1419  
1420    // Update the history table, stating that this user viewed this node.
1421    node_tag_new($node);
1422  
1423    return $nodes;
1424  }
1425  
1426  /**
1427   * Returns whether the current page is the full page view of the passed-in node.
1428   *
1429   * @param $node
1430   *   A node object.
1431   */
1432  function node_is_page($node) {
1433    $page_node = menu_get_object();
1434    return (!empty($page_node) ? $page_node->nid == $node->nid : FALSE);
1435  }
1436  
1437  /**
1438   * Process variables for node.tpl.php
1439   *
1440   * Most themes utilize their own copy of node.tpl.php. The default is located
1441   * inside "modules/node/node.tpl.php". Look in there for the full list of
1442   * variables.
1443   *
1444   * The $variables array contains the following arguments:
1445   * - $node
1446   * - $view_mode
1447   * - $page
1448   *
1449   * @see node.tpl.php
1450   */
1451  function template_preprocess_node(&$variables) {
1452    $variables['view_mode'] = $variables['elements']['#view_mode'];
1453    // Provide a distinct $teaser boolean.
1454    $variables['teaser'] = $variables['view_mode'] == 'teaser';
1455    $variables['node'] = $variables['elements']['#node'];
1456    $node = $variables['node'];
1457  
1458    $variables['date']      = format_date($node->created);
1459    $variables['name']      = theme('username', array('account' => $node));
1460  
1461    $uri = entity_uri('node', $node);
1462    $variables['node_url']  = url($uri['path'], $uri['options']);
1463    $variables['title']     = check_plain($node->title);
1464    $variables['page']      = $variables['view_mode'] == 'full' && node_is_page($node);
1465  
1466    // Flatten the node object's member fields.
1467    $variables = array_merge((array) $node, $variables);
1468  
1469    // Helpful $content variable for templates.
1470    $variables += array('content' => array());
1471    foreach (element_children($variables['elements']) as $key) {
1472      $variables['content'][$key] = $variables['elements'][$key];
1473    }
1474  
1475    // Make the field variables available with the appropriate language.
1476    field_attach_preprocess('node', $node, $variables['content'], $variables);
1477  
1478    // Display post information only on certain node types.
1479    if (variable_get('node_submitted_' . $node->type, TRUE)) {
1480      $variables['display_submitted'] = TRUE;
1481      $variables['submitted'] = t('Submitted by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
1482      $variables['user_picture'] = theme_get_setting('toggle_node_user_picture') ? theme('user_picture', array('account' => $node)) : '';
1483    }
1484    else {
1485      $variables['display_submitted'] = FALSE;
1486      $variables['submitted'] = '';
1487      $variables['user_picture'] = '';
1488    }
1489  
1490    // Gather node classes.
1491    $variables['classes_array'][] = drupal_html_class('node-' . $node->type);
1492    if ($variables['promote']) {
1493      $variables['classes_array'][] = 'node-promoted';
1494    }
1495    if ($variables['sticky']) {
1496      $variables['classes_array'][] = 'node-sticky';
1497    }
1498    if (!$variables['status']) {
1499      $variables['classes_array'][] = 'node-unpublished';
1500    }
1501    if ($variables['teaser']) {
1502      $variables['classes_array'][] = 'node-teaser';
1503    }
1504    if (isset($variables['preview'])) {
1505      $variables['classes_array'][] = 'node-preview';
1506    }
1507  
1508    // Clean up name so there are no underscores.
1509    $variables['theme_hook_suggestions'][] = 'node__' . $node->type;
1510    $variables['theme_hook_suggestions'][] = 'node__' . $node->nid;
1511  }
1512  
1513  /**
1514   * Implements hook_permission().
1515   */
1516  function node_permission() {
1517    $perms = array(
1518      'bypass node access' => array(
1519        'title' => t('Bypass content access control'),
1520        'description' => t('View, edit and delete all content regardless of permission restrictions.'),
1521        'restrict access' => TRUE,
1522      ),
1523      'administer content types' => array(
1524        'title' => t('Administer content types'),
1525        'restrict access' => TRUE,
1526      ),
1527      'administer nodes' => array(
1528        'title' => t('Administer content'),
1529        'restrict access' => TRUE,
1530      ),
1531      'access content overview' => array(
1532        'title' => t('Access the content overview page'),
1533      ),
1534      'access content' => array(
1535        'title' => t('View published content'),
1536      ),
1537      'view own unpublished content' => array(
1538        'title' => t('View own unpublished content'),
1539      ),
1540      'view revisions' => array(
1541        'title' => t('View content revisions'),
1542      ),
1543      'revert revisions' => array(
1544        'title' => t('Revert content revisions'),
1545      ),
1546      'delete revisions' => array(
1547        'title' => t('Delete content revisions'),
1548      ),
1549    );
1550  
1551    // Generate standard node permissions for all applicable node types.
1552    foreach (node_permissions_get_configured_types() as $type) {
1553      $perms += node_list_permissions($type);
1554    }
1555  
1556    return $perms;
1557  }
1558  
1559  /**
1560   * Gather the rankings from the the hook_ranking implementations.
1561   *
1562   * @param $query
1563   *   A query object that has been extended with the Search DB Extender.
1564   */
1565  function _node_rankings(SelectQueryExtender $query) {
1566    if ($ranking = module_invoke_all('ranking')) {
1567      $tables = &$query->getTables();
1568      foreach ($ranking as $rank => $values) {
1569        if ($node_rank = variable_get('node_rank_' . $rank, 0)) {
1570          // If the table defined in the ranking isn't already joined, then add it.
1571          if (isset($values['join']) && !isset($tables[$values['join']['alias']])) {
1572            $query->addJoin($values['join']['type'], $values['join']['table'], $values['join']['alias'], $values['join']['on']);
1573          }
1574          $arguments = isset($values['arguments']) ? $values['arguments'] : array();
1575          $query->addScore($values['score'], $arguments, $node_rank);
1576        }
1577      }
1578    }
1579  }
1580  
1581  /**
1582   * Implements hook_search_info().
1583   */
1584  function node_search_info() {
1585    return array(
1586      'title' => 'Content',
1587      'path' => 'node',
1588    );
1589  }
1590  
1591  /**
1592   * Implements hook_search_access().
1593   */
1594  function node_search_access() {
1595    return user_access('access content');
1596  }
1597  
1598  /**
1599   * Implements hook_search_reset().
1600   */
1601  function node_search_reset() {
1602    db_update('search_dataset')
1603      ->fields(array('reindex' => REQUEST_TIME))
1604      ->condition('type', 'node')
1605      ->execute();
1606  }
1607  
1608  /**
1609   * Implements hook_search_status().
1610   */
1611  function node_search_status() {
1612    $total = db_query('SELECT COUNT(*) FROM {node}')->fetchField();
1613    $remaining = db_query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0")->fetchField();
1614    return array('remaining' => $remaining, 'total' => $total);
1615  }
1616  
1617  /**
1618   * Implements hook_search_admin().
1619   */
1620  function node_search_admin() {
1621    // Output form for defining rank factor weights.
1622    $form['content_ranking'] = array(
1623      '#type' => 'fieldset',
1624      '#title' => t('Content ranking'),
1625    );
1626    $form['content_ranking']['#theme'] = 'node_search_admin';
1627    $form['content_ranking']['info'] = array(
1628      '#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>'
1629    );
1630  
1631    // Note: reversed to reflect that higher number = higher ranking.
1632    $options = drupal_map_assoc(range(0, 10));
1633    foreach (module_invoke_all('ranking') as $var => $values) {
1634      $form['content_ranking']['factors']['node_rank_' . $var] = array(
1635        '#title' => $values['title'],
1636        '#type' => 'select',
1637        '#options' => $options,
1638        '#default_value' => variable_get('node_rank_' . $var, 0),
1639      );
1640    }
1641    return $form;
1642  }
1643  
1644  /**
1645   * Implements hook_search_execute().
1646   */
1647  function node_search_execute($keys = NULL, $conditions = NULL) {
1648    // Build matching conditions
1649    $query = db_select('search_index', 'i', array('target' => 'slave'))->extend('SearchQuery')->extend('PagerDefault');
1650    $query->join('node', 'n', 'n.nid = i.sid');
1651    $query
1652      ->condition('n.status', 1)
1653      ->addTag('node_access')
1654      ->searchExpression($keys, 'node');
1655  
1656    // Insert special keywords.
1657    $query->setOption('type', 'n.type');
1658    $query->setOption('language', 'n.language');
1659    if ($query->setOption('term', 'ti.tid')) {
1660      $query->join('taxonomy_index', 'ti', 'n.nid = ti.nid');
1661    }
1662    // Only continue if the first pass query matches.
1663    if (!$query->executeFirstPass()) {
1664      return array();
1665    }
1666  
1667    // Add the ranking expressions.
1668    _node_rankings($query);
1669  
1670    // Load results.
1671    $find = $query
1672      ->limit(10)
1673      ->execute();
1674    $results = array();
1675    foreach ($find as $item) {
1676      // Render the node.
1677      $node = node_load($item->sid);
1678      $build = node_view($node, 'search_result');
1679      unset($build['#theme']);
1680      $node->rendered = drupal_render($build);
1681  
1682      // Fetch comments for snippet.
1683      $node->rendered .= ' ' . module_invoke('comment', 'node_update_index', $node);
1684  
1685      $extra = module_invoke_all('node_search_result', $node);
1686  
1687      $uri = entity_uri('node', $node);
1688      $results[] = array(
1689        'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE))),
1690        'type' => check_plain(node_type_get_name($node)),
1691        'title' => $node->title,
1692        'user' => theme('username', array('account' => $node)),
1693        'date' => $node->changed,
1694        'node' => $node,
1695        'extra' => $extra,
1696        'score' => $item->calculated_score,
1697        'snippet' => search_excerpt($keys, $node->rendered),
1698        'language' => entity_language('node', $node),
1699      );
1700    }
1701    return $results;
1702  }
1703  
1704  /**
1705   * Implements hook_ranking().
1706   */
1707  function node_ranking() {
1708    // Create the ranking array and add the basic ranking options.
1709    $ranking = array(
1710      'relevance' => array(
1711        'title' => t('Keyword relevance'),
1712        // Average relevance values hover around 0.15
1713        'score' => 'i.relevance',
1714      ),
1715      'sticky' => array(
1716        'title' => t('Content is sticky at top of lists'),
1717        // The sticky flag is either 0 or 1, which is automatically normalized.
1718        'score' => 'n.sticky',
1719      ),
1720      'promote' => array(
1721        'title' => t('Content is promoted to the front page'),
1722        // The promote flag is either 0 or 1, which is automatically normalized.
1723        'score' => 'n.promote',
1724      ),
1725    );
1726  
1727    // Add relevance based on creation or changed date.
1728    if ($node_cron_last = variable_get('node_cron_last', 0)) {
1729      $ranking['recent'] = array(
1730        'title' => t('Recently posted'),
1731        // Exponential decay with half-life of 6 months, starting at last indexed node
1732        'score' => 'POW(2.0, (GREATEST(n.created, n.changed) - :node_cron_last) * 6.43e-8)',
1733        'arguments' => array(':node_cron_last' => $node_cron_last),
1734      );
1735    }
1736    return $ranking;
1737  }
1738  
1739  /**
1740   * Implements hook_user_cancel().
1741   */
1742  function node_user_cancel($edit, $account, $method) {
1743    switch ($method) {
1744      case 'user_cancel_block_unpublish':
1745        // Unpublish nodes (current revisions).
1746        module_load_include('inc', 'node', 'node.admin');
1747        $nodes = db_select('node', 'n')
1748          ->fields('n', array('nid'))
1749          ->condition('uid', $account->uid)
1750          ->execute()
1751          ->fetchCol();
1752        node_mass_update($nodes, array('status' => 0));
1753        break;
1754  
1755      case 'user_cancel_reassign':
1756        // Anonymize nodes (current revisions).
1757        module_load_include('inc', 'node', 'node.admin');
1758        $nodes = db_select('node', 'n')
1759          ->fields('n', array('nid'))
1760          ->condition('uid', $account->uid)
1761          ->execute()
1762          ->fetchCol();
1763        node_mass_update($nodes, array('uid' => 0));
1764        // Anonymize old revisions.
1765        db_update('node_revision')
1766          ->fields(array('uid' => 0))
1767          ->condition('uid', $account->uid)
1768          ->execute();
1769        // Clean history.
1770        db_delete('history')
1771          ->condition('uid', $account->uid)
1772          ->execute();
1773        break;
1774    }
1775  }
1776  
1777  /**
1778   * Implements hook_user_delete().
1779   */
1780  function node_user_delete($account) {
1781    // Delete nodes (current revisions).
1782    // @todo Introduce node_mass_delete() or make node_mass_update() more flexible.
1783    $nodes = db_select('node', 'n')
1784      ->fields('n', array('nid'))
1785      ->condition('uid', $account->uid)
1786      ->execute()
1787      ->fetchCol();
1788    node_delete_multiple($nodes);
1789    // Delete old revisions.
1790    $revisions = db_query('SELECT vid FROM {node_revision} WHERE uid = :uid', array(':uid' => $account->uid))->fetchCol();
1791    foreach ($revisions as $revision) {
1792      node_revision_delete($revision);
1793    }
1794    // Clean history.
1795    db_delete('history')
1796      ->condition('uid', $account->uid)
1797      ->execute();
1798  }
1799  
1800  /**
1801   * Returns HTML for the content ranking part of the search settings admin page.
1802   *
1803   * @param $variables
1804   *   An associative array containing:
1805   *   - form: A render element representing the form.
1806   *
1807   * @ingroup themeable
1808   */
1809  function theme_node_search_admin($variables) {
1810    $form = $variables['form'];
1811  
1812    $output = drupal_render($form['info']);
1813  
1814    $header = array(t('Factor'), t('Weight'));
1815    foreach (element_children($form['factors']) as $key) {
1816      $row = array();
1817      $row[] = $form['factors'][$key]['#title'];
1818      $form['factors'][$key]['#title_display'] = 'invisible';
1819      $row[] = drupal_render($form['factors'][$key]);
1820      $rows[] = $row;
1821    }
1822    $output .= theme('table', array('header' => $header, 'rows' => $rows));
1823  
1824    $output .= drupal_render_children($form);
1825    return $output;
1826  }
1827  
1828  /**
1829   * Access callback: Checks node revision access.
1830   *
1831   * @param $node
1832   *   The node to check.
1833   * @param $op
1834   *   (optional) The specific operation being checked. Defaults to 'view.'
1835   * @param object $account
1836   *   (optional) A user object representing the user for whom the operation is
1837   *   to be performed. Determines access for a user other than the current user.
1838   *
1839   * @return
1840   *   TRUE if the operation may be performed, FALSE otherwise.
1841   *
1842   * @see node_menu()
1843   */
1844  function _node_revision_access($node, $op = 'view', $account = NULL) {
1845    $access = &drupal_static(__FUNCTION__, array());
1846  
1847    $map = array(
1848      'view' => 'view revisions',
1849      'update' => 'revert revisions',
1850      'delete' => 'delete revisions',
1851    );
1852  
1853    if (!$node || !isset($map[$op])) {
1854      // If there was no node to check against, or the $op was not one of the
1855      // supported ones, we return access denied.
1856      return FALSE;
1857    }
1858  
1859    if (!isset($account)) {
1860      $account = $GLOBALS['user'];
1861    }
1862  
1863    // Statically cache access by revision ID, user account ID, and operation.
1864    $cid = $node->vid . ':' . $account->uid . ':' . $op;
1865  
1866    if (!isset($access[$cid])) {
1867      // Perform basic permission checks first.
1868      if (!user_access($map[$op], $account) && !user_access('administer nodes', $account)) {
1869        return $access[$cid] = FALSE;
1870      }
1871  
1872      $node_current_revision = node_load($node->nid);
1873      $is_current_revision = $node_current_revision->vid == $node->vid;
1874  
1875      // There should be at least two revisions. If the vid of the given node
1876      // and the vid of the current revision differ, then we already have two
1877      // different revisions so there is no need for a separate database check.
1878      // Also, if you try to revert to or delete the current revision, that's
1879      // not good.
1880      if ($is_current_revision && (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField() == 1 || $op == 'update' || $op == 'delete')) {
1881        $access[$cid] = FALSE;
1882      }
1883      elseif (user_access('administer nodes', $account)) {
1884        $access[$cid] = TRUE;
1885      }
1886      else {
1887        // First check the access to the current revision and finally, if the
1888        // node passed in is not the current revision then access to that, too.
1889        $access[$cid] = node_access($op, $node_current_revision, $account) && ($is_current_revision || node_access($op, $node, $account));
1890      }
1891    }
1892  
1893    return $access[$cid];
1894  }
1895  
1896  function _node_add_access() {
1897    $types = node_type_get_types();
1898    foreach ($types as $type) {
1899      if (node_hook($type->type, 'form') && node_access('create', $type->type)) {
1900        return TRUE;
1901      }
1902    }
1903    if (user_access('administer content types')) {
1904      // There are no content types defined that the user has permission to create,
1905      // but the user does have the permission to administer the content types, so
1906      // grant them access to the page anyway.
1907      return TRUE;
1908    }
1909    return FALSE;
1910  }
1911  
1912  /**
1913   * Implements hook_menu().
1914   */
1915  function node_menu() {
1916    $items['admin/content'] = array(
1917      'title' => 'Content',
1918      'description' => 'Find and manage content.',
1919      'page callback' => 'drupal_get_form',
1920      'page arguments' => array('node_admin_content'),
1921      'access arguments' => array('access content overview'),
1922      'weight' => -10,
1923      'file' => 'node.admin.inc',
1924    );
1925    $items['admin/content/node'] = array(
1926      'title' => 'Content',
1927      'type' => MENU_DEFAULT_LOCAL_TASK,
1928      'weight' => -10,
1929    );
1930  
1931    $items['admin/reports/status/rebuild'] = array(
1932      'title' => 'Rebuild permissions',
1933      'page callback' => 'drupal_get_form',
1934      'page arguments' => array('node_configure_rebuild_confirm'),
1935      // Any user than can potentially trigger a node_access_needs_rebuild(TRUE)
1936      // has to be allowed access to the 'node access rebuild' confirm form.
1937      'access arguments' => array('access administration pages'),
1938      'type' => MENU_CALLBACK,
1939      'file' => 'node.admin.inc',
1940    );
1941  
1942    $items['admin/structure/types'] = array(
1943      'title' => 'Content types',
1944      'description' => 'Manage content types, including default status, front page promotion, comment settings, etc.',
1945      'page callback' => 'node_overview_types',
1946      'access arguments' => array('administer content types'),
1947      'file' => 'content_types.inc',
1948    );
1949    $items['admin/structure/types/list'] = array(
1950      'title' => 'List',
1951      'type' => MENU_DEFAULT_LOCAL_TASK,
1952      'weight' => -10,
1953    );
1954    $items['admin/structure/types/add'] = array(
1955      'title' => 'Add content type',
1956      'page callback' => 'drupal_get_form',
1957      'page arguments' => array('node_type_form'),
1958      'access arguments' => array('administer content types'),
1959      'type' => MENU_LOCAL_ACTION,
1960      'file' => 'content_types.inc',
1961    );
1962    $items['admin/structure/types/manage/%node_type'] = array(
1963      'title' => 'Edit content type',
1964      'title callback' => 'node_type_page_title',
1965      'title arguments' => array(4),
1966      'page callback' => 'drupal_get_form',
1967      'page arguments' => array('node_type_form', 4),
1968      'access arguments' => array('administer content types'),
1969      'file' => 'content_types.inc',
1970    );
1971    $items['admin/structure/types/manage/%node_type/edit'] = array(
1972      'title' => 'Edit',
1973      'type' => MENU_DEFAULT_LOCAL_TASK,
1974    );
1975    $items['admin/structure/types/manage/%node_type/delete'] = array(
1976      'title' => 'Delete',
1977      'page arguments' => array('node_type_delete_confirm', 4),
1978      'access arguments' => array('administer content types'),
1979      'file' => 'content_types.inc',
1980    );
1981  
1982    $items['node'] = array(
1983      'page callback' => 'node_page_default',
1984      'access arguments' => array('access content'),
1985      'menu_name' => 'navigation',
1986      'type' => MENU_CALLBACK,
1987    );
1988    $items['node/add'] = array(
1989      'title' => 'Add content',
1990      'page callback' => 'node_add_page',
1991      'access callback' => '_node_add_access',
1992      'file' => 'node.pages.inc',
1993    );
1994    $items['rss.xml'] = array(
1995      'title' => 'RSS feed',
1996      'page callback' => 'node_feed',
1997      'access arguments' => array('access content'),
1998      'type' => MENU_CALLBACK,
1999      // Pass a FALSE and array argument to ensure that additional path components
2000      // are not passed to node_feed().
2001      'page arguments' => array(FALSE, array()),
2002    );
2003    // @todo Remove this loop when we have a 'description callback' property.
2004    // Reset internal static cache of _node_types_build(), forces to rebuild the
2005    // node type information.
2006    node_type_cache_reset();
2007    foreach (node_type_get_types() as $type) {
2008      $type_url_str = str_replace('_', '-', $type->type);
2009      $items['node/add/' . $type_url_str] = array(
2010        'title' => $type->name,
2011        'title callback' => 'check_plain',
2012        'page callback' => 'node_add',
2013        'page arguments' => array($type->type),
2014        'access callback' => 'node_access',
2015        'access arguments' => array('create', $type->type),
2016        'description' => $type->description,
2017        'file' => 'node.pages.inc',
2018      );
2019    }
2020    $items['node/%node'] = array(
2021      'title callback' => 'node_page_title',
2022      'title arguments' => array(1),
2023      // The page callback also invokes drupal_set_title() in case
2024      // the menu router's title is overridden by a menu link.
2025      'page callback' => 'node_page_view',
2026      'page arguments' => array(1),
2027      'access callback' => 'node_access',
2028      'access arguments' => array('view', 1),
2029    );
2030    $items['node/%node/view'] = array(
2031      'title' => 'View',
2032      'type' => MENU_DEFAULT_LOCAL_TASK,
2033      'weight' => -10,
2034    );
2035    $items['node/%node/edit'] = array(
2036      'title' => 'Edit',
2037      'page callback' => 'node_page_edit',
2038      'page arguments' => array(1),
2039      'access callback' => 'node_access',
2040      'access arguments' => array('update', 1),
2041      'weight' => 0,
2042      'type' => MENU_LOCAL_TASK,
2043      'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
2044      'file' => 'node.pages.inc',
2045    );
2046    $items['node/%node/delete'] = array(
2047      'title' => 'Delete',
2048      'page callback' => 'drupal_get_form',
2049      'page arguments' => array('node_delete_confirm', 1),
2050      'access callback' => 'node_access',
2051      'access arguments' => array('delete', 1),
2052      'weight' => 1,
2053      'type' => MENU_LOCAL_TASK,
2054      'context' => MENU_CONTEXT_INLINE,
2055      'file' => 'node.pages.inc',
2056    );
2057    $items['node/%node/revisions'] = array(
2058      'title' => 'Revisions',
2059      'page callback' => 'node_revision_overview',
2060      'page arguments' => array(1),
2061      'access callback' => '_node_revision_access',
2062      'access arguments' => array(1),
2063      'weight' => 2,
2064      'type' => MENU_LOCAL_TASK,
2065      'file' => 'node.pages.inc',
2066    );
2067    $items['node/%node/revisions/%/view'] = array(
2068      'title' => 'Revisions',
2069      'load arguments' => array(3),
2070      'page callback' => 'node_show',
2071      'page arguments' => array(1, TRUE),
2072      'access callback' => '_node_revision_access',
2073      'access arguments' => array(1),
2074    );
2075    $items['node/%node/revisions/%/revert'] = array(
2076      'title' => 'Revert to earlier revision',
2077      'load arguments' => array(3),
2078      'page callback' => 'drupal_get_form',
2079      'page arguments' => array('node_revision_revert_confirm', 1),
2080      'access callback' => '_node_revision_access',
2081      'access arguments' => array(1, 'update'),
2082      'file' => 'node.pages.inc',
2083    );
2084    $items['node/%node/revisions/%/delete'] = array(
2085      'title' => 'Delete earlier revision',
2086      'load arguments' => array(3),
2087      'page callback' => 'drupal_get_form',
2088      'page arguments' => array('node_revision_delete_confirm', 1),
2089      'access callback' => '_node_revision_access',
2090      'access arguments' => array(1, 'delete'),
2091      'file' => 'node.pages.inc',
2092    );
2093    return $items;
2094  }
2095  
2096  /**
2097   * Implements hook_menu_local_tasks_alter().
2098   */
2099  function node_menu_local_tasks_alter(&$data, $router_item, $root_path) {
2100    // Add action link to 'node/add' on 'admin/content' page.
2101    if ($root_path == 'admin/content') {
2102      $item = menu_get_item('node/add');
2103      if ($item['access']) {
2104        $data['actions']['output'][] = array(
2105          '#theme' => 'menu_local_action',
2106          '#link' => $item,
2107        );
2108      }
2109    }
2110  }
2111  
2112  /**
2113   * Title callback for a node type.
2114   */
2115  function node_type_page_title($type) {
2116    return $type->name;
2117  }
2118  
2119  /**
2120   * Title callback.
2121   */
2122  function node_page_title($node) {
2123    return $node->title;
2124  }
2125  
2126  /**
2127   * Finds the last time a node was changed.
2128   *
2129   * @param $nid
2130   *   The ID of a node.
2131   *
2132   * @return
2133   *   A unix timestamp indicating the last time the node was changed.
2134   */
2135  function node_last_changed($nid) {
2136    return db_query('SELECT changed FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetch()->changed;
2137  }
2138  
2139  /**
2140   * Return a list of all the existing revision numbers.
2141   */
2142  function node_revision_list($node) {
2143    $revisions = array();
2144    $result = db_query('SELECT r.vid, r.title, r.log, r.uid, n.vid AS current_vid, r.timestamp, u.name FROM {node_revision} r LEFT JOIN {node} n ON n.vid = r.vid INNER JOIN {users} u ON u.uid = r.uid WHERE r.nid = :nid ORDER BY r.vid DESC', array(':nid' => $node->nid));
2145    foreach ($result as $revision) {
2146      $revisions[$revision->vid] = $revision;
2147    }
2148  
2149    return $revisions;
2150  }
2151  
2152  /**
2153   * Implements hook_block_info().
2154   */
2155  function node_block_info() {
2156    $blocks['syndicate']['info'] = t('Syndicate');
2157    // Not worth caching.
2158    $blocks['syndicate']['cache'] = DRUPAL_NO_CACHE;
2159  
2160    $blocks['recent']['info'] = t('Recent content');
2161    $blocks['recent']['properties']['administrative'] = TRUE;
2162  
2163    return $blocks;
2164  }
2165  
2166  /**
2167   * Implements hook_block_view().
2168   */
2169  function node_block_view($delta = '') {
2170    $block = array();
2171  
2172    switch ($delta) {
2173      case 'syndicate':
2174        $block['subject'] = t('Syndicate');
2175        $block['content'] = theme('feed_icon', array('url' => 'rss.xml', 'title' => t('Syndicate')));
2176        break;
2177  
2178      case 'recent':
2179        if (user_access('access content')) {
2180          $block['subject'] = t('Recent content');
2181          if ($nodes = node_get_recent(variable_get('node_recent_block_count', 10))) {
2182            $block['content'] = theme('node_recent_block', array(
2183              'nodes' => $nodes,
2184            ));
2185          } else {
2186            $block['content'] = t('No content available.');
2187          }
2188        }
2189        break;
2190    }
2191    return $block;
2192  }
2193  
2194  /**
2195   * Implements hook_block_configure().
2196   */
2197  function node_block_configure($delta = '') {
2198    $form = array();
2199    if ($delta == 'recent') {
2200      $form['node_recent_block_count'] = array(
2201        '#type' => 'select',
2202        '#title' => t('Number of recent content items to display'),
2203        '#default_value' => variable_get('node_recent_block_count', 10),
2204        '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 30)),
2205      );
2206    }
2207    return $form;
2208  }
2209  
2210  /**
2211   * Implements hook_block_save().
2212   */
2213  function node_block_save($delta = '', $edit = array()) {
2214    if ($delta == 'recent') {
2215      variable_set('node_recent_block_count', $edit['node_recent_block_count']);
2216    }
2217  }
2218  
2219  /**
2220   * Finds the most recently changed nodes that are available to the current user.
2221   *
2222   * @param $number
2223   *   (optional) The maximum number of nodes to find. Defaults to 10.
2224   *
2225   * @return
2226   *   An array of partial node objects or an empty array if there are no recent
2227   *   nodes visible to the current user.
2228   */
2229  function node_get_recent($number = 10) {
2230    $query = db_select('node', 'n');
2231  
2232    if (!user_access('bypass node access')) {
2233      // If the user is able to view their own unpublished nodes, allow them
2234      // to see these in addition to published nodes. Check that they actually
2235      // have some unpublished nodes to view before adding the condition.
2236      if (user_access('view own unpublished content') && $own_unpublished = db_query('SELECT nid FROM {node} WHERE uid = :uid AND status = :status', array(':uid' => $GLOBALS['user']->uid, ':status' => NODE_NOT_PUBLISHED))->fetchCol()) {
2237        $query->condition(db_or()
2238          ->condition('n.status', NODE_PUBLISHED)
2239          ->condition('n.nid', $own_unpublished, 'IN')
2240        );
2241      }
2242      else {
2243        // If not, restrict the query to published nodes.
2244        $query->condition('n.status', NODE_PUBLISHED);
2245      }
2246    }
2247    $nids = $query
2248      ->fields('n', array('nid'))
2249      ->orderBy('n.changed', 'DESC')
2250      ->range(0, $number)
2251      ->addTag('node_access')
2252      ->execute()
2253      ->fetchCol();
2254  
2255    $nodes = node_load_multiple($nids);
2256  
2257    return $nodes ? $nodes : array();
2258  }
2259  
2260  /**
2261   * Returns HTML for a list of recent content.
2262   *
2263   * @param $variables
2264   *   An associative array containing:
2265   *   - nodes: An array of recent node objects.
2266   *
2267   * @ingroup themeable
2268   */
2269  function theme_node_recent_block($variables) {
2270    $rows = array();
2271    $output = '';
2272  
2273    $l_options = array('query' => drupal_get_destination());
2274    foreach ($variables['nodes'] as $node) {
2275      $row = array();
2276      $row[] = array(
2277        'data' => theme('node_recent_content', array('node' => $node)),
2278        'class' => 'title-author',
2279      );
2280      $row[] = array(
2281        'data' => node_access('update', $node) ? l(t('edit'), 'node/' . $node->nid . '/edit', $l_options) : '',
2282        'class' => 'edit',
2283      );
2284      $row[] = array(
2285        'data' => node_access('delete', $node) ? l(t('delete'), 'node/' . $node->nid . '/delete', $l_options) : '',
2286        'class' => 'delete',
2287      );
2288      $rows[] = $row;
2289    }
2290  
2291    if ($rows) {
2292      $output = theme('table', array('rows' => $rows));
2293      if (user_access('access content overview')) {
2294        $output .= theme('more_link', array('url' => 'admin/content', 'title' => t('Show more content')));
2295      }
2296    }
2297  
2298    return $output;
2299  }
2300  
2301  /**
2302   * Returns HTML for a recent node to be displayed in the recent content block.
2303   *
2304   * @param $variables
2305   *   An associative array containing:
2306   *   - node: A node object.
2307   *
2308   * @ingroup themeable
2309   */
2310  function theme_node_recent_content($variables) {
2311    $node = $variables['node'];
2312  
2313    $output = '<div class="node-title">';
2314    $output .= l($node->title, 'node/' . $node->nid);
2315    $output .= theme('mark', array('type' => node_mark($node->nid, $node->changed)));
2316    $output .= '</div><div class="node-author">';
2317    $output .= theme('username', array('account' => user_load($node->uid)));
2318    $output .= '</div>';
2319  
2320    return $output;
2321  }
2322  
2323  /**
2324   * Implements hook_form_FORMID_alter().
2325   *
2326   * Adds node-type specific visibility options to add block form.
2327   *
2328   * @see block_add_block_form()
2329   */
2330  function node_form_block_add_block_form_alter(&$form, &$form_state) {
2331    node_form_block_admin_configure_alter($form, $form_state);
2332  }
2333  
2334  /**
2335   * Implements hook_form_FORMID_alter().
2336   *
2337   * Adds node-type specific visibility options to block configuration form.
2338   *
2339   * @see block_admin_configure()
2340   */
2341  function node_form_block_admin_configure_alter(&$form, &$form_state) {
2342    $default_type_options = db_query("SELECT type FROM {block_node_type} WHERE module = :module AND delta = :delta", array(
2343      ':module' => $form['module']['#value'],
2344      ':delta' => $form['delta']['#value'],
2345    ))->fetchCol();
2346    $form['visibility']['node_type'] = array(
2347      '#type' => 'fieldset',
2348      '#title' => t('Content types'),
2349      '#collapsible' => TRUE,
2350      '#collapsed' => TRUE,
2351      '#group' => 'visibility',
2352      '#weight' => 5,
2353    );
2354    $form['visibility']['node_type']['types'] = array(
2355      '#type' => 'checkboxes',
2356      '#title' => t('Show block for specific content types'),
2357      '#default_value' => $default_type_options,
2358      '#options' => node_type_get_names(),
2359      '#description' => t('Show this block only on pages that display content of the given type(s). If you select no types, there will be no type-specific limitation.'),
2360    );
2361    $form['#submit'][] = 'node_form_block_admin_configure_submit';
2362  }
2363  
2364  /**
2365   * Form submit handler for block configuration form.
2366   *
2367   * @see node_form_block_admin_configure_alter()
2368   */
2369  function node_form_block_admin_configure_submit($form, &$form_state) {
2370    db_delete('block_node_type')
2371      ->condition('module', $form_state['values']['module'])
2372      ->condition('delta', $form_state['values']['delta'])
2373      ->execute();
2374    $query = db_insert('block_node_type')->fields(array('type', 'module', 'delta'));
2375    foreach (array_filter($form_state['values']['types']) as $type) {
2376      $query->values(array(
2377        'type' => $type,
2378        'module' => $form_state['values']['module'],
2379        'delta' => $form_state['values']['delta'],
2380      ));
2381    }
2382    $query->execute();
2383  }
2384  
2385  /**
2386   * Implements hook_form_FORMID_alter().
2387   *
2388   * Adds node specific submit handler to delete custom block form.
2389   *
2390   * @see block_custom_block_delete()
2391   */
2392  function node_form_block_custom_block_delete_alter(&$form, &$form_state) {
2393    $form['#submit'][] = 'node_form_block_custom_block_delete_submit';
2394  }
2395  
2396  /**
2397   * Form submit handler for custom block delete form.
2398   *
2399   * @see node_form_block_custom_block_delete_alter()
2400   */
2401  function node_form_block_custom_block_delete_submit($form, &$form_state) {
2402    db_delete('block_node_type')
2403      ->condition('module', 'block')
2404      ->condition('delta', $form_state['values']['bid'])
2405      ->execute();
2406  }
2407  
2408  /**
2409   * Implements hook_modules_uninstalled().
2410   *
2411   * Cleanup {block_node_type} table from modules' blocks.
2412   */
2413  function node_modules_uninstalled($modules) {
2414    db_delete('block_node_type')
2415      ->condition('module', $modules, 'IN')
2416      ->execute();
2417  }
2418  
2419  /**
2420   * Implements hook_block_list_alter().
2421   *
2422   * Check the content type specific visibilty settings.
2423   * Remove the block if the visibility conditions are not met.
2424   */
2425  function node_block_list_alter(&$blocks) {
2426    global $theme_key;
2427  
2428    // Build an array of node types for each block.
2429    $block_node_types = array();
2430    $result = db_query('SELECT module, delta, type FROM {block_node_type}');
2431    foreach ($result as $record) {
2432      $block_node_types[$record->module][$record->delta][$record->type] = TRUE;
2433    }
2434  
2435    $node = menu_get_object();
2436    $node_types = node_type_get_types();
2437    if (arg(0) == 'node' && arg(1) == 'add' && arg(2)) {
2438      $node_add_arg = strtr(arg(2), '-', '_');
2439    }
2440    foreach ($blocks as $key => $block) {
2441      if (!isset($block->theme) || !isset($block->status) || $block->theme != $theme_key || $block->status != 1) {
2442        // This block was added by a contrib module, leave it in the list.
2443        continue;
2444      }
2445  
2446      // If a block has no node types associated, it is displayed for every type.
2447      // For blocks with node types associated, if the node type does not match
2448      // the settings from this block, remove it from the block list.
2449      if (isset($block_node_types[$block->module][$block->delta])) {
2450        if (!empty($node)) {
2451          // This is a node or node edit page.
2452          if (!isset($block_node_types[$block->module][$block->delta][$node->type])) {
2453            // This block should not be displayed for this node type.
2454            unset($blocks[$key]);
2455            continue;
2456          }
2457        }
2458        elseif (isset($node_add_arg) && isset($node_types[$node_add_arg])) {
2459          // This is a node creation page
2460          if (!isset($block_node_types[$block->module][$block->delta][$node_add_arg])) {
2461            // This block should not be displayed for this node type.
2462            unset($blocks[$key]);
2463            continue;
2464          }
2465        }
2466        else {
2467          // This is not a node page, remove the block.
2468          unset($blocks[$key]);
2469          continue;
2470        }
2471      }
2472    }
2473  }
2474  
2475  /**
2476   * Generates and prints an RSS feed.
2477   *
2478   * Generates an RSS feed from an array of node IDs, and prints it with an HTTP
2479   * header, with Content Type set to RSS/XML.
2480   *
2481   * @param $nids
2482   *   An array of node IDs (nid). Defaults to FALSE so empty feeds can be
2483   *   generated with passing an empty array, if no items are to be added
2484   *   to the feed.
2485   * @param $channel
2486   *   An associative array containing title, link, description and other keys,
2487   *   to be parsed by format_rss_channel() and format_xml_elements().
2488   *   A list of channel elements can be found at the @link http://cyber.law.harvard.edu/rss/rss.html RSS 2.0 Specification. @endlink
2489   *   The link should be an absolute URL.
2490   */
2491  function node_feed($nids = FALSE, $channel = array()) {
2492    global $base_url, $language_content;
2493  
2494    if ($nids === FALSE) {
2495      $nids = db_select('node', 'n')
2496        ->fields('n', array('nid', 'created'))
2497        ->condition('n.promote', 1)
2498        ->condition('n.status', 1)
2499        ->orderBy('n.created', 'DESC')
2500        ->range(0, variable_get('feed_default_items', 10))
2501        ->addTag('node_access')
2502        ->execute()
2503        ->fetchCol();
2504    }
2505  
2506    $item_length = variable_get('feed_item_length', 'fulltext');
2507    $namespaces = array('xmlns:dc' => 'http://purl.org/dc/elements/1.1/');
2508    $teaser = ($item_length == 'teaser');
2509  
2510    // Load all nodes to be rendered.
2511    $nodes = node_load_multiple($nids);
2512    $items = '';
2513    foreach ($nodes as $node) {
2514      $item_text = '';
2515  
2516      $node->link = url("node/$node->nid", array('absolute' => TRUE));
2517      $node->rss_namespaces = array();
2518      $node->rss_elements = array(
2519        array('key' => 'pubDate', 'value' => gmdate('r', $node->created)),
2520        array('key' => 'dc:creator', 'value' => $node->name),
2521        array('key' => 'guid', 'value' => $node->nid . ' at ' . $base_url, 'attributes' => array('isPermaLink' => 'false'))
2522      );
2523  
2524      // The node gets built and modules add to or modify $node->rss_elements
2525      // and $node->rss_namespaces.
2526      $build = node_view($node, 'rss');
2527      unset($build['#theme']);
2528  
2529      if (!empty($node->rss_namespaces)) {
2530        $namespaces = array_merge($namespaces, $node->rss_namespaces);
2531      }
2532  
2533      if ($item_length != 'title') {
2534        // We render node contents and force links to be last.
2535        $build['links']['#weight'] = 1000;
2536        $item_text .= drupal_render($build);
2537      }
2538  
2539      $items .= format_rss_item($node->title, $node->link, $item_text, $node->rss_elements);
2540    }
2541  
2542    $channel_defaults = array(
2543      'version'     => '2.0',
2544      'title'       => variable_get('site_name', 'Drupal'),
2545      'link'        => $base_url,
2546      'description' => variable_get('feed_description', ''),
2547      'language'    => $language_content->language
2548    );
2549    $channel_extras = array_diff_key($channel, $channel_defaults);
2550    $channel = array_merge($channel_defaults, $channel);
2551  
2552    $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2553    $output .= "<rss version=\"" . $channel["version"] . "\" xml:base=\"" . $base_url . "\" " . drupal_attributes($namespaces) . ">\n";
2554    $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language'], $channel_extras);
2555    $output .= "</rss>\n";
2556  
2557    drupal_add_http_header('Content-Type', 'application/rss+xml; charset=utf-8');
2558    print $output;
2559  }
2560  
2561  /**
2562   * Construct a drupal_render() style array from an array of loaded nodes.
2563   *
2564   * @param $nodes
2565   *   An array of nodes as returned by node_load_multiple().
2566   * @param $view_mode
2567   *   View mode, e.g. 'full', 'teaser'...
2568   * @param $weight
2569   *   An integer representing the weight of the first node in the list.
2570   * @param $langcode
2571   *   (optional) A language code to use for rendering. Defaults to the global
2572   *   content language of the current request.
2573   *
2574   * @return
2575   *   An array in the format expected by drupal_render().
2576   */
2577  function node_view_multiple($nodes, $view_mode = 'teaser', $weight = 0, $langcode = NULL) {
2578    field_attach_prepare_view('node', $nodes, $view_mode, $langcode);
2579    entity_prepare_view('node', $nodes, $langcode);
2580    $build = array();
2581    foreach ($nodes as $node) {
2582      $build['nodes'][$node->nid] = node_view($node, $view_mode, $langcode);
2583      $build['nodes'][$node->nid]['#weight'] = $weight;
2584      $weight++;
2585    }
2586    $build['nodes']['#sorted'] = TRUE;
2587    return $build;
2588  }
2589  
2590  /**
2591   * Menu callback; Generate a listing of promoted nodes.
2592   */
2593  function node_page_default() {
2594    $select = db_select('node', 'n')
2595      ->fields('n', array('nid', 'sticky', 'created'))
2596      ->condition('n.promote', 1)
2597      ->condition('n.status', 1)
2598      ->orderBy('n.sticky', 'DESC')
2599      ->orderBy('n.created', 'DESC')
2600      ->extend('PagerDefault')
2601      ->limit(variable_get('default_nodes_main', 10))
2602      ->addTag('node_access');
2603  
2604    $nids = $select->execute()->fetchCol();
2605  
2606    if (!empty($nids)) {
2607      $nodes = node_load_multiple($nids);
2608      $build = node_view_multiple($nodes);
2609  
2610      // 'rss.xml' is a path, not a file, registered in node_menu().
2611      drupal_add_feed('rss.xml', variable_get('site_name', 'Drupal') . ' ' . t('RSS'));
2612      $build['pager'] = array(
2613        '#theme' => 'pager',
2614        '#weight' => 5,
2615      );
2616      drupal_set_title('');
2617    }
2618    else {
2619      drupal_set_title(t('Welcome to @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), PASS_THROUGH);
2620  
2621      $default_message = '<p>' . t('No front page content has been created yet.') . '</p>';
2622  
2623      $default_links = array();
2624      if (_node_add_access()) {
2625        $default_links[] = l(t('Add new content'), 'node/add');
2626      }
2627      if (!empty($default_links)) {
2628        $default_message .= theme('item_list', array('items' => $default_links));
2629      }
2630  
2631      $build['default_message'] = array(
2632        '#markup' => $default_message,
2633        '#prefix' => '<div id="first-time">',
2634        '#suffix' => '</div>',
2635      );
2636    }
2637    return $build;
2638  }
2639  
2640  /**
2641   * Menu callback; view a single node.
2642   */
2643  function node_page_view($node) {
2644    // If there is a menu link to this node, the link becomes the last part
2645    // of the active trail, and the link name becomes the page title.
2646    // Thus, we must explicitly set the page title to be the node title.
2647    drupal_set_title($node->title);
2648    $uri = entity_uri('node', $node);
2649    // Set the node path as the canonical URL to prevent duplicate content.
2650    drupal_add_html_head_link(array('rel' => 'canonical', 'href' => url($uri['path'], $uri['options'])), TRUE);
2651    // Set the non-aliased path as a default shortlink.
2652    drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE);
2653    return node_show($node);
2654  }
2655  
2656  /**
2657   * Implements hook_update_index().
2658   */
2659  function node_update_index() {
2660    $limit = (int)variable_get('search_cron_limit', 100);
2661  
2662    $result = db_query_range("SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, $limit, array(), array('target' => 'slave'));
2663  
2664    foreach ($result as $node) {
2665      _node_index_node($node);
2666    }
2667  }
2668  
2669  /**
2670   * Index a single node.
2671   *
2672   * @param $node
2673   *   The node to index.
2674   */
2675  function _node_index_node($node) {
2676    $node = node_load($node->nid);
2677  
2678    // Save the changed time of the most recent indexed node, for the search
2679    // results half-life calculation.
2680    variable_set('node_cron_last', $node->changed);
2681  
2682    // Render the node.
2683    $build = node_view($node, 'search_index');
2684    unset($build['#theme']);
2685    $node->rendered = drupal_render($build);
2686  
2687    $text = '<h1>' . check_plain($node->title) . '</h1>' . $node->rendered;
2688  
2689    // Fetch extra data normally not visible
2690    $extra = module_invoke_all('node_update_index', $node);
2691    foreach ($extra as $t) {
2692      $text .= $t;
2693    }
2694  
2695    // Update index
2696    search_index($node->nid, 'node', $text);
2697  }
2698  
2699  /**
2700   * Implements hook_form_FORM_ID_alter().
2701   */
2702  function node_form_search_form_alter(&$form, $form_state) {
2703    if (isset($form['module']) && $form['module']['#value'] == 'node' && user_access('use advanced search')) {
2704      // Keyword boxes:
2705      $form['advanced'] = array(
2706        '#type' => 'fieldset',
2707        '#title' => t('Advanced search'),
2708        '#collapsible' => TRUE,
2709        '#collapsed' => TRUE,
2710        '#attributes' => array('class' => array('search-advanced')),
2711      );
2712      $form['advanced']['keywords'] = array(
2713        '#prefix' => '<div class="criterion">',
2714        '#suffix' => '</div>',
2715      );
2716      $form['advanced']['keywords']['or'] = array(
2717        '#type' => 'textfield',
2718        '#title' => t('Containing any of the words'),
2719        '#size' => 30,
2720        '#maxlength' => 255,
2721      );
2722      $form['advanced']['keywords']['phrase'] = array(
2723        '#type' => 'textfield',
2724        '#title' => t('Containing the phrase'),
2725        '#size' => 30,
2726        '#maxlength' => 255,
2727      );
2728      $form['advanced']['keywords']['negative'] = array(
2729        '#type' => 'textfield',
2730        '#title' => t('Containing none of the words'),
2731        '#size' => 30,
2732        '#maxlength' => 255,
2733      );
2734  
2735      // Node types:
2736      $types = array_map('check_plain', node_type_get_names());
2737      $form['advanced']['type'] = array(
2738        '#type' => 'checkboxes',
2739        '#title' => t('Only of the type(s)'),
2740        '#prefix' => '<div class="criterion">',
2741        '#suffix' => '</div>',
2742        '#options' => $types,
2743      );
2744      $form['advanced']['submit'] = array(
2745        '#type' => 'submit',
2746        '#value' => t('Advanced search'),
2747        '#prefix' => '<div class="action">',
2748        '#suffix' => '</div>',
2749        '#weight' => 100,
2750      );
2751  
2752      // Languages:
2753      $language_options = array();
2754      foreach (language_list('language') as $key => $entity) {
2755        if ($entity->enabled) {
2756          $language_options[$key] = $entity->name;
2757        }
2758      }
2759      if (count($language_options) > 1) {
2760        $form['advanced']['language'] = array(
2761          '#type' => 'checkboxes',
2762          '#title' => t('Languages'),
2763          '#prefix' => '<div class="criterion">',
2764          '#suffix' => '</div>',
2765          '#options' => $language_options,
2766        );
2767      }
2768  
2769      $form['#validate'][] = 'node_search_validate';
2770    }
2771  }
2772  
2773  /**
2774   * Form API callback for the search form. Registered in node_form_alter().
2775   */
2776  function node_search_validate($form, &$form_state) {
2777    // Initialize using any existing basic search keywords.
2778    $keys = $form_state['values']['processed_keys'];
2779  
2780    // Insert extra restrictions into the search keywords string.
2781    if (isset($form_state['values']['type']) && is_array($form_state['values']['type'])) {
2782      // Retrieve selected types - Form API sets the value of unselected
2783      // checkboxes to 0.
2784      $form_state['values']['type'] = array_filter($form_state['values']['type']);
2785      if (count($form_state['values']['type'])) {
2786        $keys = search_expression_insert($keys, 'type', implode(',', array_keys($form_state['values']['type'])));
2787      }
2788    }
2789  
2790    if (isset($form_state['values']['term']) && is_array($form_state['values']['term']) && count($form_state['values']['term'])) {
2791      $keys = search_expression_insert($keys, 'term', implode(',', $form_state['values']['term']));
2792    }
2793    if (isset($form_state['values']['language']) && is_array($form_state['values']['language'])) {
2794      $languages = array_filter($form_state['values']['language']);
2795      if (count($languages)) {
2796        $keys = search_expression_insert($keys, 'language', implode(',', $languages));
2797      }
2798    }
2799    if ($form_state['values']['or'] != '') {
2800      if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['or'], $matches)) {
2801        $keys .= ' ' . implode(' OR ', $matches[1]);
2802      }
2803    }
2804    if ($form_state['values']['negative'] != '') {
2805      if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['negative'], $matches)) {
2806        $keys .= ' -' . implode(' -', $matches[1]);
2807      }
2808    }
2809    if ($form_state['values']['phrase'] != '') {
2810      $keys .= ' "' . str_replace('"', ' ', $form_state['values']['phrase']) . '"';
2811    }
2812    if (!empty($keys)) {
2813      form_set_value($form['basic']['processed_keys'], trim($keys), $form_state);
2814    }
2815  }
2816  
2817  /**
2818   * @defgroup node_access Node access rights
2819   * @{
2820   * The node access system determines who can do what to which nodes.
2821   *
2822   * In determining access rights for a node, node_access() first checks
2823   * whether the user has the "bypass node access" permission. Such users have
2824   * unrestricted access to all nodes. user 1 will always pass this check.
2825   *
2826   * Next, all implementations of hook_node_access() will be called. Each
2827   * implementation may explicitly allow, explicitly deny, or ignore the access
2828   * request. If at least one module says to deny the request, it will be rejected.
2829   * If no modules deny the request and at least one says to allow it, the request
2830   * will be permitted.
2831   *
2832   * If all modules ignore the access request, then the node_access table is used
2833   * to determine access. All node access modules are queried using
2834   * hook_node_grants() to assemble a list of "grant IDs" for the user. This list
2835   * is compared against the table. If any row contains the node ID in question
2836   * (or 0, which stands for "all nodes"), one of the grant IDs returned, and a
2837   * value of TRUE for the operation in question, then access is granted. Note
2838   * that this table is a list of grants; any matching row is sufficient to
2839   * grant access to the node.
2840   *
2841   * In node listings (lists of nodes generated from a select query, such as the
2842   * default home page at path 'node', an RSS feed, a recent content block, etc.),
2843   * the process above is followed except that hook_node_access() is not called on
2844   * each node for performance reasons and for proper functioning of the pager
2845   * system. When adding a node listing to your module, be sure to use a dynamic
2846   * query created by db_select() and add a tag of "node_access". This will allow
2847   * modules dealing with node access to ensure only nodes to which the user has
2848   * access are retrieved, through the use of hook_query_TAG_alter().
2849   *
2850   * Note: Even a single module returning NODE_ACCESS_DENY from hook_node_access()
2851   * will block access to the node. Therefore, implementers should take care to
2852   * not deny access unless they really intend to. Unless a module wishes to
2853   * actively deny access it should return NODE_ACCESS_IGNORE (or simply return
2854   * nothing) to allow other modules or the node_access table to control access.
2855   *
2856   * To see how to write a node access module of your own, see
2857   * node_access_example.module.
2858   */
2859  
2860  /**
2861   * Determine whether the current user may perform the given operation on the
2862   * specified node.
2863   *
2864   * @param $op
2865   *   The operation to be performed on the node. Possible values are:
2866   *   - "view"
2867   *   - "update"
2868   *   - "delete"
2869   *   - "create"
2870   * @param $node
2871   *   The node object on which the operation is to be performed, or node type
2872   *   (e.g. 'forum') for "create" operation.
2873   * @param $account
2874   *   Optional, a user object representing the user for whom the operation is to
2875   *   be performed. Determines access for a user other than the current user.
2876   * @return
2877   *   TRUE if the operation may be performed, FALSE otherwise.
2878   */
2879  function node_access($op, $node, $account = NULL) {
2880    $rights = &drupal_static(__FUNCTION__, array());
2881  
2882    if (!$node || !in_array($op, array('view', 'update', 'delete', 'create'), TRUE)) {
2883      // If there was no node to check against, or the $op was not one of the
2884      // supported ones, we return access denied.
2885      return FALSE;
2886    }
2887    // If no user object is supplied, the access check is for the current user.
2888    if (empty($account)) {
2889      $account = $GLOBALS['user'];
2890    }
2891  
2892    // $node may be either an object or a node type. Since node types cannot be
2893    // an integer, use either nid or type as the static cache id.
2894  
2895    $cid = is_object($node) ? $node->nid : $node;
2896  
2897    // If we've already checked access for this node, user and op, return from
2898    // cache.
2899    if (isset($rights[$account->uid][$cid][$op])) {
2900      return $rights[$account->uid][$cid][$op];
2901    }
2902  
2903    if (user_access('bypass node access', $account)) {
2904      $rights[$account->uid][$cid][$op] = TRUE;
2905      return TRUE;
2906    }
2907    if (!user_access('access content', $account)) {
2908      $rights[$account->uid][$cid][$op] = FALSE;
2909      return FALSE;
2910    }
2911  
2912    // We grant access to the node if both of the following conditions are met:
2913    // - No modules say to deny access.
2914    // - At least one module says to grant access.
2915    // If no module specified either allow or deny, we fall back to the
2916    // node_access table.
2917    $access = module_invoke_all('node_access', $node, $op, $account);
2918    if (in_array(NODE_ACCESS_DENY, $access, TRUE)) {
2919      $rights[$account->uid][$cid][$op] = FALSE;
2920      return FALSE;
2921    }
2922    elseif (in_array(NODE_ACCESS_ALLOW, $access, TRUE)) {
2923      $rights[$account->uid][$cid][$op] = TRUE;
2924      return TRUE;
2925    }
2926  
2927    // Check if authors can view their own unpublished nodes.
2928    if ($op == 'view' && !$node->status && user_access('view own unpublished content', $account) && $account->uid == $node->uid && $account->uid != 0) {
2929      $rights[$account->uid][$cid][$op] = TRUE;
2930      return TRUE;
2931    }
2932  
2933    // If the module did not override the access rights, use those set in the
2934    // node_access table.
2935    if ($op != 'create' && $node->nid) {
2936      if (module_implements('node_grants')) {
2937        $query = db_select('node_access');
2938        $query->addExpression('1');
2939        $query->condition('grant_' . $op, 1, '>=');
2940        $nids = db_or()->condition('nid', $node->nid);
2941        if ($node->status) {
2942          $nids->condition('nid', 0);
2943        }
2944        $query->condition($nids);
2945        $query->range(0, 1);
2946  
2947        $grants = db_or();
2948        foreach (node_access_grants($op, $account) as $realm => $gids) {
2949          foreach ($gids as $gid) {
2950            $grants->condition(db_and()
2951              ->condition('gid', $gid)
2952              ->condition('realm', $realm)
2953            );
2954          }
2955        }
2956        if (count($grants) > 0) {
2957          $query->condition($grants);
2958        }
2959        $result =  (bool) $query
2960          ->execute()
2961          ->fetchField();
2962        $rights[$account->uid][$cid][$op] = $result;
2963        return $result;
2964      }
2965      elseif (is_object($node) && $op == 'view' && $node->status) {
2966        // If no modules implement hook_node_grants(), the default behavior is to
2967        // allow all users to view published nodes, so reflect that here.
2968        $rights[$account->uid][$cid][$op] = TRUE;
2969        return TRUE;
2970      }
2971    }
2972  
2973    return FALSE;
2974  }
2975  
2976  /**
2977   * Implements hook_node_access().
2978   */
2979  function node_node_access($node, $op, $account) {
2980    $type = is_string($node) ? $node : $node->type;
2981  
2982    if (in_array($type, node_permissions_get_configured_types())) {
2983      if ($op == 'create' && user_access('create ' . $type . ' content', $account)) {
2984        return NODE_ACCESS_ALLOW;
2985      }
2986  
2987      if ($op == 'update') {
2988        if (user_access('edit any ' . $type . ' content', $account) || (user_access('edit own ' . $type . ' content', $account) && ($account->uid == $node->uid))) {
2989          return NODE_ACCESS_ALLOW;
2990        }
2991      }
2992  
2993      if ($op == 'delete') {
2994        if (user_access('delete any ' . $type . ' content', $account) || (user_access('delete own ' . $type . ' content', $account) && ($account->uid == $node->uid))) {
2995          return NODE_ACCESS_ALLOW;
2996        }
2997      }
2998    }
2999  
3000    return NODE_ACCESS_IGNORE;
3001  }
3002  
3003  /**
3004   * Helper function to generate standard node permission list for a given type.
3005   *
3006   * @param $type
3007   *   The machine-readable name of the node type.
3008   * @return array
3009   *   An array of permission names and descriptions.
3010   */
3011  function node_list_permissions($type) {
3012    $info = node_type_get_type($type);
3013  
3014    // Build standard list of node permissions for this type.
3015    $perms = array(
3016      "create $type content" => array(
3017        'title' => t('%type_name: Create new content', array('%type_name' => $info->name)),
3018      ),
3019      "edit own $type content" => array(
3020        'title' => t('%type_name: Edit own content', array('%type_name' => $info->name)),
3021      ),
3022      "edit any $type content" => array(
3023        'title' => t('%type_name: Edit any content', array('%type_name' => $info->name)),
3024      ),
3025      "delete own $type content" => array(
3026        'title' => t('%type_name: Delete own content', array('%type_name' => $info->name)),
3027      ),
3028      "delete any $type content" => array(
3029        'title' => t('%type_name: Delete any content', array('%type_name' => $info->name)),
3030      ),
3031    );
3032  
3033    return $perms;
3034  }
3035  
3036  /**
3037   * Returns an array of node types that should be managed by permissions.
3038   *
3039   * By default, this will include all node types in the system. To exclude a
3040   * specific node from getting permissions defined for it, set the
3041   * node_permissions_$type variable to 0. Core does not provide an interface
3042   * for doing so, however, contrib modules may exclude their own nodes in
3043   * hook_install(). Alternatively, contrib modules may configure all node types
3044   * at once, or decide to apply some other hook_node_access() implementation
3045   * to some or all node types.
3046   *
3047   * @return
3048   *   An array of node types managed by this module.
3049   */
3050  function node_permissions_get_configured_types() {
3051  
3052    $configured_types = array();
3053  
3054    foreach (node_type_get_types() as $type => $info) {
3055      if (variable_get('node_permissions_' . $type, 1)) {
3056        $configured_types[] = $type;
3057      }
3058    }
3059  
3060    return $configured_types;
3061  }
3062  
3063  /**
3064   * Fetch an array of permission IDs granted to the given user ID.
3065   *
3066   * The implementation here provides only the universal "all" grant. A node
3067   * access module should implement hook_node_grants() to provide a grant
3068   * list for the user.
3069   *
3070   * After the default grants have been loaded, we allow modules to alter
3071   * the grants array by reference. This hook allows for complex business
3072   * logic to be applied when integrating multiple node access modules.
3073   *
3074   * @param $op
3075   *   The operation that the user is trying to perform.
3076   * @param $account
3077   *   The user object for the user performing the operation. If omitted, the
3078   *   current user is used.
3079   * @return
3080   *   An associative array in which the keys are realms, and the values are
3081   *   arrays of grants for those realms.
3082   */
3083  function node_access_grants($op, $account = NULL) {
3084  
3085    if (!isset($account)) {
3086      $account = $GLOBALS['user'];
3087    }
3088  
3089    // Fetch node access grants from other modules.
3090    $grants = module_invoke_all('node_grants', $account, $op);
3091    // Allow modules to alter the assigned grants.
3092    drupal_alter('node_grants', $grants, $account, $op);
3093  
3094    return array_merge(array('all' => array(0)), $grants);
3095  }
3096  
3097  /**
3098   * Determines whether the user has a global viewing grant for all nodes.
3099   *
3100   * Checks to see whether any module grants global 'view' access to a user
3101   * account; global 'view' access is encoded in the {node_access} table as a
3102   * grant with nid=0. If no node access modules are enabled, node.module defines
3103   * such a global 'view' access grant.
3104   *
3105   * This function is called when a node listing query is tagged with
3106   * 'node_access'; when this function returns TRUE, no node access joins are
3107   * added to the query.
3108   *
3109   * @param $account
3110   *   The user object for the user whose access is being checked. If omitted,
3111   *   the current user is used.
3112   *
3113   * @return
3114   *   TRUE if 'view' access to all nodes is granted, FALSE otherwise.
3115   *
3116   * @see hook_node_grants()
3117   * @see _node_query_node_access_alter()
3118   */
3119  function node_access_view_all_nodes($account = NULL) {
3120    global $user;
3121    if (!$account) {
3122      $account = $user;
3123    }
3124  
3125    // Statically cache results in an array keyed by $account->uid.
3126    $access = &drupal_static(__FUNCTION__);
3127    if (isset($access[$account->uid])) {
3128      return $access[$account->uid];
3129    }
3130  
3131    // If no modules implement the node access system, access is always TRUE.
3132    if (!module_implements('node_grants')) {
3133      $access[$account->uid] = TRUE;
3134    }
3135    else {
3136      $query = db_select('node_access');
3137      $query->addExpression('COUNT(*)');
3138      $query
3139        ->condition('nid', 0)
3140        ->condition('grant_view', 1, '>=');
3141  
3142      $grants = db_or();
3143      foreach (node_access_grants('view', $account) as $realm => $gids) {
3144        foreach ($gids as $gid) {
3145          $grants->condition(db_and()
3146            ->condition('gid', $gid)
3147            ->condition('realm', $realm)
3148          );
3149        }
3150      }
3151      if (count($grants) > 0 ) {
3152        $query->condition($grants);
3153      }
3154      $access[$account->uid] = $query
3155        ->execute()
3156        ->fetchField();
3157    }
3158  
3159    return $access[$account->uid];
3160  }
3161  
3162  
3163  /**
3164   * Implements hook_query_TAG_alter().
3165   *
3166   * This is the hook_query_alter() for queries tagged with 'node_access'.
3167   * It adds node access checks for the user account given by the 'account'
3168   * meta-data (or global $user if not provided), for an operation given by
3169   * the 'op' meta-data (or 'view' if not provided; other possible values are
3170   * 'update' and 'delete').
3171   */
3172  function node_query_node_access_alter(QueryAlterableInterface $query) {
3173    _node_query_node_access_alter($query, 'node');
3174  }
3175  
3176  /**
3177   * Implements hook_query_TAG_alter().
3178   *
3179   * This function implements the same functionality as
3180   * node_query_node_access_alter() for the SQL field storage engine. Node access
3181   * conditions are added for field values belonging to nodes only.
3182   */
3183  function node_query_entity_field_access_alter(QueryAlterableInterface $query) {
3184    _node_query_node_access_alter($query, 'entity');
3185  }
3186  
3187  /**
3188   * Helper for node access functions.
3189   *
3190   * @param $query
3191   *   The query to add conditions to.
3192   * @param $type
3193   *   Either 'node' or 'entity' depending on what sort of query it is. See
3194   *   node_query_node_access_alter() and node_query_entity_field_access_alter()
3195   *   for more.
3196   */
3197  function _node_query_node_access_alter($query, $type) {
3198    global $user;
3199  
3200    // Read meta-data from query, if provided.
3201    if (!$account = $query->getMetaData('account')) {
3202      $account = $user;
3203    }
3204    if (!$op = $query->getMetaData('op')) {
3205      $op = 'view';
3206    }
3207  
3208    // If $account can bypass node access, or there are no node access modules,
3209    // or the operation is 'view' and the $acount has a global view grant (i.e.,
3210    // a view grant for node ID 0), we don't need to alter the query.
3211    if (user_access('bypass node access', $account)) {
3212      return;
3213    }
3214    if (!count(module_implements('node_grants'))) {
3215      return;
3216    }
3217    if ($op == 'view' && node_access_view_all_nodes($account)) {
3218      return;
3219    }
3220  
3221    $tables = $query->getTables();
3222    $base_table = $query->getMetaData('base_table');
3223    // If no base table is specified explicitly, search for one.
3224    if (!$base_table) {
3225      $fallback = '';
3226      foreach ($tables as $alias => $table_info) {
3227        if (!($table_info instanceof SelectQueryInterface)) {
3228          $table = $table_info['table'];
3229          // If the node table is in the query, it wins immediately.
3230          if ($table == 'node') {
3231            $base_table = $table;
3232            break;
3233          }
3234          // Check whether the table has a foreign key to node.nid. If it does,
3235          // do not run this check again as we found a base table and only node
3236          // can triumph that.
3237          if (!$base_table) {
3238            // The schema is cached.
3239            $schema = drupal_get_schema($table);
3240            if (isset($schema['fields']['nid'])) {
3241              if (isset($schema['foreign keys'])) {
3242                foreach ($schema['foreign keys'] as $relation) {
3243                  if ($relation['table'] === 'node' && $relation['columns'] === array('nid' => 'nid')) {
3244                    $base_table = $table;
3245                  }
3246                }
3247              }
3248              else {
3249                // At least it's a nid. A table with a field called nid is very
3250                // very likely to be a node.nid in a node access query.
3251                $fallback = $table;
3252              }
3253            }
3254          }
3255        }
3256      }
3257      // If there is nothing else, use the fallback.
3258      if (!$base_table) {
3259        if ($fallback) {
3260          watchdog('security', 'Your node listing query is using @fallback as a base table in a query tagged for node access. This might not be secure and might not even work. Specify foreign keys in your schema to node.nid ', array('@fallback' => $fallback), WATCHDOG_WARNING);
3261          $base_table = $fallback;
3262        }
3263        else {
3264          throw new Exception(t('Query tagged for node access but there is no nid. Add foreign keys to node.nid in schema to fix.'));
3265        }
3266      }
3267    }
3268  
3269    // Find all instances of the base table being joined -- could appear
3270    // more than once in the query, and could be aliased. Join each one to
3271    // the node_access table.
3272  
3273    $grants = node_access_grants($op, $account);
3274    if ($type == 'entity') {
3275      // The original query looked something like:
3276      // @code
3277      //  SELECT nid FROM sometable s
3278      //  INNER JOIN node_access na ON na.nid = s.nid
3279      //  WHERE ($node_access_conditions)
3280      // @endcode
3281      //
3282      // Our query will look like:
3283      // @code
3284      //  SELECT entity_type, entity_id
3285      //  FROM field_data_something s
3286      //  LEFT JOIN node_access na ON s.entity_id = na.nid
3287      //  WHERE (entity_type = 'node' AND $node_access_conditions) OR (entity_type <> 'node')
3288      // @endcode
3289      //
3290      // So instead of directly adding to the query object, we need to collect
3291      // all of the node access conditions in a separate db_and() object and
3292      // then add it to the query at the end.
3293      $node_conditions = db_and();
3294    }
3295    foreach ($tables as $nalias => $tableinfo) {
3296      $table = $tableinfo['table'];
3297      if (!($table instanceof SelectQueryInterface) && $table == $base_table) {
3298        // Set the subquery.
3299        $subquery = db_select('node_access', 'na')
3300         ->fields('na', array('nid'));
3301  
3302        $grant_conditions = db_or();
3303        // If any grant exists for the specified user, then user has access
3304        // to the node for the specified operation.
3305        foreach ($grants as $realm => $gids) {
3306          foreach ($gids as $gid) {
3307            $grant_conditions->condition(db_and()
3308              ->condition('na.gid', $gid)
3309              ->condition('na.realm', $realm)
3310            );
3311          }
3312        }
3313  
3314        // Attach conditions to the subquery for nodes.
3315        if (count($grant_conditions->conditions())) {
3316          $subquery->condition($grant_conditions);
3317        }
3318        $subquery->condition('na.grant_' . $op, 1, '>=');
3319        $field = 'nid';
3320        // Now handle entities.
3321        if ($type == 'entity') {
3322          // Set a common alias for entities.
3323          $base_alias = $nalias;
3324          $field = 'entity_id';
3325        }
3326        $subquery->where("$nalias.$field = na.nid");
3327  
3328        // For an entity query, attach the subquery to entity conditions.
3329        if ($type == 'entity') {
3330          $node_conditions->exists($subquery);
3331        }
3332        // Otherwise attach it to the node query itself.
3333        else {
3334          $query->exists($subquery);
3335        }
3336      }
3337    }
3338  
3339    if ($type == 'entity' && count($subquery->conditions())) {
3340      // All the node access conditions are only for field values belonging to
3341      // nodes.
3342      $node_conditions->condition("$base_alias.entity_type", 'node');
3343      $or = db_or();
3344      $or->condition($node_conditions);
3345      // If the field value belongs to a non-node entity type then this function
3346      // does not do anything with it.
3347      $or->condition("$base_alias.entity_type", 'node', '<>');
3348      // Add the compiled set of rules to the query.
3349      $query->condition($or);
3350    }
3351  
3352  }
3353  
3354  /**
3355   * Gets the list of node access grants and writes them to the database.
3356   *
3357   * This function is called when a node is saved, and can also be called by
3358   * modules if something other than a node save causes node access permissions to
3359   * change. It collects all node access grants for the node from
3360   * hook_node_access_records() implementations, allows these grants to be altered
3361   * via hook_node_access_records_alter() implementations, and saves the collected
3362   * and altered grants to the database.
3363   *
3364   * @param $node
3365   *   The $node to acquire grants for.
3366   *
3367   * @param $delete
3368   *   Whether to delete existing node access records before inserting new ones.
3369   *   Defaults to TRUE.
3370   */
3371  function node_access_acquire_grants($node, $delete = TRUE) {
3372    $grants = module_invoke_all('node_access_records', $node);
3373    // Let modules alter the grants.
3374    drupal_alter('node_access_records', $grants, $node);
3375    // If no grants are set and the node is published, then use the default grant.
3376    if (empty($grants) && !empty($node->status)) {
3377      $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0);
3378    }
3379    else {
3380      // Retain grants by highest priority.
3381      $grant_by_priority = array();
3382      foreach ($grants as $g) {
3383        $grant_by_priority[intval($g['priority'])][] = $g;
3384      }
3385      krsort($grant_by_priority);
3386      $grants = array_shift($grant_by_priority);
3387    }
3388  
3389    node_access_write_grants($node, $grants, NULL, $delete);
3390  }
3391  
3392  /**
3393   * Writes a list of grants to the database, deleting any previously saved ones.
3394   *
3395   * If a realm is provided, it will only delete grants from that realm, but it
3396   * will always delete a grant from the 'all' realm. Modules that utilize
3397   * node_access can use this function when doing mass updates due to widespread
3398   * permission changes.
3399   *
3400   * Note: Don't call this function directly from a contributed module. Call
3401   * node_access_acquire_grants() instead.
3402   *
3403   * @param $node
3404   *   The $node being written to. All that is necessary is that it contains a
3405   *   nid.
3406   * @param $grants
3407   *   A list of grants to write. Each grant is an array that must contain the
3408   *   following keys: realm, gid, grant_view, grant_update, grant_delete.
3409   *   The realm is specified by a particular module; the gid is as well, and
3410   *   is a module-defined id to define grant privileges. each grant_* field
3411   *   is a boolean value.
3412   * @param $realm
3413   *   If provided, only read/write grants for that realm.
3414   * @param $delete
3415   *   If false, do not delete records. This is only for optimization purposes,
3416   *   and assumes the caller has already performed a mass delete of some form.
3417   */
3418  function node_access_write_grants($node, $grants, $realm = NULL, $delete = TRUE) {
3419    if ($delete) {
3420      $query = db_delete('node_access')->condition('nid', $node->nid);
3421      if ($realm) {
3422        $query->condition('realm', array($realm, 'all'), 'IN');
3423      }
3424      $query->execute();
3425    }
3426  
3427    // Only perform work when node_access modules are active.
3428    if (!empty($grants) && count(module_implements('node_grants'))) {
3429      $query = db_insert('node_access')->fields(array('nid', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete'));
3430      foreach ($grants as $grant) {
3431        if ($realm && $realm != $grant['realm']) {
3432          continue;
3433        }
3434        // Only write grants; denies are implicit.
3435        if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
3436          $grant['nid'] = $node->nid;
3437          $query->values($grant);
3438        }
3439      }
3440      $query->execute();
3441    }
3442  }
3443  
3444  /**
3445   * Flag / unflag the node access grants for rebuilding, or read the current
3446   * value of the flag.
3447   *
3448   * When the flag is set, a message is displayed to users with 'access
3449   * administration pages' permission, pointing to the 'rebuild' confirm form.
3450   * This can be used as an alternative to direct node_access_rebuild calls,
3451   * allowing administrators to decide when they want to perform the actual
3452   * (possibly time consuming) rebuild.
3453   * When unsure the current user is an administrator, node_access_rebuild
3454   * should be used instead.
3455   *
3456   * @param $rebuild
3457   *   (Optional) The boolean value to be written.
3458    * @return
3459   *   (If no value was provided for $rebuild) The current value of the flag.
3460   */
3461  function node_access_needs_rebuild($rebuild = NULL) {
3462    if (!isset($rebuild)) {
3463      return variable_get('node_access_needs_rebuild', FALSE);
3464    }
3465    elseif ($rebuild) {
3466      variable_set('node_access_needs_rebuild', TRUE);
3467    }
3468    else {
3469      variable_del('node_access_needs_rebuild');
3470    }
3471  }
3472  
3473  /**
3474   * Rebuild the node access database. This is occasionally needed by modules
3475   * that make system-wide changes to access levels.
3476   *
3477   * When the rebuild is required by an admin-triggered action (e.g module
3478   * settings form), calling node_access_needs_rebuild(TRUE) instead of
3479   * node_access_rebuild() lets the user perform his changes and actually
3480   * rebuild only once he is done.
3481   *
3482   * Note : As of Drupal 6, node access modules are not required to (and actually
3483   * should not) call node_access_rebuild() in hook_enable/disable anymore.
3484   *
3485   * @see node_access_needs_rebuild()
3486   *
3487   * @param $batch_mode
3488   *   Set to TRUE to process in 'batch' mode, spawning processing over several
3489   *   HTTP requests (thus avoiding the risk of PHP timeout if the site has a
3490   *   large number of nodes).
3491   *   hook_update_N and any form submit handler are safe contexts to use the
3492   *   'batch mode'. Less decidable cases (such as calls from hook_user,
3493   *   hook_taxonomy, etc...) might consider using the non-batch mode.
3494   */
3495  function node_access_rebuild($batch_mode = FALSE) {
3496    db_delete('node_access')->execute();
3497    // Only recalculate if the site is using a node_access module.
3498    if (count(module_implements('node_grants'))) {
3499      if ($batch_mode) {
3500        $batch = array(
3501          'title' => t('Rebuilding content access permissions'),
3502          'operations' => array(
3503            array('_node_access_rebuild_batch_operation', array()),
3504          ),
3505          'finished' => '_node_access_rebuild_batch_finished'
3506        );
3507        batch_set($batch);
3508      }
3509      else {
3510        // Try to allocate enough time to rebuild node grants
3511        drupal_set_time_limit(240);
3512  
3513        $nids = db_query("SELECT nid FROM {node}")->fetchCol();
3514        foreach ($nids as $nid) {
3515          $node = node_load($nid, NULL, TRUE);
3516          // To preserve database integrity, only acquire grants if the node
3517          // loads successfully.
3518          if (!empty($node)) {
3519            node_access_acquire_grants($node);
3520          }
3521        }
3522      }
3523    }
3524    else {
3525      // Not using any node_access modules. Add the default grant.
3526      db_insert('node_access')
3527        ->fields(array(
3528          'nid' => 0,
3529          'realm' => 'all',
3530          'gid' => 0,
3531          'grant_view' => 1,
3532          'grant_update' => 0,
3533          'grant_delete' => 0,
3534        ))
3535        ->execute();
3536    }
3537  
3538    if (!isset($batch)) {
3539      drupal_set_message(t('Content permissions have been rebuilt.'));
3540      node_access_needs_rebuild(FALSE);
3541      cache_clear_all();
3542    }
3543  }
3544  
3545  /**
3546   * Batch operation for node_access_rebuild_batch.
3547   *
3548   * This is a multistep operation : we go through all nodes by packs of 20.
3549   * The batch processing engine interrupts processing and sends progress
3550   * feedback after 1 second execution time.
3551   */
3552  function _node_access_rebuild_batch_operation(&$context) {
3553    if (empty($context['sandbox'])) {
3554      // Initiate multistep processing.
3555      $context['sandbox']['progress'] = 0;
3556      $context['sandbox']['current_node'] = 0;
3557      $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField();
3558    }
3559  
3560    // Process the next 20 nodes.
3561    $limit = 20;
3562    $nids = db_query_range("SELECT nid FROM {node} WHERE nid > :nid ORDER BY nid ASC", 0, $limit, array(':nid' => $context['sandbox']['current_node']))->fetchCol();
3563    $nodes = node_load_multiple($nids, array(), TRUE);
3564    foreach ($nodes as $nid => $node) {
3565      // To preserve database integrity, only acquire grants if the node
3566      // loads successfully.
3567      if (!empty($node)) {
3568        node_access_acquire_grants($node);
3569      }
3570      $context['sandbox']['progress']++;
3571      $context['sandbox']['current_node'] = $nid;
3572    }
3573  
3574    // Multistep processing : report progress.
3575    if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
3576      $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
3577    }
3578  }
3579  
3580  /**
3581   * Post-processing for node_access_rebuild_batch.
3582   */
3583  function _node_access_rebuild_batch_finished($success, $results, $operations) {
3584    if ($success) {
3585      drupal_set_message(t('The content access permissions have been rebuilt.'));
3586      node_access_needs_rebuild(FALSE);
3587    }
3588    else {
3589      drupal_set_message(t('The content access permissions have not been properly rebuilt.'), 'error');
3590    }
3591    cache_clear_all();
3592  }
3593  
3594  /**
3595   * @} End of "defgroup node_access".
3596   */
3597  
3598  
3599  /**
3600   * @defgroup node_content Hook implementations for user-created content types
3601   * @{
3602   * Functions that implement hooks for user-created content types.
3603   */
3604  
3605  /**
3606   * Implements hook_form().
3607   */
3608  function node_content_form($node, $form_state) {
3609    // It is impossible to define a content type without implementing hook_form()
3610    // @todo: remove this requirement.
3611    $form = array();
3612    $type = node_type_get_type($node);
3613  
3614    if ($type->has_title) {
3615      $form['title'] = array(
3616        '#type' => 'textfield',
3617        '#title' => check_plain($type->title_label),
3618        '#required' => TRUE,
3619        '#default_value' => $node->title,
3620        '#maxlength' => 255,
3621        '#weight' => -5,
3622      );
3623    }
3624  
3625    return $form;
3626  }
3627  
3628  /**
3629   * @} End of "defgroup node_content".
3630   */
3631  
3632  /**
3633   * Implements hook_forms().
3634   * All node forms share the same form handler.
3635   */
3636  function node_forms() {
3637    $forms = array();
3638    if ($types = node_type_get_types()) {
3639      foreach (array_keys($types) as $type) {
3640        $forms[$type . '_node_form']['callback'] = 'node_form';
3641      }
3642    }
3643    return $forms;
3644  }
3645  
3646  /**
3647   * Implements hook_action_info().
3648   */
3649  function node_action_info() {
3650    return array(
3651      'node_publish_action' => array(
3652        'type' => 'node',
3653        'label' => t('Publish content'),
3654        'configurable' => FALSE,
3655        'behavior' => array('changes_property'),
3656        'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
3657      ),
3658      'node_unpublish_action' => array(
3659        'type' => 'node',
3660        'label' => t('Unpublish content'),
3661        'configurable' => FALSE,
3662        'behavior' => array('changes_property'),
3663        'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
3664      ),
3665      'node_make_sticky_action' => array(
3666        'type' => 'node',
3667        'label' => t('Make content sticky'),
3668        'configurable' => FALSE,
3669        'behavior' => array('changes_property'),
3670        'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
3671      ),
3672      'node_make_unsticky_action' => array(
3673        'type' => 'node',
3674        'label' => t('Make content unsticky'),
3675        'configurable' => FALSE,
3676        'behavior' => array('changes_property'),
3677        'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
3678      ),
3679      'node_promote_action' => array(
3680        'type' => 'node',
3681        'label' => t('Promote content to front page'),
3682        'configurable' => FALSE,
3683        'behavior' => array('changes_property'),
3684        'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
3685      ),
3686      'node_unpromote_action' => array(
3687        'type' => 'node',
3688        'label' => t('Remove content from front page'),
3689        'configurable' => FALSE,
3690        'behavior' => array('changes_property'),
3691        'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
3692      ),
3693      'node_assign_owner_action' => array(
3694        'type' => 'node',
3695        'label' => t('Change the author of content'),
3696        'configurable' => TRUE,
3697        'behavior' => array('changes_property'),
3698        'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
3699      ),
3700      'node_save_action' => array(
3701        'type' => 'node',
3702        'label' => t('Save content'),
3703        'configurable' => FALSE,
3704        'triggers' => array('comment_insert', 'comment_update', 'comment_delete'),
3705      ),
3706      'node_unpublish_by_keyword_action' => array(
3707        'type' => 'node',
3708        'label' => t('Unpublish content containing keyword(s)'),
3709        'configurable' => TRUE,
3710        'triggers' => array('node_presave', 'node_insert', 'node_update'),
3711      ),
3712    );
3713  }
3714  
3715  /**
3716   * Sets the status of a node to 1 (published).
3717   *
3718   * @ingroup actions
3719   */
3720  function node_publish_action($node, $context = array()) {
3721    $node->status = NODE_PUBLISHED;
3722    watchdog('action', 'Set @type %title to published.', array('@type' => node_type_get_name($node), '%title' => $node->title));
3723  }
3724  
3725  /**
3726   * Sets the status of a node to 0 (unpublished).
3727   *
3728   * @ingroup actions
3729   */
3730  function node_unpublish_action($node, $context = array()) {
3731    $node->status = NODE_NOT_PUBLISHED;
3732    watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_type_get_name($node), '%title' => $node->title));
3733  }
3734  
3735  /**
3736   * Sets the sticky-at-top-of-list property of a node to 1.
3737   *
3738   * @ingroup actions
3739   */
3740  function node_make_sticky_action($node, $context = array()) {
3741    $node->sticky = NODE_STICKY;
3742    watchdog('action', 'Set @type %title to sticky.', array('@type' => node_type_get_name($node), '%title' => $node->title));
3743  }
3744  
3745  /**
3746   * Sets the sticky-at-top-of-list property of a node to 0.
3747   *
3748   * @ingroup actions
3749   */
3750  function node_make_unsticky_action($node, $context = array()) {
3751    $node->sticky = NODE_NOT_STICKY;
3752    watchdog('action', 'Set @type %title to unsticky.', array('@type' => node_type_get_name($node), '%title' => $node->title));
3753  }
3754  
3755  /**
3756   * Sets the promote property of a node to 1.
3757   *
3758   * @ingroup actions
3759   */
3760  function node_promote_action($node, $context = array()) {
3761    $node->promote = NODE_PROMOTED;
3762    watchdog('action', 'Promoted @type %title to front page.', array('@type' => node_type_get_name($node), '%title' => $node->title));
3763  }
3764  
3765  /**
3766   * Sets the promote property of a node to 0.
3767   *
3768   * @ingroup actions
3769   */
3770  function node_unpromote_action($node, $context = array()) {
3771    $node->promote = NODE_NOT_PROMOTED;
3772    watchdog('action', 'Removed @type %title from front page.', array('@type' => node_type_get_name($node), '%title' => $node->title));
3773  }
3774  
3775  /**
3776   * Saves a node.
3777   *
3778   * @ingroup actions
3779   */
3780  function node_save_action($node) {
3781    node_save($node);
3782    watchdog('action', 'Saved @type %title', array('@type' => node_type_get_name($node), '%title' => $node->title));
3783  }
3784  
3785  /**
3786   * Assigns ownership of a node to a user.
3787   *
3788   * @param $node
3789   *   A node object to modify.
3790   * @param $context
3791   *   Array with the following elements:
3792   *   - 'owner_uid': User ID to assign to the node.
3793   *
3794   * @ingroup actions
3795   */
3796  function node_assign_owner_action($node, $context) {
3797    $node->uid = $context['owner_uid'];
3798    $owner_name = db_query("SELECT name FROM {users} WHERE uid = :uid", array(':uid' => $context['owner_uid']))->fetchField();
3799    watchdog('action', 'Changed owner of @type %title to uid %name.', array('@type' =>  node_type_get_name($node), '%title' => $node->title, '%name' => $owner_name));
3800  }
3801  
3802  /**
3803   * Generates the settings form for node_assign_owner_action().
3804   */
3805  function node_assign_owner_action_form($context) {
3806    $description = t('The username of the user to which you would like to assign ownership.');
3807    $count = db_query("SELECT COUNT(*) FROM {users}")->fetchField();
3808    $owner_name = '';
3809    if (isset($context['owner_uid'])) {
3810      $owner_name = db_query("SELECT name FROM {users} WHERE uid = :uid", array(':uid' => $context['owner_uid']))->fetchField();
3811    }
3812  
3813    // Use dropdown for fewer than 200 users; textbox for more than that.
3814    if (intval($count) < 200) {
3815      $options = array();
3816      $result = db_query("SELECT uid, name FROM {users} WHERE uid > 0 ORDER BY name");
3817      foreach ($result as $data) {
3818        $options[$data->name] = $data->name;
3819      }
3820      $form['owner_name'] = array(
3821        '#type' => 'select',
3822        '#title' => t('Username'),
3823        '#default_value' => $owner_name,
3824        '#options' => $options,
3825        '#description' => $description,
3826      );
3827    }
3828    else {
3829      $form['owner_name'] = array(
3830        '#type' => 'textfield',
3831        '#title' => t('Username'),
3832        '#default_value' => $owner_name,
3833        '#autocomplete_path' => 'user/autocomplete',
3834        '#size' => '6',
3835        '#maxlength' => '60',
3836        '#description' => $description,
3837      );
3838    }
3839    return $form;
3840  }
3841  
3842  /**
3843   * Validates settings form for node_assign_owner_action().
3844   */
3845  function node_assign_owner_action_validate($form, $form_state) {
3846    $exists = (bool) db_query_range('SELECT 1 FROM {users} WHERE name = :name', 0, 1, array(':name' => $form_state['values']['owner_name']))->fetchField();
3847    if (!$exists) {
3848      form_set_error('owner_name', t('Enter a valid username.'));
3849    }
3850  }
3851  
3852  /**
3853   * Saves settings form for node_assign_owner_action().
3854   */
3855  function node_assign_owner_action_submit($form, $form_state) {
3856    // Username can change, so we need to store the ID, not the username.
3857    $uid = db_query('SELECT uid from {users} WHERE name = :name', array(':name' => $form_state['values']['owner_name']))->fetchField();
3858    return array('owner_uid' => $uid);
3859  }
3860  
3861  /**
3862   * Generates settings form for node_unpublish_by_keyword_action().
3863   */
3864  function node_unpublish_by_keyword_action_form($context) {
3865    $form['keywords'] = array(
3866      '#title' => t('Keywords'),
3867      '#type' => 'textarea',
3868      '#description' => t('The content will be unpublished if it contains any of the phrases above. Use a case-sensitive, comma-separated list of phrases. Example: funny, bungee jumping, "Company, Inc."'),
3869      '#default_value' => isset($context['keywords']) ? drupal_implode_tags($context['keywords']) : '',
3870    );
3871    return $form;
3872  }
3873  
3874  /**
3875   * Saves settings form for node_unpublish_by_keyword_action().
3876   */
3877  function node_unpublish_by_keyword_action_submit($form, $form_state) {
3878    return array('keywords' => drupal_explode_tags($form_state['values']['keywords']));
3879  }
3880  
3881  /**
3882   * Unpublishes a node containing certain keywords.
3883   *
3884   * @param $node
3885   *   A node object to modify.
3886   * @param $context
3887   *   Array with the following elements:
3888   *   - 'keywords': Array of keywords. If any keyword is present in the rendered
3889   *     node, the node's status flag is set to unpublished.
3890   *
3891   * @ingroup actions
3892   */
3893  function node_unpublish_by_keyword_action($node, $context) {
3894    foreach ($context['keywords'] as $keyword) {
3895      $elements = node_view(clone $node);
3896      if (strpos(drupal_render($elements), $keyword) !== FALSE || strpos($node->title, $keyword) !== FALSE) {
3897        $node->status = NODE_NOT_PUBLISHED;
3898        watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_type_get_name($node), '%title' => $node->title));
3899        break;
3900      }
3901    }
3902  }
3903  
3904  /**
3905   * Implements hook_requirements().
3906   */
3907  function node_requirements($phase) {
3908    $requirements = array();
3909    if ($phase === 'runtime') {
3910      // Only show rebuild button if there are either 0, or 2 or more, rows
3911      // in the {node_access} table, or if there are modules that
3912      // implement hook_node_grants().
3913      $grant_count = db_query('SELECT COUNT(*) FROM {node_access}')->fetchField();
3914      if ($grant_count != 1 || count(module_implements('node_grants')) > 0) {
3915        $value = format_plural($grant_count, 'One permission in use', '@count permissions in use', array('@count' => $grant_count));
3916      }
3917      else {
3918        $value = t('Disabled');
3919      }
3920      $description = t('If the site is experiencing problems with permissions to content, you may have to rebuild the permissions cache. Rebuilding will remove all privileges to content and replace them with permissions based on the current modules and settings. Rebuilding may take some time if there is a lot of content or complex permission settings. After rebuilding has completed, content will automatically use the new permissions.');
3921  
3922      $requirements['node_access'] = array(
3923        'title' => t('Node Access Permissions'),
3924        'value' => $value,
3925        'description' => $description . ' ' . l(t('Rebuild permissions'), 'admin/reports/status/rebuild'),
3926      );
3927    }
3928    return $requirements;
3929  }
3930  
3931  /**
3932   * Implements hook_modules_enabled().
3933   */
3934  function node_modules_enabled($modules) {
3935    // Check if any of the newly enabled modules require the node_access table to
3936    // be rebuilt.
3937    if (!node_access_needs_rebuild() && array_intersect($modules, module_implements('node_grants'))) {
3938      node_access_needs_rebuild(TRUE);
3939    }
3940  }
3941  
3942  /**
3943   * Controller class for nodes.
3944   *
3945   * This extends the DrupalDefaultEntityController class, adding required
3946   * special handling for node objects.
3947   */
3948  class NodeController extends DrupalDefaultEntityController {
3949  
3950    protected function attachLoad(&$nodes, $revision_id = FALSE) {
3951      // Create an array of nodes for each content type and pass this to the
3952      // object type specific callback.
3953      $typed_nodes = array();
3954      foreach ($nodes as $id => $entity) {
3955        $typed_nodes[$entity->type][$id] = $entity;
3956      }
3957  
3958      // Call object type specific callbacks on each typed array of nodes.
3959      foreach ($typed_nodes as $node_type => $nodes_of_type) {
3960        if (node_hook($node_type, 'load')) {
3961          $function = node_type_get_base($node_type) . '_load';
3962          $function($nodes_of_type);
3963        }
3964      }
3965      // Besides the list of nodes, pass one additional argument to
3966      // hook_node_load(), containing a list of node types that were loaded.
3967      $argument = array_keys($typed_nodes);
3968      $this->hookLoadArguments = array($argument);
3969      parent::attachLoad($nodes, $revision_id);
3970    }
3971  
3972    protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
3973      // Ensure that uid is taken from the {node} table,
3974      // alias timestamp to revision_timestamp and add revision_uid.
3975      $query = parent::buildQuery($ids, $conditions, $revision_id);
3976      $fields =& $query->getFields();
3977      unset($fields['timestamp']);
3978      $query->addField('revision', 'timestamp', 'revision_timestamp');
3979      $fields['uid']['table'] = 'base';
3980      $query->addField('revision', 'uid', 'revision_uid');
3981      return $query;
3982    }
3983  }
3984  
3985  /**
3986   * Implements hook_file_download_access().
3987   */
3988  function node_file_download_access($field, $entity_type, $entity) {
3989    if ($entity_type == 'node') {
3990      return node_access('view', $entity);
3991    }
3992  }

title

Description

title

Description

title

Description

title

title

Body