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