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