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