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