[ Index ] |
|
Code source de Drupal 5.3 |
1 <?php 2 // $Id: node.module,v 1.776.2.21 2007/09/29 23:41:28 drumm 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 define('NODE_NEW_LIMIT', time() - 30 * 24 * 60 * 60); 11 12 /** 13 * Implementation of hook_help(). 14 */ 15 function node_help($section) { 16 switch ($section) { 17 case 'admin/help#node': 18 $output = '<p>'. t('All content in a website is stored and treated as <b>nodes</b>. Therefore nodes are any postings such as blogs, stories, polls and forums. The node module manages these content types and is one of the strengths of Drupal over other content management systems.') .'</p>'; 19 $output .= '<p>'. t('Treating all content as nodes allows the flexibility of creating new types of content. It also allows you to painlessly apply new features or changes to all content. Comments are not stored as nodes but are always associated with a node.') .'</p>'; 20 $output .= t('<p>Node module features</p> 21 <ul> 22 <li>The list tab provides an interface to search and sort all content on your site.</li> 23 <li>The configure settings tab has basic settings for content on your site.</li> 24 <li>The configure content types tab lists all content types for your site and lets you configure their default workflow.</li> 25 <li>The search tab lets you search all content on your site</li> 26 </ul> 27 '); 28 $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="@node">Node page</a>.', array('@node' => 'http://drupal.org/handbook/modules/node/')) .'</p>'; 29 return $output; 30 case 'admin/content/search': 31 return '<p>'. t('Enter a simple pattern to search for a post. Words are matched exactly. Phrases can be surrounded by quotes to do an exact search.') .'</p>'; 32 case 'admin/content/types': 33 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>'; 34 case 'admin/content/types/add': 35 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>'; 36 } 37 38 if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == 'revisions' && arg(3) == NULL) { 39 return '<p>'. t('The revisions let you track differences between multiple versions of a post.') .'</p>'; 40 } 41 42 if (arg(0) == 'node' && arg(1) == 'add' && $type = arg(2)) { 43 $type = node_get_types('type', str_replace('-', '_', arg(2))); 44 return '<p>'. filter_xss_admin($type->help) .'</p>'; 45 } 46 } 47 48 /** 49 * Implementation of hook_cron(). 50 */ 51 function node_cron() { 52 db_query('DELETE FROM {history} WHERE timestamp < %d', NODE_NEW_LIMIT); 53 } 54 55 /** 56 * Gather a listing of links to nodes. 57 * 58 * @param $result 59 * A DB result object from a query to fetch node objects. If your query joins the <code>node_comment_statistics</code> table so that the <code>comment_count</code> field is available, a title attribute will be added to show the number of comments. 60 * @param $title 61 * A heading for the resulting list. 62 * 63 * @return 64 * An HTML list suitable as content for a block. 65 */ 66 function node_title_list($result, $title = NULL) { 67 while ($node = db_fetch_object($result)) { 68 $items[] = l($node->title, 'node/'. $node->nid, $node->comment_count ? array('title' => format_plural($node->comment_count, '1 comment', '@count comments')) : ''); 69 } 70 71 return theme('node_list', $items, $title); 72 } 73 74 /** 75 * Format a listing of links to nodes. 76 */ 77 function theme_node_list($items, $title = NULL) { 78 return theme('item_list', $items, $title); 79 } 80 81 /** 82 * Update the 'last viewed' timestamp of the specified node for current user. 83 */ 84 function node_tag_new($nid) { 85 global $user; 86 87 if ($user->uid) { 88 if (node_last_viewed($nid)) { 89 db_query('UPDATE {history} SET timestamp = %d WHERE uid = %d AND nid = %d', time(), $user->uid, $nid); 90 } 91 else { 92 @db_query('INSERT INTO {history} (uid, nid, timestamp) VALUES (%d, %d, %d)', $user->uid, $nid, time()); 93 } 94 } 95 } 96 97 /** 98 * Retrieves the timestamp at which the current user last viewed the 99 * specified node. 100 */ 101 function node_last_viewed($nid) { 102 global $user; 103 static $history; 104 105 if (!isset($history[$nid])) { 106 $history[$nid] = db_fetch_object(db_query("SELECT timestamp FROM {history} WHERE uid = %d AND nid = %d", $user->uid, $nid)); 107 } 108 109 return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp : 0); 110 } 111 112 /** 113 * Decide on the type of marker to be displayed for a given node. 114 * 115 * @param $nid 116 * Node ID whose history supplies the "last viewed" timestamp. 117 * @param $timestamp 118 * Time which is compared against node's "last viewed" timestamp. 119 * @return 120 * One of the MARK constants. 121 */ 122 function node_mark($nid, $timestamp) { 123 global $user; 124 static $cache; 125 126 if (!$user->uid) { 127 return MARK_READ; 128 } 129 if (!isset($cache[$nid])) { 130 $cache[$nid] = node_last_viewed($nid); 131 } 132 if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) { 133 return MARK_NEW; 134 } 135 elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) { 136 return MARK_UPDATED; 137 } 138 return MARK_READ; 139 } 140 141 /** 142 * Automatically generate a teaser for a node body in a given format. 143 */ 144 function node_teaser($body, $format = NULL) { 145 146 $size = variable_get('teaser_length', 600); 147 148 // Find where the delimiter is in the body 149 $delimiter = strpos($body, '<!--break-->'); 150 151 // If the size is zero, and there is no delimiter, the entire body is the teaser. 152 if ($size == 0 && $delimiter === FALSE) { 153 return $body; 154 } 155 156 // If a valid delimiter has been specified, use it to chop off the teaser. 157 if ($delimiter !== FALSE) { 158 return substr($body, 0, $delimiter); 159 } 160 161 // We check for the presence of the PHP evaluator filter in the current 162 // format. If the body contains PHP code, we do not split it up to prevent 163 // parse errors. 164 if (isset($format)) { 165 $filters = filter_list_format($format); 166 if (isset($filters['filter/1']) && strpos($body, '<?') !== FALSE) { 167 return $body; 168 } 169 } 170 171 // If we have a short body, the entire body is the teaser. 172 if (strlen($body) < $size) { 173 return $body; 174 } 175 176 // The teaser may not be longer than maximum length specified. Initial slice. 177 $teaser = truncate_utf8($body, $size); 178 $position = 0; 179 // Cache the reverse of the teaser. 180 $reversed = strrev($teaser); 181 182 // In some cases, no delimiter has been specified. In this case, we try to 183 // split at paragraph boundaries. 184 $breakpoints = array('</p>' => 0, '<br />' => 6, '<br>' => 4, "\n" => 1); 185 // We use strpos on the reversed needle and haystack for speed. 186 foreach ($breakpoints as $point => $offset) { 187 $length = strpos($reversed, strrev($point)); 188 if ($length !== FALSE) { 189 $position = - $length - $offset; 190 return ($position == 0) ? $teaser : substr($teaser, 0, $position); 191 } 192 } 193 194 // When even the first paragraph is too long, we try to split at the end of 195 // the last full sentence. 196 $breakpoints = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟ ' => 1); 197 $min_length = strlen($reversed); 198 foreach ($breakpoints as $point => $offset) { 199 $length = strpos($reversed, strrev($point)); 200 if ($length !== FALSE) { 201 $min_length = min($length, $min_length); 202 $position = 0 - $length - $offset; 203 } 204 } 205 return ($position == 0) ? $teaser : substr($teaser, 0, $position); 206 } 207 208 /** 209 * Builds a list of available node types, and returns all of part of this list 210 * in the specified format. 211 * 212 * @param $op 213 * The format in which to return the list. When this is set to 'type', 214 * 'module', or 'name', only the specified node type is returned. When set to 215 * 'types' or 'names', all node types are returned. 216 * @param $node 217 * A node object, array, or string that indicates the node type to return. 218 * Leave at default value (NULL) to return a list of all node types. 219 * @param $reset 220 * Whether or not to reset this function's internal cache (defaults to 221 * FALSE). 222 * 223 * @return 224 * Either an array of all available node types, or a single node type, in a 225 * variable format. 226 */ 227 function node_get_types($op = 'types', $node = NULL, $reset = FALSE) { 228 static $_node_types, $_node_names; 229 230 if ($reset || !isset($_node_types)) { 231 list($_node_types, $_node_names) = _node_types_build(); 232 } 233 234 if ($node) { 235 if (is_array($node)) { 236 $type = $node['type']; 237 } 238 elseif (is_object($node)) { 239 $type = $node->type; 240 } 241 elseif (is_string($node)) { 242 $type = $node; 243 } 244 if (!isset($_node_types[$type])) { 245 return FALSE; 246 } 247 } 248 switch ($op) { 249 case 'types': 250 return $_node_types; 251 case 'type': 252 return $_node_types[$type]; 253 case 'module': 254 return $_node_types[$type]->module; 255 case 'names': 256 return $_node_names; 257 case 'name': 258 return $_node_names[$type]; 259 } 260 } 261 262 /** 263 * Resets the database cache of node types, and saves all new or non-modified 264 * module-defined node types to the database. 265 */ 266 function node_types_rebuild() { 267 _node_types_build(); 268 269 $node_types = node_get_types('types', NULL, TRUE); 270 271 foreach ($node_types as $type => $info) { 272 if (!empty($info->is_new)) { 273 node_type_save($info); 274 } 275 if (!empty($info->disabled)) { 276 node_type_delete($info->type); 277 } 278 } 279 280 _node_types_build(); 281 } 282 283 /** 284 * Saves a node type to the database. 285 * 286 * @param $info 287 * The node type to save, as an object. 288 * 289 * @return 290 * Status flag indicating outcome of the operation. 291 */ 292 function node_type_save($info) { 293 $is_existing = FALSE; 294 $existing_type = !empty($info->old_type) ? $info->old_type : $info->type; 295 $is_existing = db_num_rows(db_query("SELECT * FROM {node_type} WHERE type = '%s'", $existing_type)); 296 297 if ($is_existing) { 298 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); 299 300 module_invoke_all('node_type', 'update', $info); 301 return SAVED_UPDATED; 302 } 303 else { 304 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); 305 306 module_invoke_all('node_type', 'insert', $info); 307 return SAVED_NEW; 308 } 309 } 310 311 /** 312 * Deletes a node type from the database. 313 * 314 * @param $type 315 * The machine-readable name of the node type to be deleted. 316 */ 317 function node_type_delete($type) { 318 db_query("DELETE FROM {node_type} WHERE type = '%s'", $type); 319 320 $info = node_get_types('type', $type); 321 module_invoke_all('node_type', 'delete', $info); 322 } 323 324 /** 325 * Updates all nodes of one type to be of another type. 326 * 327 * @param $old_type 328 * The current node type of the nodes. 329 * @param $type 330 * The new node type of the nodes. 331 * 332 * @return 333 * The number of nodes whose node type field was modified. 334 */ 335 function node_type_update_nodes($old_type, $type) { 336 db_query("UPDATE {node} SET type = '%s' WHERE type = '%s'", $type, $old_type); 337 return db_affected_rows(); 338 } 339 340 /** 341 * Builds and returns the list of available node types. 342 * 343 * The list of types is built by querying hook_node_info() in all modules, and 344 * by comparing this information with the node types in the {node_type} table. 345 * 346 */ 347 function _node_types_build() { 348 $_node_types = array(); 349 $_node_names = array(); 350 351 $info_array = module_invoke_all('node_info'); 352 foreach ($info_array as $type => $info) { 353 $info['type'] = $type; 354 $_node_types[$type] = (object) _node_type_set_defaults($info); 355 $_node_names[$type] = $info['name']; 356 } 357 358 $type_result = db_query(db_rewrite_sql('SELECT nt.type, nt.* FROM {node_type} nt ORDER BY nt.type ASC', 'nt', 'type')); 359 while ($type_object = db_fetch_object($type_result)) { 360 // Check for node types from disabled modules and mark their types for removal. 361 // Types defined by the node module in the database (rather than by a separate 362 // module using hook_node_info) have a module value of 'node'. 363 if ($type_object->module != 'node' && empty($info_array[$type_object->type])) { 364 $type_object->disabled = TRUE; 365 } 366 if (!isset($_node_types[$type_object->type]) || $type_object->modified) { 367 $_node_types[$type_object->type] = $type_object; 368 $_node_names[$type_object->type] = $type_object->name; 369 370 if ($type_object->type != $type_object->orig_type) { 371 unset($_node_types[$type_object->orig_type]); 372 unset($_node_names[$type_object->orig_type]); 373 } 374 } 375 } 376 377 asort($_node_names); 378 379 return array($_node_types, $_node_names); 380 } 381 382 /** 383 * Set default values for a node type defined through hook_node_info(). 384 */ 385 function _node_type_set_defaults($info) { 386 if (!isset($info['has_title'])) { 387 $info['has_title'] = TRUE; 388 } 389 if ($info['has_title'] && !isset($info['title_label'])) { 390 $info['title_label'] = t('Title'); 391 } 392 393 if (!isset($info['has_body'])) { 394 $info['has_body'] = TRUE; 395 } 396 if ($info['has_body'] && !isset($info['body_label'])) { 397 $info['body_label'] = t('Body'); 398 } 399 400 if (!isset($info['help'])) { 401 $info['help'] = ''; 402 } 403 if (!isset($info['min_word_count'])) { 404 $info['min_word_count'] = 0; 405 } 406 if (!isset($info['custom'])) { 407 $info['custom'] = FALSE; 408 } 409 if (!isset($info['modified'])) { 410 $info['modified'] = FALSE; 411 } 412 if (!isset($info['locked'])) { 413 $info['locked'] = TRUE; 414 } 415 416 $info['orig_type'] = $info['type']; 417 $info['is_new'] = TRUE; 418 419 return $info; 420 } 421 422 /** 423 * Determine whether a node hook exists. 424 * 425 * @param &$node 426 * Either a node object, node array, or a string containing the node type. 427 * @param $hook 428 * A string containing the name of the hook. 429 * @return 430 * TRUE iff the $hook exists in the node type of $node. 431 */ 432 function node_hook(&$node, $hook) { 433 $module = node_get_types('module', $node); 434 if ($module == 'node') { 435 $module = 'node_content'; // Avoid function name collisions. 436 } 437 return module_hook($module, $hook); 438 } 439 440 /** 441 * Invoke a node hook. 442 * 443 * @param &$node 444 * Either a node object, node array, or a string containing the node type. 445 * @param $hook 446 * A string containing the name of the hook. 447 * @param $a2, $a3, $a4 448 * Arguments to pass on to the hook, after the $node argument. 449 * @return 450 * The returned value of the invoked hook. 451 */ 452 function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) { 453 if (node_hook($node, $hook)) { 454 $module = node_get_types('module', $node); 455 if ($module == 'node') { 456 $module = 'node_content'; // Avoid function name collisions. 457 } 458 $function = $module .'_'. $hook; 459 return ($function($node, $a2, $a3, $a4)); 460 } 461 } 462 463 /** 464 * Invoke a hook_nodeapi() operation in all modules. 465 * 466 * @param &$node 467 * A node object. 468 * @param $op 469 * A string containing the name of the nodeapi operation. 470 * @param $a3, $a4 471 * Arguments to pass on to the hook, after the $node and $op arguments. 472 * @return 473 * The returned value of the invoked hooks. 474 */ 475 function node_invoke_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { 476 $return = array(); 477 foreach (module_implements('nodeapi') as $name) { 478 $function = $name .'_nodeapi'; 479 $result = $function($node, $op, $a3, $a4); 480 if (isset($result) && is_array($result)) { 481 $return = array_merge($return, $result); 482 } 483 else if (isset($result)) { 484 $return[] = $result; 485 } 486 } 487 return $return; 488 } 489 490 /** 491 * Load a node object from the database. 492 * 493 * @param $param 494 * Either the nid of the node or an array of conditions to match against in the database query 495 * @param $revision 496 * Which numbered revision to load. Defaults to the current version. 497 * @param $reset 498 * Whether to reset the internal node_load cache. 499 * 500 * @return 501 * A fully-populated node object. 502 */ 503 function node_load($param = array(), $revision = NULL, $reset = NULL) { 504 static $nodes = array(); 505 506 if ($reset) { 507 $nodes = array(); 508 } 509 510 $cachable = ($revision == NULL); 511 $arguments = array(); 512 if (is_numeric($param)) { 513 if ($cachable && isset($nodes[$param])) { 514 return is_object($nodes[$param]) ? drupal_clone($nodes[$param]) : $nodes[$param]; 515 } 516 $cond = 'n.nid = %d'; 517 $arguments[] = $param; 518 } 519 else { 520 // Turn the conditions into a query. 521 foreach ($param as $key => $value) { 522 $cond[] = 'n.'. db_escape_string($key) ." = '%s'"; 523 $arguments[] = $value; 524 } 525 $cond = implode(' AND ', $cond); 526 } 527 528 // Retrieve the node. 529 // No db_rewrite_sql is applied so as to get complete indexing for search. 530 if ($revision) { 531 array_unshift($arguments, $revision); 532 $node = db_fetch_object(db_query('SELECT n.nid, r.vid, n.type, n.status, n.created, n.changed, n.comment, n.promote, n.sticky, r.timestamp AS revision_timestamp, r.title, r.body, r.teaser, r.log, r.format, u.uid, u.name, u.picture, u.data 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)); 533 } 534 else { 535 $node = db_fetch_object(db_query('SELECT n.nid, n.vid, n.type, n.status, n.created, n.changed, n.comment, n.promote, n.sticky, r.timestamp AS revision_timestamp, r.title, r.body, r.teaser, r.log, r.format, u.uid, u.name, u.picture, u.data 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)); 536 } 537 538 if ($node->nid) { 539 // Call the node specific callback (if any) and piggy-back the 540 // results to the node or overwrite some values. 541 if ($extra = node_invoke($node, 'load')) { 542 foreach ($extra as $key => $value) { 543 $node->$key = $value; 544 } 545 } 546 547 if ($extra = node_invoke_nodeapi($node, 'load')) { 548 foreach ($extra as $key => $value) { 549 $node->$key = $value; 550 } 551 } 552 if ($cachable) { 553 $nodes[$node->nid] = is_object($node) ? drupal_clone($node) : $node; 554 } 555 } 556 557 return $node; 558 } 559 560 /** 561 * Save a node object into the database. 562 */ 563 function node_save(&$node) { 564 global $user; 565 566 $node->is_new = FALSE; 567 568 // Apply filters to some default node fields: 569 if (empty($node->nid)) { 570 // Insert a new node. 571 $node->is_new = TRUE; 572 573 $node->nid = db_next_id('{node}_nid'); 574 $node->vid = db_next_id('{node_revisions}_vid'); 575 } 576 else { 577 // We need to ensure that all node fields are filled. 578 $node_current = node_load($node->nid); 579 foreach ($node as $field => $data) { 580 $node_current->$field = $data; 581 } 582 $node = $node_current; 583 584 if ($node->revision) { 585 $node->old_vid = $node->vid; 586 $node->vid = db_next_id('{node_revisions}_vid'); 587 } 588 } 589 590 // Set some required fields: 591 if (empty($node->created)) { 592 $node->created = time(); 593 } 594 // The changed timestamp is always updated for bookkeeping purposes (revisions, searching, ...) 595 $node->changed = time(); 596 597 // Split off revisions data to another structure 598 $revisions_table_values = array('nid' => $node->nid, 'vid' => $node->vid, 599 'title' => $node->title, 'body' => $node->body, 600 'teaser' => $node->teaser, 'timestamp' => $node->changed, 601 'uid' => $user->uid, 'format' => $node->format); 602 $revisions_table_types = array('nid' => '%d', 'vid' => '%d', 603 'title' => "'%s'", 'body' => "'%s'", 604 'teaser' => "'%s'", 'timestamp' => '%d', 605 'uid' => '%d', 'format' => '%d'); 606 if (!empty($node->log) || $node->is_new || $node->revision) { 607 // Only store the log message if there's something to store; this prevents 608 // existing log messages from being unintentionally overwritten by a blank 609 // message. A new revision will have an empty log message (or $node->log). 610 $revisions_table_values['log'] = $node->log; 611 $revisions_table_types['log'] = "'%s'"; 612 } 613 $node_table_values = array('nid' => $node->nid, 'vid' => $node->vid, 614 'title' => $node->title, 'type' => $node->type, 'uid' => $node->uid, 615 'status' => $node->status, 'created' => $node->created, 616 'changed' => $node->changed, 'comment' => $node->comment, 617 'promote' => $node->promote, 'sticky' => $node->sticky); 618 $node_table_types = array('nid' => '%d', 'vid' => '%d', 619 'title' => "'%s'", 'type' => "'%s'", 'uid' => '%d', 620 'status' => '%d', 'created' => '%d', 621 'changed' => '%d', 'comment' => '%d', 622 'promote' => '%d', 'sticky' => '%d'); 623 624 //Generate the node table query and the 625 //the node_revisions table query 626 if ($node->is_new) { 627 $node_query = 'INSERT INTO {node} ('. implode(', ', array_keys($node_table_types)) .') VALUES ('. implode(', ', $node_table_types) .')'; 628 $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')'; 629 } 630 else { 631 $arr = array(); 632 foreach ($node_table_types as $key => $value) { 633 $arr[] = $key .' = '. $value; 634 } 635 $node_table_values[] = $node->nid; 636 $node_query = 'UPDATE {node} SET '. implode(', ', $arr) .' WHERE nid = %d'; 637 if ($node->revision) { 638 $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')'; 639 } 640 else { 641 $arr = array(); 642 foreach ($revisions_table_types as $key => $value) { 643 $arr[] = $key .' = '. $value; 644 } 645 $revisions_table_values[] = $node->vid; 646 $revisions_query = 'UPDATE {node_revisions} SET '. implode(', ', $arr) .' WHERE vid = %d'; 647 } 648 } 649 650 // Insert the node into the database: 651 db_query($node_query, $node_table_values); 652 db_query($revisions_query, $revisions_table_values); 653 654 // Call the node specific callback (if any): 655 if ($node->is_new) { 656 node_invoke($node, 'insert'); 657 node_invoke_nodeapi($node, 'insert'); 658 } 659 else { 660 node_invoke($node, 'update'); 661 node_invoke_nodeapi($node, 'update'); 662 } 663 664 // Update the node access table for this node. 665 node_access_acquire_grants($node); 666 667 // Clear the cache so an anonymous poster can see the node being added or updated. 668 cache_clear_all(); 669 } 670 671 /** 672 * Generate a display of the given node. 673 * 674 * @param $node 675 * A node array or node object. 676 * @param $teaser 677 * Whether to display the teaser only, as on the main page. 678 * @param $page 679 * Whether the node is being displayed by itself as a page. 680 * @param $links 681 * Whether or not to display node links. Links are omitted for node previews. 682 * 683 * @return 684 * An HTML representation of the themed node. 685 */ 686 function node_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) { 687 $node = (object)$node; 688 689 $node = node_build_content($node, $teaser, $page); 690 691 if ($links) { 692 $node->links = module_invoke_all('link', 'node', $node, $teaser); 693 694 foreach (module_implements('link_alter') AS $module) { 695 $function = $module .'_link_alter'; 696 $function($node, $node->links); 697 } 698 } 699 700 // Set the proper node part, then unset unused $node part so that a bad 701 // theme can not open a security hole. 702 $content = drupal_render($node->content); 703 if ($teaser) { 704 $node->teaser = $content; 705 unset($node->body); 706 } 707 else { 708 $node->body = $content; 709 unset($node->teaser); 710 } 711 712 // Allow modules to modify the fully-built node. 713 node_invoke_nodeapi($node, 'alter', $teaser, $page); 714 715 return theme('node', $node, $teaser, $page); 716 } 717 718 /** 719 * Apply filters and build the node's standard elements. 720 */ 721 function node_prepare($node, $teaser = FALSE) { 722 // First we'll overwrite the existing node teaser and body with 723 // the filtered copies! Then, we'll stick those into the content 724 // array and set the read more flag if appropriate. 725 $node->readmore = (strlen($node->teaser) < strlen($node->body)); 726 727 if ($teaser == FALSE) { 728 $node->body = check_markup($node->body, $node->format, FALSE); 729 } 730 else { 731 $node->teaser = check_markup($node->teaser, $node->format, FALSE); 732 } 733 734 $node->content['body'] = array( 735 '#value' => $teaser ? $node->teaser : $node->body, 736 '#weight' => 0, 737 ); 738 739 return $node; 740 } 741 742 /** 743 * Builds a structured array representing the node's content. 744 * 745 * @param $node 746 * A node object. 747 * @param $teaser 748 * Whether to display the teaser only, as on the main page. 749 * @param $page 750 * Whether the node is being displayed by itself as a page. 751 * 752 * @return 753 * An structured array containing the individual elements 754 * of the node's body. 755 */ 756 function node_build_content($node, $teaser = FALSE, $page = FALSE) { 757 // Remove the delimiter (if any) that separates the teaser from the body. 758 $node->body = str_replace('<!--break-->', '', $node->body); 759 760 // The 'view' hook can be implemented to overwrite the default function 761 // to display nodes. 762 if (node_hook($node, 'view')) { 763 $node = node_invoke($node, 'view', $teaser, $page); 764 } 765 else { 766 $node = node_prepare($node, $teaser); 767 } 768 769 // Allow modules to make their own additions to the node. 770 node_invoke_nodeapi($node, 'view', $teaser, $page); 771 772 return $node; 773 } 774 775 /** 776 * Generate a page displaying a single node, along with its comments. 777 */ 778 function node_show($node, $cid) { 779 $output = node_view($node, FALSE, TRUE); 780 781 if (function_exists('comment_render') && $node->comment) { 782 $output .= comment_render($node, $cid); 783 } 784 785 // Update the history table, stating that this user viewed this node. 786 node_tag_new($node->nid); 787 788 return $output; 789 } 790 791 /** 792 * Implementation of hook_perm(). 793 */ 794 function node_perm() { 795 $perms = array('administer content types', 'administer nodes', 'access content', 'view revisions', 'revert revisions'); 796 797 foreach (node_get_types() as $type) { 798 if ($type->module == 'node') { 799 $name = check_plain($type->type); 800 $perms[] = 'create '. $name .' content'; 801 $perms[] = 'edit own '. $name .' content'; 802 $perms[] = 'edit '. $name .' content'; 803 } 804 } 805 806 return $perms; 807 } 808 809 /** 810 * Implementation of hook_search(). 811 */ 812 function node_search($op = 'search', $keys = NULL) { 813 switch ($op) { 814 case 'name': 815 return t('Content'); 816 817 case 'reset': 818 variable_del('node_cron_last'); 819 variable_del('node_cron_last_nid'); 820 return; 821 822 case 'status': 823 $last = variable_get('node_cron_last', 0); 824 $last_nid = variable_get('node_cron_last_nid', 0); 825 $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1')); 826 $remaining = db_result(db_query('SELECT COUNT(*) FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND ((GREATEST(n.created, n.changed, c.last_comment_timestamp) = %d AND n.nid > %d ) OR (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d))', $last, $last_nid, $last, $last, $last)); 827 return array('remaining' => $remaining, 'total' => $total); 828 829 case 'admin': 830 $form = array(); 831 // Output form for defining rank factor weights. 832 $form['content_ranking'] = array('#type' => 'fieldset', '#title' => t('Content ranking')); 833 $form['content_ranking']['#theme'] = 'node_search_admin'; 834 $form['content_ranking']['info'] = array('#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>'); 835 836 $ranking = array('node_rank_relevance' => t('Keyword relevance'), 837 'node_rank_recent' => t('Recently posted')); 838 if (module_exists('comment')) { 839 $ranking['node_rank_comments'] = t('Number of comments'); 840 } 841 if (module_exists('statistics') && variable_get('statistics_count_content_views', 0)) { 842 $ranking['node_rank_views'] = t('Number of views'); 843 } 844 845 // Note: reversed to reflect that higher number = higher ranking. 846 $options = drupal_map_assoc(range(0, 10)); 847 foreach ($ranking as $var => $title) { 848 $form['content_ranking']['factors'][$var] = array('#title' => $title, '#type' => 'select', '#options' => $options, '#default_value' => variable_get($var, 5)); 849 } 850 return $form; 851 852 case 'search': 853 // Build matching conditions 854 list($join1, $where1) = _db_rewrite_sql(); 855 $arguments1 = array(); 856 $conditions1 = 'n.status = 1'; 857 858 if ($type = search_query_extract($keys, 'type')) { 859 $types = array(); 860 foreach (explode(',', $type) as $t) { 861 $types[] = "n.type = '%s'"; 862 $arguments1[] = $t; 863 } 864 $conditions1 .= ' AND ('. implode(' OR ', $types) .')'; 865 $keys = search_query_insert($keys, 'type'); 866 } 867 868 if ($category = search_query_extract($keys, 'category')) { 869 $categories = array(); 870 foreach (explode(',', $category) as $c) { 871 $categories[] = "tn.tid = %d"; 872 $arguments1[] = $c; 873 } 874 $conditions1 .= ' AND ('. implode(' OR ', $categories) .')'; 875 $join1 .= ' INNER JOIN {term_node} tn ON n.nid = tn.nid'; 876 $keys = search_query_insert($keys, 'category'); 877 } 878 879 // Build ranking expression (we try to map each parameter to a 880 // uniform distribution in the range 0..1). 881 $ranking = array(); 882 $arguments2 = array(); 883 $join2 = ''; 884 // Used to avoid joining on node_comment_statistics twice 885 $stats_join = FALSE; 886 $total = 0; 887 if ($weight = (int)variable_get('node_rank_relevance', 5)) { 888 // Average relevance values hover around 0.15 889 $ranking[] = '%d * i.relevance'; 890 $arguments2[] = $weight; 891 $total += $weight; 892 } 893 if ($weight = (int)variable_get('node_rank_recent', 5)) { 894 // Exponential decay with half-life of 6 months, starting at last indexed node 895 $ranking[] = '%d * POW(2, (GREATEST(n.created, n.changed, c.last_comment_timestamp) - %d) * 6.43e-8)'; 896 $arguments2[] = $weight; 897 $arguments2[] = (int)variable_get('node_cron_last', 0); 898 $join2 .= ' INNER JOIN {node} n ON n.nid = i.sid LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid'; 899 $stats_join = TRUE; 900 $total += $weight; 901 } 902 if (module_exists('comment') && $weight = (int)variable_get('node_rank_comments', 5)) { 903 // Inverse law that maps the highest reply count on the site to 1 and 0 to 0. 904 $scale = variable_get('node_cron_comments_scale', 0.0); 905 $ranking[] = '%d * (2.0 - 2.0 / (1.0 + c.comment_count * %f))'; 906 $arguments2[] = $weight; 907 $arguments2[] = $scale; 908 if (!$stats_join) { 909 $join2 .= ' LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid'; 910 } 911 $total += $weight; 912 } 913 if (module_exists('statistics') && variable_get('statistics_count_content_views', 0) && 914 $weight = (int)variable_get('node_rank_views', 5)) { 915 // Inverse law that maps the highest view count on the site to 1 and 0 to 0. 916 $scale = variable_get('node_cron_views_scale', 0.0); 917 $ranking[] = '%d * (2.0 - 2.0 / (1.0 + nc.totalcount * %f))'; 918 $arguments2[] = $weight; 919 $arguments2[] = $scale; 920 $join2 .= ' LEFT JOIN {node_counter} nc ON nc.nid = i.sid'; 921 $total += $weight; 922 } 923 $select2 = (count($ranking) ? implode(' + ', $ranking) : 'i.relevance') .' AS score'; 924 925 // Do search 926 $find = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid = i.sid '. $join1 .' INNER JOIN {users} u ON n.uid = u.uid', $conditions1 . (empty($where1) ? '' : ' AND '. $where1), $arguments1, $select2, $join2, $arguments2); 927 928 // Load results 929 $results = array(); 930 foreach ($find as $item) { 931 // Build the node body. 932 $node = node_load($item->sid); 933 $node = node_build_content($node, FALSE, FALSE); 934 $node->body = drupal_render($node->content); 935 936 // Fetch comments for snippet 937 $node->body .= module_invoke('comment', 'nodeapi', $node, 'update index'); 938 // Fetch terms for snippet 939 $node->body .= module_invoke('taxonomy', 'nodeapi', $node, 'update index'); 940 941 $extra = node_invoke_nodeapi($node, 'search result'); 942 $results[] = array('link' => url('node/'. $item->sid, NULL, NULL, TRUE), 943 'type' => node_get_types('name', $node), 944 'title' => $node->title, 945 'user' => theme('username', $node), 946 'date' => $node->changed, 947 'node' => $node, 948 'extra' => $extra, 949 'score' => $item->score / $total, 950 'snippet' => search_excerpt($keys, $node->body)); 951 } 952 return $results; 953 } 954 } 955 956 /** 957 * Implementation of hook_user(). 958 */ 959 function node_user($op, &$edit, &$user) { 960 if ($op == 'delete') { 961 db_query('UPDATE {node} SET uid = 0 WHERE uid = %d', $user->uid); 962 db_query('UPDATE {node_revisions} SET uid = 0 WHERE uid = %d', $user->uid); 963 } 964 } 965 966 function theme_node_search_admin($form) { 967 $output = drupal_render($form['info']); 968 969 $header = array(t('Factor'), t('Weight')); 970 foreach (element_children($form['factors']) as $key) { 971 $row = array(); 972 $row[] = $form['factors'][$key]['#title']; 973 unset($form['factors'][$key]['#title']); 974 $row[] = drupal_render($form['factors'][$key]); 975 $rows[] = $row; 976 } 977 $output .= theme('table', $header, $rows); 978 979 $output .= drupal_render($form); 980 return $output; 981 } 982 983 /** 984 * Menu callback; presents general node configuration options. 985 */ 986 function node_configure() { 987 // Only show rebuild button if there is 0 or more than 2 rows in node_access table, or if there are modules that implement node_grant. 988 if (db_result(db_query('SELECT COUNT(*) FROM {node_access}')) != 1 || count(module_implements('node_grants')) > 0) { 989 $status = '<p>'. t('If the site is experiencing problems with permissions to content, you may have to rebuild the permissions cache. Possible causes for permission problems are disabling modules or configuration changes to permissions. Rebuilding will remove all privileges to posts, and replace them with permissions based on the current modules and settings.') .'</p>'; 990 $status .= '<p>'. t('Rebuilding may take some time if there is a lot of content or complex permission settings. After rebuilding has completed posts will automatically use the new permissions.') .'</p>'; 991 992 $form['access'] = array('#type' => 'fieldset', '#title' => t('Node access status')); 993 $form['access']['status'] = array('#value' => $status); 994 $form['access']['rebuild'] = array('#type' => 'submit', '#value' => t('Rebuild permissions')); 995 } 996 997 $form['default_nodes_main'] = array( 998 '#type' => 'select', '#title' => t('Number of posts on main page'), '#default_value' => variable_get('default_nodes_main', 10), 999 '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30)), 1000 '#description' => t('The default maximum number of posts to display per page on overview pages such as the main page.') 1001 ); 1002 $form['teaser_length'] = array( 1003 '#type' => 'select', '#title' => t('Length of trimmed posts'), '#default_value' => variable_get('teaser_length', 600), 1004 '#options' => array(0 => t('Unlimited'), 200 => t('200 characters'), 400 => t('400 characters'), 600 => t('600 characters'), 1005 800 => t('800 characters'), 1000 => t('1000 characters'), 1200 => t('1200 characters'), 1400 => t('1400 characters'), 1006 1600 => t('1600 characters'), 1800 => t('1800 characters'), 2000 => t('2000 characters')), 1007 '#description' => t("The maximum number of characters used in the trimmed version of a post. Drupal will use this setting to determine at which offset long posts should be trimmed. The trimmed version of a post is typically used as a teaser when displaying the post on the main page, in XML feeds, etc. To disable teasers, set to 'Unlimited'. Note that this setting will only affect new or updated content and will not affect existing teasers.") 1008 ); 1009 1010 $form['node_preview'] = array( 1011 '#type' => 'radios', '#title' => t('Preview post'), '#default_value' => variable_get('node_preview', 0), 1012 '#options' => array(t('Optional'), t('Required')), '#description' => t('Must users preview posts before submitting?') 1013 ); 1014 1015 return system_settings_form($form); 1016 } 1017 1018 /** 1019 * Form validate callback. 1020 */ 1021 function node_configure_validate($form_id, $form_values) { 1022 if ($form_values['op'] == t('Rebuild permissions')) { 1023 drupal_goto('admin/content/node-settings/rebuild'); 1024 } 1025 } 1026 1027 /** 1028 * Menu callback: confirm rebuilding of permissions. 1029 */ 1030 function node_configure_rebuild_confirm() { 1031 return confirm_form(array(), t('Are you sure you want to rebuild node permissions on the site?'), 1032 'admin/content/node-settings', t('This will wipe all current node permissions and rebuild them based on current settings. Rebuilding the permissions may take a while so please be patient. This action cannot be undone.'), t('Rebuild permissions'), t('Cancel')); 1033 } 1034 1035 /** 1036 * Handler for wipe confirmation 1037 */ 1038 function node_configure_rebuild_confirm_submit($form_id, &$form) { 1039 node_access_rebuild(); 1040 drupal_set_message(t('The node access table has been rebuilt.')); 1041 return 'admin/content/node-settings'; 1042 } 1043 1044 /** 1045 * Retrieve the comment mode for the given node ID (none, read, or read/write). 1046 */ 1047 function node_comment_mode($nid) { 1048 static $comment_mode; 1049 if (!isset($comment_mode[$nid])) { 1050 $comment_mode[$nid] = db_result(db_query('SELECT comment FROM {node} WHERE nid = %d', $nid)); 1051 } 1052 return $comment_mode[$nid]; 1053 } 1054 1055 /** 1056 * Implementation of hook_link(). 1057 */ 1058 function node_link($type, $node = NULL, $teaser = FALSE) { 1059 $links = array(); 1060 1061 if ($type == 'node') { 1062 if ($teaser == 1 && $node->teaser && $node->readmore) { 1063 $links['node_read_more'] = array( 1064 'title' => t('Read more'), 1065 'href' => "node/$node->nid", 1066 'attributes' => array('title' => t('Read the rest of this posting.')) 1067 ); 1068 } 1069 } 1070 1071 return $links; 1072 } 1073 1074 /** 1075 * Implementation of hook_menu(). 1076 */ 1077 function node_menu($may_cache) { 1078 $items = array(); 1079 if ($may_cache) { 1080 $items[] = array('path' => 'admin/content', 1081 'title' => t('Content management'), 1082 'description' => t("Manage your site's content."), 1083 'position' => 'left', 1084 'weight' => -10, 1085 'callback' => 'system_admin_menu_block_page', 1086 'access' => user_access('administer site configuration'), 1087 ); 1088 1089 $items[] = array( 1090 'path' => 'admin/content/node', 1091 'title' => t('Content'), 1092 'description' => t("View, edit, and delete your site's content."), 1093 'callback' => 'node_admin_content', 1094 'access' => user_access('administer nodes') 1095 ); 1096 1097 $items[] = array('path' => 'admin/content/node/overview', 'title' => t('List'), 1098 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10); 1099 1100 if (module_exists('search')) { 1101 $items[] = array('path' => 'admin/content/search', 'title' => t('Search content'), 1102 'description' => t('Search content by keyword.'), 1103 'callback' => 'node_admin_search', 1104 'access' => user_access('administer nodes'), 1105 'type' => MENU_NORMAL_ITEM); 1106 } 1107 1108 $items[] = array( 1109 'path' => 'admin/content/node-settings', 1110 'title' => t('Post settings'), 1111 'description' => t('Control posting behavior, such as teaser length, requiring previews before posting, and the number of posts on the front page.'), 1112 'callback' => 'drupal_get_form', 1113 'callback arguments' => array('node_configure'), 1114 'access' => user_access('administer nodes') 1115 ); 1116 $items[] = array( 1117 'path' => 'admin/content/node-settings/rebuild', 1118 'title' => t('rebuild permissions'), 1119 'callback' => 'drupal_get_form', 1120 'callback arguments' => array('node_configure_rebuild_confirm'), 1121 'access' => user_access('administer nodes'), 1122 'type' => MENU_CALLBACK); 1123 1124 $items[] = array( 1125 'path' => 'admin/content/types', 1126 'title' => t('Content types'), 1127 'description' => t('Manage posts by content type, including default status, front page promotion, etc.'), 1128 'callback' => 'node_overview_types', 1129 'access' => user_access('administer content types'), 1130 ); 1131 $items[] = array( 1132 'path' => 'admin/content/types/list', 1133 'title' => t('List'), 1134 'type' => MENU_DEFAULT_LOCAL_TASK, 1135 'weight' => -10, 1136 ); 1137 $items[] = array( 1138 'path' => 'admin/content/types/add', 1139 'title' => t('Add content type'), 1140 'callback' => 'drupal_get_form', 1141 'callback arguments' => array('node_type_form'), 1142 'type' => MENU_LOCAL_TASK, 1143 ); 1144 $items[] = array('path' => 'node', 1145 'title' => t('Content'), 1146 'callback' => 'node_page_default', 1147 'access' => user_access('access content'), 1148 'type' => MENU_MODIFIABLE_BY_ADMIN); 1149 $items[] = array('path' => 'node/add', 1150 'title' => t('Create content'), 1151 'callback' => 'node_add', 1152 'access' => user_access('access content'), 1153 'type' => MENU_ITEM_GROUPING, 1154 'weight' => 1); 1155 $items[] = array('path' => 'rss.xml', 'title' => t('RSS feed'), 1156 'callback' => 'node_feed', 1157 'access' => user_access('access content'), 1158 'type' => MENU_CALLBACK); 1159 1160 foreach (node_get_types() as $type) { 1161 if (function_exists($type->module .'_form')) { 1162 $type_url_str = str_replace('_', '-', $type->type); 1163 $items[] = array( 1164 'path' => 'node/add/'. $type_url_str, 1165 'title' => drupal_ucfirst($type->name), 1166 'access' => node_access('create', $type->type), 1167 ); 1168 } 1169 } 1170 // Error pages must to be present in the menu cache and be accessible to 1171 // all. More often than not these are individual nodes. 1172 for ($error_code = 403; $error_code <= 404; $error_code++) { 1173 if (preg_match('|^node/(?P<nid>\d+)(?:/view)?$|', drupal_get_normal_path(variable_get('site_'. $error_code, '')), $matches) && ($node = node_load($matches['nid']))) { 1174 $items[] = array( 1175 'path' => 'node/'. $node->nid, 1176 'title' => t('View'), 1177 'callback' => 'node_page_view', 1178 'callback arguments' => array($node), 1179 'access' => TRUE, 1180 'type' => MENU_CALLBACK, 1181 ); 1182 } 1183 } 1184 } 1185 else { 1186 // Add the CSS for this module 1187 // We put this in !$may_cache so it's only added once per request 1188 drupal_add_css(drupal_get_path('module', 'node') .'/node.css'); 1189 1190 if (arg(0) == 'node' && is_numeric(arg(1))) { 1191 $node = node_load(arg(1)); 1192 if ($node->nid) { 1193 $items[] = array('path' => 'node/'. arg(1), 'title' => t('View'), 1194 'callback' => 'node_page_view', 1195 'callback arguments' => array($node), 1196 'access' => node_access('view', $node), 1197 'type' => MENU_CALLBACK); 1198 $items[] = array('path' => 'node/'. arg(1) .'/view', 'title' => t('View'), 1199 'type' => MENU_DEFAULT_LOCAL_TASK, 1200 'weight' => -10); 1201 $items[] = array('path' => 'node/'. arg(1) .'/edit', 'title' => t('Edit'), 1202 'callback' => 'node_page_edit', 1203 'callback arguments' => array($node), 1204 'access' => node_access('update', $node), 1205 'weight' => 1, 1206 'type' => MENU_LOCAL_TASK); 1207 $items[] = array('path' => 'node/'. arg(1) .'/delete', 'title' => t('Delete'), 1208 'callback' => 'drupal_get_form', 1209 'callback arguments' => array('node_delete_confirm', $node), 1210 'access' => node_access('delete', $node), 1211 'weight' => 1, 1212 'type' => MENU_CALLBACK); 1213 $revisions_access = ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node) && db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', arg(1))) > 1); 1214 $items[] = array( 1215 'path' => 'node/'. arg(1) .'/revisions', 1216 'title' => t('Revisions'), 1217 'callback' => 'node_revisions', 1218 'access' => $revisions_access, 1219 'weight' => 2, 1220 'type' => MENU_LOCAL_TASK, 1221 ); 1222 if (!is_null(arg(3))) { 1223 $items[] = array( 1224 'path' => 'node/'. arg(1) .'/revisions/'. arg(3) .'/delete', 1225 'callback' => 'node_revision_delete', 1226 'callback arguments' => array(arg(1), arg(3)), 1227 ); 1228 $items[] = array( 1229 'path' => 'node/'. arg(1) .'/revisions/'. arg(3) .'/revert', 1230 'callback' => 'node_revision_revert', 1231 'callback arguments' => array(arg(1), arg(3)), 1232 ); 1233 } 1234 } 1235 } 1236 1237 // Content type configuration. 1238 if (arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'types') { 1239 include_once './'. drupal_get_path('module', 'node') .'/content_types.inc'; 1240 1241 if (arg(3) != NULL) { 1242 $type_name = arg(3); 1243 $type_name = !empty($type_name) ? str_replace('-', '_', $type_name) : NULL; 1244 $type = node_get_types('type', $type_name); 1245 1246 if (!empty($type)) { 1247 $type_url_str = str_replace('_', '-', $type->type); 1248 1249 $items[] = array( 1250 'path' => 'admin/content/types/'. $type_url_str, 1251 'title' => t($type->name), 1252 'callback' => 'drupal_get_form', 1253 'callback arguments' => array('node_type_form', $type), 1254 'type' => MENU_CALLBACK, 1255 ); 1256 $items[] = array( 1257 'path' => 'admin/content/types/'. $type_url_str .'/delete', 1258 'title' => t('Delete'), 1259 'callback' => 'drupal_get_form', 1260 'callback arguments' => array('node_type_delete_confirm', $type), 1261 'type' => MENU_CALLBACK, 1262 ); 1263 } 1264 } 1265 } 1266 } 1267 return $items; 1268 } 1269 1270 function node_last_changed($nid) { 1271 $node = db_fetch_object(db_query('SELECT changed FROM {node} WHERE nid = %d', $nid)); 1272 return ($node->changed); 1273 } 1274 1275 /** 1276 * Implementation of hook_node_operations(). 1277 */ 1278 function node_node_operations() { 1279 $operations = array( 1280 'publish' => array( 1281 'label' => t('Publish'), 1282 'callback' => 'node_operations_publish', 1283 ), 1284 'unpublish' => array( 1285 'label' => t('Unpublish'), 1286 'callback' => 'node_operations_unpublish', 1287 ), 1288 'promote' => array( 1289 'label' => t('Promote to front page'), 1290 'callback' => 'node_operations_promote', 1291 ), 1292 'demote' => array( 1293 'label' => t('Demote from front page'), 1294 'callback' => 'node_operations_demote', 1295 ), 1296 'sticky' => array( 1297 'label' => t('Make sticky'), 1298 'callback' => 'node_operations_sticky', 1299 ), 1300 'unsticky' => array( 1301 'label' => t('Remove stickiness'), 1302 'callback' => 'node_operations_unsticky', 1303 ), 1304 'delete' => array( 1305 'label' => t('Delete'), 1306 ), 1307 ); 1308 return $operations; 1309 } 1310 1311 /** 1312 * Callback function for admin mass publishing nodes. 1313 */ 1314 function node_operations_publish($nodes) { 1315 $placeholders = implode(',', array_fill(0, count($nodes), '%d')); 1316 db_query('UPDATE {node} SET status = 1 WHERE nid IN('. $placeholders .')', $nodes); 1317 } 1318 1319 /** 1320 * Callback function for admin mass unpublishing nodes. 1321 */ 1322 function node_operations_unpublish($nodes) { 1323 $placeholders = implode(',', array_fill(0, count($nodes), '%d')); 1324 db_query('UPDATE {node} SET status = 0 WHERE nid IN('. $placeholders .')', $nodes); 1325 } 1326 1327 /** 1328 * Callback function for admin mass promoting nodes. 1329 */ 1330 function node_operations_promote($nodes) { 1331 $placeholders = implode(',', array_fill(0, count($nodes), '%d')); 1332 db_query('UPDATE {node} SET status = 1, promote = 1 WHERE nid IN('. $placeholders .')', $nodes); 1333 } 1334 1335 /** 1336 * Callback function for admin mass demoting nodes. 1337 */ 1338 function node_operations_demote($nodes) { 1339 $placeholders = implode(',', array_fill(0, count($nodes), '%d')); 1340 db_query('UPDATE {node} SET promote = 0 WHERE nid IN('. $placeholders .')', $nodes); 1341 } 1342 1343 /** 1344 * Callback function for admin mass editing nodes to be sticky. 1345 */ 1346 function node_operations_sticky($nodes) { 1347 $placeholders = implode(',', array_fill(0, count($nodes), '%d')); 1348 db_query('UPDATE {node} SET status = 1, sticky = 1 WHERE nid IN('. $placeholders .')', $nodes); 1349 } 1350 1351 /** 1352 * Callback function for admin mass editing nodes to remove stickiness. 1353 */ 1354 function node_operations_unsticky($nodes) { 1355 $placeholders = implode(',', array_fill(0, count($nodes), '%d')); 1356 db_query('UPDATE {node} SET sticky = 0 WHERE nid IN('. $placeholders .')', $nodes); 1357 } 1358 1359 /** 1360 * List node administration filters that can be applied. 1361 */ 1362 function node_filters() { 1363 // Regular filters 1364 $filters['status'] = array('title' => t('status'), 1365 'options' => array('status-1' => t('published'), 'status-0' => t('not published'), 1366 'promote-1' => t('promoted'), 'promote-0' => t('not promoted'), 1367 'sticky-1' => t('sticky'), 'sticky-0' => t('not sticky'))); 1368 $filters['type'] = array('title' => t('type'), 'options' => node_get_types('names')); 1369 // The taxonomy filter 1370 if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) { 1371 $filters['category'] = array('title' => t('category'), 'options' => $taxonomy); 1372 } 1373 1374 return $filters; 1375 } 1376 1377 /** 1378 * Build query for node administration filters based on session. 1379 */ 1380 function node_build_filter_query() { 1381 $filters = node_filters(); 1382 1383 // Build query 1384 $where = $args = array(); 1385 $join = ''; 1386 foreach ($_SESSION['node_overview_filter'] as $index => $filter) { 1387 list($key, $value) = $filter; 1388 switch ($key) { 1389 case 'status': 1390 // Note: no exploitable hole as $key/$value have already been checked when submitted 1391 list($key, $value) = explode('-', $value, 2); 1392 $where[] = 'n.'. $key .' = %d'; 1393 break; 1394 case 'category': 1395 $table = "tn$index"; 1396 $where[] = "$table.tid = %d"; 1397 $join .= "INNER JOIN {term_node} $table ON n.nid = $table.nid "; 1398 break; 1399 case 'type': 1400 $where[] = "n.type = '%s'"; 1401 } 1402 $args[] = $value; 1403 } 1404 $where = count($where) ? 'WHERE '. implode(' AND ', $where) : ''; 1405 1406 return array('where' => $where, 'join' => $join, 'args' => $args); 1407 } 1408 1409 /** 1410 * Return form for node administration filters. 1411 */ 1412 function node_filter_form() { 1413 $session = &$_SESSION['node_overview_filter']; 1414 $session = is_array($session) ? $session : array(); 1415 $filters = node_filters(); 1416 1417 $i = 0; 1418 $form['filters'] = array('#type' => 'fieldset', 1419 '#title' => t('Show only items where'), 1420 '#theme' => 'node_filters', 1421 ); 1422 foreach ($session as $filter) { 1423 list($type, $value) = $filter; 1424 if ($type == 'category') { 1425 // Load term name from DB rather than search and parse options array. 1426 $value = module_invoke('taxonomy', 'get_term', $value); 1427 $value = $value->name; 1428 } 1429 else { 1430 $value = $filters[$type]['options'][$value]; 1431 } 1432 $string = ($i++ ? '<em>and</em> where <strong>%a</strong> is <strong>%b</strong>' : '<strong>%a</strong> is <strong>%b</strong>'); 1433 $form['filters']['current'][] = array('#value' => t($string, array('%a' => $filters[$type]['title'] , '%b' => $value))); 1434 if ($type == 'type') { 1435 // Remove the type option if it is already being filtered on. 1436 unset($filters['type']); 1437 } 1438 } 1439 1440 foreach ($filters as $key => $filter) { 1441 $names[$key] = $filter['title']; 1442 $form['filters']['status'][$key] = array('#type' => 'select', '#options' => $filter['options']); 1443 } 1444 1445 $form['filters']['filter'] = array('#type' => 'radios', '#options' => $names, '#default_value' => 'status'); 1446 $form['filters']['buttons']['submit'] = array('#type' => 'submit', '#value' => (count($session) ? t('Refine') : t('Filter'))); 1447 if (count($session)) { 1448 $form['filters']['buttons']['undo'] = array('#type' => 'submit', '#value' => t('Undo')); 1449 $form['filters']['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset')); 1450 } 1451 1452 return $form; 1453 } 1454 1455 /** 1456 * Theme node administration filter form. 1457 */ 1458 function theme_node_filter_form($form) { 1459 $output .= '<div id="node-admin-filter">'; 1460 $output .= drupal_render($form['filters']); 1461 $output .= '</div>'; 1462 $output .= drupal_render($form); 1463 return $output; 1464 } 1465 1466 /** 1467 * Theme node administration filter selector. 1468 */ 1469 function theme_node_filters($form) { 1470 $output .= '<ul class="clear-block">'; 1471 if (sizeof($form['current'])) { 1472 foreach (element_children($form['current']) as $key) { 1473 $output .= '<li>'. drupal_render($form['current'][$key]) .'</li>'; 1474 } 1475 } 1476 1477 $output .= '<li><dl class="multiselect">'. (sizeof($form['current']) ? '<dt><em>'. t('and') .'</em> '. t('where') .'</dt>' : '') .'<dd class="a">'; 1478 foreach (element_children($form['filter']) as $key) { 1479 $output .= drupal_render($form['filter'][$key]); 1480 } 1481 $output .= '</dd>'; 1482 1483 $output .= '<dt>'. t('is') .'</dt><dd class="b">'; 1484 1485 foreach (element_children($form['status']) as $key) { 1486 $output .= drupal_render($form['status'][$key]); 1487 } 1488 $output .= '</dd>'; 1489 1490 $output .= '</dl>'; 1491 $output .= '<div class="container-inline" id="node-admin-buttons">'. drupal_render($form['buttons']) .'</div>'; 1492 $output .= '</li></ul>'; 1493 1494 return $output; 1495 } 1496 1497 /** 1498 * Process result from node administration filter form. 1499 */ 1500 function node_filter_form_submit($form_id, $form_values) { 1501 $filters = node_filters(); 1502 switch ($form_values['op']) { 1503 case t('Filter'): 1504 case t('Refine'): 1505 if (isset($form_values['filter'])) { 1506 $filter = $form_values['filter']; 1507 1508 // Flatten the options array to accommodate hierarchical/nested options. 1509 $flat_options = form_options_flatten($filters[$filter]['options']); 1510 1511 if (isset($flat_options[$form_values[$filter]])) { 1512 $_SESSION['node_overview_filter'][] = array($filter, $form_values[$filter]); 1513 } 1514 } 1515 break; 1516 case t('Undo'): 1517 array_pop($_SESSION['node_overview_filter']); 1518 break; 1519 case t('Reset'): 1520 $_SESSION['node_overview_filter'] = array(); 1521 break; 1522 } 1523 } 1524 1525 /** 1526 * Submit the node administration update form. 1527 */ 1528 function node_admin_nodes_submit($form_id, $form_values) { 1529 $operations = module_invoke_all('node_operations'); 1530 $operation = $operations[$form_values['operation']]; 1531 // Filter out unchecked nodes 1532 $nodes = array_filter($form_values['nodes']); 1533 if ($function = $operation['callback']) { 1534 // Add in callback arguments if present. 1535 if (isset($operation['callback arguments'])) { 1536 $args = array_merge(array($nodes), $operation['callback arguments']); 1537 } 1538 else { 1539 $args = array($nodes); 1540 } 1541 call_user_func_array($function, $args); 1542 1543 cache_clear_all(); 1544 drupal_set_message(t('The update has been performed.')); 1545 } 1546 } 1547 1548 function node_admin_nodes_validate($form_id, $form_values) { 1549 $nodes = array_filter($form_values['nodes']); 1550 if (count($nodes) == 0) { 1551 form_set_error('', t('No items selected.')); 1552 } 1553 } 1554 1555 /** 1556 * Menu callback: content administration. 1557 */ 1558 function node_admin_content() { 1559 $output = drupal_get_form('node_filter_form'); 1560 1561 if ($_POST['operation'] == 'delete' && $_POST['nodes']) { 1562 return drupal_get_form('node_multiple_delete_confirm'); 1563 } 1564 // Call the form first, to allow for the form_values array to be populated. 1565 $output .= drupal_get_form('node_admin_nodes'); 1566 1567 return $output; 1568 } 1569 1570 function node_admin_nodes() { 1571 $filter = node_build_filter_query(); 1572 1573 $result = pager_query('SELECT n.*, u.name, u.uid FROM {node} n '. $filter['join'] .' INNER JOIN {users} u ON n.uid = u.uid '. $filter['where'] .' ORDER BY n.changed DESC', 50, 0, NULL, $filter['args']); 1574 1575 $form['options'] = array('#type' => 'fieldset', 1576 '#title' => t('Update options'), 1577 '#prefix' => '<div class="container-inline">', 1578 '#suffix' => '</div>', 1579 ); 1580 $options = array(); 1581 foreach (module_invoke_all('node_operations') as $operation => $array) { 1582 $options[$operation] = $array['label']; 1583 } 1584 $form['options']['operation'] = array('#type' => 'select', '#options' => $options, '#default_value' => 'approve'); 1585 $form['options']['submit'] = array('#type' => 'submit', '#value' => t('Update')); 1586 1587 $destination = drupal_get_destination(); 1588 while ($node = db_fetch_object($result)) { 1589 $nodes[$node->nid] = ''; 1590 $form['title'][$node->nid] = array('#value' => l($node->title, 'node/'. $node->nid) .' '. theme('mark', node_mark($node->nid, $node->changed))); 1591 $form['name'][$node->nid] = array('#value' => check_plain(node_get_types('name', $node))); 1592 $form['username'][$node->nid] = array('#value' => theme('username', $node)); 1593 $form['status'][$node->nid] = array('#value' => ($node->status ? t('published') : t('not published'))); 1594 $form['operations'][$node->nid] = array('#value' => l(t('edit'), 'node/'. $node->nid .'/edit', array(), $destination)); 1595 } 1596 $form['nodes'] = array('#type' => 'checkboxes', '#options' => $nodes); 1597 $form['pager'] = array('#value' => theme('pager', NULL, 50, 0)); 1598 return $form; 1599 } 1600 1601 /** 1602 * Theme node administration overview. 1603 */ 1604 function theme_node_admin_nodes($form) { 1605 // Overview table: 1606 $header = array(theme('table_select_header_cell'), t('Title'), t('Type'), t('Author'), t('Status'), t('Operations')); 1607 1608 $output .= drupal_render($form['options']); 1609 if (isset($form['title']) && is_array($form['title'])) { 1610 foreach (element_children($form['title']) as $key) { 1611 $row = array(); 1612 $row[] = drupal_render($form['nodes'][$key]); 1613 $row[] = drupal_render($form['title'][$key]); 1614 $row[] = drupal_render($form['name'][$key]); 1615 $row[] = drupal_render($form['username'][$key]); 1616 $row[] = drupal_render($form['status'][$key]); 1617 $row[] = drupal_render($form['operations'][$key]); 1618 $rows[] = $row; 1619 } 1620 1621 } 1622 else { 1623 $rows[] = array(array('data' => t('No posts available.'), 'colspan' => '6')); 1624 } 1625 1626 $output .= theme('table', $header, $rows); 1627 if ($form['pager']['#value']) { 1628 $output .= drupal_render($form['pager']); 1629 } 1630 1631 $output .= drupal_render($form); 1632 1633 return $output; 1634 } 1635 1636 function node_multiple_delete_confirm() { 1637 $edit = $_POST; 1638 1639 $form['nodes'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE); 1640 // array_filter returns only elements with TRUE values 1641 foreach (array_filter($edit['nodes']) as $nid => $value) { 1642 $title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $nid)); 1643 $form['nodes'][$nid] = array('#type' => 'hidden', '#value' => $nid, '#prefix' => '<li>', '#suffix' => check_plain($title) ."</li>\n"); 1644 } 1645 $form['operation'] = array('#type' => 'hidden', '#value' => 'delete'); 1646 1647 return confirm_form($form, 1648 t('Are you sure you want to delete these items?'), 1649 'admin/content/node', t('This action cannot be undone.'), 1650 t('Delete all'), t('Cancel')); 1651 } 1652 1653 function node_multiple_delete_confirm_submit($form_id, $form_values) { 1654 if ($form_values['confirm']) { 1655 foreach ($form_values['nodes'] as $nid => $value) { 1656 node_delete($nid); 1657 } 1658 drupal_set_message(t('The items have been deleted.')); 1659 } 1660 return 'admin/content/node'; 1661 } 1662 1663 /** 1664 * Generate an overview table of older revisions of a node. 1665 */ 1666 function node_revision_overview($node) { 1667 drupal_set_title(t('Revisions for %title', array('%title' => $node->title))); 1668 1669 $header = array(t('Revision'), array('data' => t('Operations'), 'colspan' => 2)); 1670 1671 $revisions = node_revision_list($node); 1672 1673 $rows = array(); 1674 $revert_permission = FALSE; 1675 if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) { 1676 $revert_permission = TRUE; 1677 } 1678 $delete_permission = FALSE; 1679 if (user_access('administer nodes')) { 1680 $delete_permission = TRUE; 1681 } 1682 foreach ($revisions as $revision) { 1683 $row = array(); 1684 $operations = array(); 1685 1686 if ($revision->current_vid > 0) { 1687 $row[] = array('data' => t('!date by !username', array('!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid"), '!username' => theme('username', $revision))) 1688 . (($revision->log != '') ? '<p class="revision-log">'. filter_xss($revision->log) .'</p>' : ''), 1689 'class' => 'revision-current'); 1690 $operations[] = array('data' => theme('placeholder', t('current revision')), 'class' => 'revision-current', 'colspan' => 2); 1691 } 1692 else { 1693 $row[] = t('!date by !username', array('!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid/revisions/$revision->vid/view"), '!username' => theme('username', $revision))) 1694 . (($revision->log != '') ? '<p class="revision-log">'. filter_xss($revision->log) .'</p>' : ''); 1695 if ($revert_permission) { 1696 $operations[] = l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert"); 1697 } 1698 if ($delete_permission) { 1699 $operations[] = l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete"); 1700 } 1701 } 1702 $rows[] = array_merge($row, $operations); 1703 } 1704 1705 return theme('table', $header, $rows); 1706 } 1707 1708 /** 1709 * Revert to the revision with the specified revision number. A node and nodeapi "update" event is triggered 1710 * (via the node_save() call) when a revision is reverted. 1711 */ 1712 function node_revision_revert($nid, $revision) { 1713 global $user; 1714 1715 $node = node_load($nid, $revision); 1716 if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) { 1717 if ($node->vid) { 1718 return drupal_get_form('node_revision_revert_confirm', $node); 1719 } 1720 else { 1721 drupal_set_message(t('You tried to revert to an invalid revision.'), 'error'); 1722 } 1723 drupal_goto('node/'. $nid .'/revisions'); 1724 } 1725 drupal_access_denied(); 1726 } 1727 1728 /** 1729 * Ask for confirmation of the reversion to prevent against CSRF attacks. 1730 */ 1731 function node_revision_revert_confirm($node) { 1732 $form['node'] = array('#type' => 'value', '#value' => $node); 1733 return confirm_form($form, t('Are you sure you want to revert to the revision from %revision-date?', array('%revision-date' => format_date($node->revision_timestamp))), 'node/'. $node->nid .'/revisions', '', t('Revert'), t('Cancel')); 1734 } 1735 1736 function node_revision_revert_confirm_submit($form_id, $form_values) { 1737 $node = $form_values['node']; 1738 1739 $node->revision = 1; 1740 $node->log = t('Copy of the revision from %date.', array('%date' => format_date($node->revision_timestamp))); 1741 if (module_exists('taxonomy')) { 1742 $node->taxonomy = array_keys($node->taxonomy); 1743 } 1744 1745 node_save($node); 1746 1747 drupal_set_message(t('%title has been reverted back to the revision from %revision-date', array('%revision-date' => format_date($node->revision_timestamp), '%title' => $node->title))); 1748 watchdog('content', t('@type: reverted %title revision %revision.', array('@type' => t($node->type), '%title' => $node->title, '%revision' => $revision))); 1749 1750 return 'node/'. $node->nid .'/revisions'; 1751 } 1752 1753 /** 1754 * Delete the revision with specified revision number. A "delete revision" nodeapi event is invoked when a 1755 * revision is deleted. 1756 */ 1757 function node_revision_delete($nid, $revision) { 1758 if (user_access('administer nodes')) { 1759 $node = node_load($nid); 1760 if (node_access('delete', $node)) { 1761 // Don't delete the current revision 1762 if ($revision != $node->vid) { 1763 if ($node = node_load($nid, $revision)) { 1764 return drupal_get_form('node_revision_delete_confirm', $node); 1765 } 1766 else { 1767 drupal_set_message(t('Deletion failed. You tried to delete a non-existing revision.')); 1768 } 1769 } 1770 else { 1771 drupal_set_message(t('Deletion failed. You tried to delete the current revision.')); 1772 } 1773 1774 if (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $nid)) > 1) { 1775 drupal_goto('node/'. $nid .'/revisions'); 1776 } 1777 else { 1778 drupal_goto('node/'. $nid); 1779 } 1780 } 1781 } 1782 1783 drupal_access_denied(); 1784 } 1785 1786 /** 1787 * Ask confirmation for revision deletion to prevent against CSRF attacks. 1788 */ 1789 function node_revision_delete_confirm($node) { 1790 $form['node'] = array('#type' => 'value', '#value' => $node); 1791 return confirm_form($form, t('Are you sure you want to delete the revision from %revision-date?', array('%revision-date' => format_date($node->revision_timestamp))), 'node/'. $node->nid .'/revisions', t('This action cannot be undone.'), t('Delete'), t('Cancel')); 1792 } 1793 1794 function node_revision_delete_confirm_submit($form_id, $form_values) { 1795 $node = $form_values['node']; 1796 db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $node->nid, $node->vid); 1797 node_invoke_nodeapi($node, 'delete revision'); 1798 drupal_set_message(t('Deleted %title revision %revision.', array('%title' => $node->title, '%revision' => $node->vid))); 1799 watchdog('content', t('@type: deleted %title revision %revision.', array('@type' => t($node->type), '%title' => $node->title, '%revision' => $node->vid))); 1800 1801 if (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $node->nid)) > 1) { 1802 return 'node/'. $node->nid .'/revisions'; 1803 } 1804 else { 1805 return 'node/'. $node->nid; 1806 } 1807 } 1808 1809 /** 1810 * Return a list of all the existing revision numbers. 1811 */ 1812 function node_revision_list($node) { 1813 $revisions = array(); 1814 $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); 1815 while ($revision = db_fetch_object($result)) { 1816 $revisions[] = $revision; 1817 } 1818 1819 return $revisions; 1820 } 1821 1822 function node_admin_search() { 1823 return drupal_get_form('search_form', url('admin/content/search'), $_POST['keys'], 'node') . search_data($_POST['keys'], 'node'); 1824 } 1825 1826 /** 1827 * Implementation of hook_block(). 1828 */ 1829 function node_block($op = 'list', $delta = 0) { 1830 if ($op == 'list') { 1831 $blocks[0]['info'] = t('Syndicate'); 1832 return $blocks; 1833 } 1834 else if ($op == 'view') { 1835 $block['subject'] = t('Syndicate'); 1836 $block['content'] = theme('feed_icon', url('rss.xml')); 1837 1838 return $block; 1839 } 1840 } 1841 1842 /** 1843 * A generic function for generating RSS feeds from a set of nodes. 1844 * 1845 * @param $nodes 1846 * An object as returned by db_query() which contains the nid field. 1847 * @param $channel 1848 * An associative array containing title, link, description and other keys. 1849 * The link should be an absolute URL. 1850 */ 1851 function node_feed($nodes = 0, $channel = array()) { 1852 global $base_url, $locale; 1853 1854 if (!$nodes) { 1855 $nodes = 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)); 1856 } 1857 1858 $item_length = variable_get('feed_item_length', 'teaser'); 1859 $namespaces = array('xmlns:dc="http://purl.org/dc/elements/1.1/"'); 1860 1861 while ($node = db_fetch_object($nodes)) { 1862 // Load the specified node: 1863 $item = node_load($node->nid); 1864 $link = url("node/$node->nid", NULL, NULL, 1); 1865 1866 if ($item_length != 'title') { 1867 $teaser = ($item_length == 'teaser') ? TRUE : FALSE; 1868 1869 // Filter and prepare node teaser 1870 if (node_hook($item, 'view')) { 1871 $item = node_invoke($item, 'view', $teaser, FALSE); 1872 } 1873 else { 1874 $item = node_prepare($item, $teaser); 1875 } 1876 1877 // Allow modules to change $node->teaser before viewing. 1878 node_invoke_nodeapi($item, 'view', $teaser, FALSE); 1879 } 1880 1881 // Allow modules to add additional item fields and/or modify $item 1882 $extra = node_invoke_nodeapi($item, 'rss item'); 1883 $extra = array_merge($extra, array(array('key' => 'pubDate', 'value' => date('r', $item->created)), array('key' => 'dc:creator', 'value' => $item->name), array('key' => 'guid', 'value' => $item->nid .' at '. $base_url, 'attributes' => array('isPermaLink' => 'false')))); 1884 foreach ($extra as $element) { 1885 if ($element['namespace']) { 1886 $namespaces = array_merge($namespaces, $element['namespace']); 1887 } 1888 } 1889 1890 // Prepare the item description 1891 switch ($item_length) { 1892 case 'fulltext': 1893 $item_text = $item->body; 1894 break; 1895 case 'teaser': 1896 $item_text = $item->teaser; 1897 if ($item->readmore) { 1898 $item_text .= '<p>'. l(t('read more'), 'node/'. $item->nid, NULL, NULL, NULL, TRUE) .'</p>'; 1899 } 1900 break; 1901 case 'title': 1902 $item_text = ''; 1903 break; 1904 } 1905 1906 $items .= format_rss_item($item->title, $link, $item_text, $extra); 1907 } 1908 1909 $channel_defaults = array( 1910 'version' => '2.0', 1911 'title' => variable_get('site_name', 'Drupal') .' - '. variable_get('site_slogan', ''), 1912 'link' => $base_url, 1913 'description' => variable_get('site_mission', ''), 1914 'language' => $locale 1915 ); 1916 $channel = array_merge($channel_defaults, $channel); 1917 1918 $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; 1919 $output .= "<rss version=\"". $channel["version"] ."\" xml:base=\"". $base_url ."\" ". implode(' ', $namespaces) .">\n"; 1920 $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language']); 1921 $output .= "</rss>\n"; 1922 1923 drupal_set_header('Content-Type: application/rss+xml; charset=utf-8'); 1924 print $output; 1925 } 1926 1927 /** 1928 * Prepare node for save and allow modules to make changes. 1929 */ 1930 function node_submit($node) { 1931 global $user; 1932 1933 // Convert the node to an object, if necessary. 1934 $node = (object)$node; 1935 1936 // Auto-generate the teaser, but only if it hasn't been set (e.g. by a 1937 // module-provided 'teaser' form item). 1938 if (!isset($node->teaser)) { 1939 $node->teaser = isset($node->body) ? node_teaser($node->body, isset($node->format) ? $node->format : NULL) : ''; 1940 } 1941 1942 if (user_access('administer nodes')) { 1943 // Populate the "authored by" field. 1944 if ($account = user_load(array('name' => $node->name))) { 1945 $node->uid = $account->uid; 1946 } 1947 else { 1948 $node->uid = 0; 1949 } 1950 1951 $node->created = $node->date ? strtotime($node->date) : NULL; 1952 } 1953 1954 // Do node-type-specific validation checks. 1955 node_invoke($node, 'submit'); 1956 node_invoke_nodeapi($node, 'submit'); 1957 1958 $node->validated = TRUE; 1959 1960 return $node; 1961 } 1962 1963 /** 1964 * Perform validation checks on the given node. 1965 */ 1966 function node_validate($node, $form = array()) { 1967 // Convert the node to an object, if necessary. 1968 $node = (object)$node; 1969 $type = node_get_types('type', $node); 1970 1971 // Make sure the body has the minimum number of words. 1972 // todo use a better word counting algorithm that will work in other languages 1973 if (isset($node->body) && count(explode(' ', $node->body)) < $type->min_word_count) { 1974 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))); 1975 } 1976 1977 if (isset($node->nid) && (node_last_changed($node->nid) > $node->changed)) { 1978 form_set_error('changed', t('This content has been modified by another user, changes cannot be saved.')); 1979 } 1980 1981 if (user_access('administer nodes')) { 1982 // Validate the "authored by" field. 1983 if (!empty($node->name) && !($account = user_load(array('name' => $node->name)))) { 1984 // The use of empty() is mandatory in the context of usernames 1985 // as the empty string denotes the anonymous user. In case we 1986 // are dealing with an anonymous user we set the user ID to 0. 1987 form_set_error('name', t('The username %name does not exist.', array('%name' => $node->name))); 1988 } 1989 1990 // Validate the "authored on" field. As of PHP 5.1.0, strtotime returns FALSE instead of -1 upon failure. 1991 if (!empty($node->date) && strtotime($node->date) <= 0) { 1992 form_set_error('date', t('You have to specify a valid date.')); 1993 } 1994 } 1995 1996 // Do node-type-specific validation checks. 1997 node_invoke($node, 'validate', $form); 1998 node_invoke_nodeapi($node, 'validate', $form); 1999 } 2000 2001 function node_form_validate($form_id, $form_values, $form) { 2002 node_validate($form_values, $form); 2003 } 2004 2005 function node_object_prepare(&$node) { 2006 if (user_access('administer nodes')) { 2007 // Set up default values, if required. 2008 if (!isset($node->created)) { 2009 $node->created = time(); 2010 } 2011 2012 if (!isset($node->date)) { 2013 $node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O'); 2014 } 2015 } 2016 node_invoke($node, 'prepare'); 2017 node_invoke_nodeapi($node, 'prepare'); 2018 } 2019 2020 /** 2021 * Generate the node add/edit form array. 2022 */ 2023 function node_form($node, $form_values = NULL) { 2024 global $user; 2025 2026 $node = (object)$node; 2027 node_object_prepare($node); 2028 2029 // Set the id of the top-level form tag 2030 $form['#id'] = 'node-form'; 2031 2032 /** 2033 * Basic node information. 2034 * These elements are just values so they are not even sent to the client. 2035 */ 2036 foreach (array('nid', 'vid', 'uid', 'created', 'type') as $key) { 2037 $form[$key] = array('#type' => 'value', '#value' => $node->$key); 2038 } 2039 2040 // Changed must be sent to the client, for later overwrite error checking. 2041 $form['changed'] = array('#type' => 'hidden', '#default_value' => $node->changed); 2042 2043 // Get the node-specific bits. 2044 if ($extra = node_invoke($node, 'form', $form_values)) { 2045 $form = array_merge_recursive($form, $extra); 2046 } 2047 if (!isset($form['title']['#weight'])) { 2048 $form['title']['#weight'] = -5; 2049 } 2050 2051 $node_options = variable_get('node_options_'. $node->type, array('status', 'promote')); 2052 // If this is a new node, fill in the default values. 2053 if (!isset($node->nid)) { 2054 foreach (array('status', 'promote', 'sticky') as $key) { 2055 $node->$key = in_array($key, $node_options); 2056 } 2057 global $user; 2058 $node->uid = $user->uid; 2059 } 2060 // Always use the default revision setting. 2061 $node->revision = in_array('revision', $node_options); 2062 2063 $form['#node'] = $node; 2064 2065 // Add a log field if the "Create new revision" option is checked, or if the 2066 // current user has the ability to check that option. 2067 if ($node->revision || user_access('administer nodes')) { 2068 $form['log'] = array( 2069 '#type' => 'textarea', 2070 '#title' => t('Log message'), 2071 '#rows' => 2, 2072 '#weight' => 20, 2073 '#description' => t('An explanation of the additions or updates being made to help other authors understand your motivations.'), 2074 ); 2075 } 2076 2077 // Node author information for administrators 2078 $form['author'] = array( 2079 '#type' => 'fieldset', 2080 '#access' => user_access('administer nodes'), 2081 '#title' => t('Authoring information'), 2082 '#collapsible' => TRUE, 2083 '#collapsed' => TRUE, 2084 '#weight' => 20, 2085 ); 2086 $form['author']['name'] = array('#type' => 'textfield', '#title' => t('Authored by'), '#maxlength' => 60, '#autocomplete_path' => 'user/autocomplete', '#default_value' => $node->name ? $node->name : '', '#weight' => -1, '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous'))))); 2087 $form['author']['date'] = array('#type' => 'textfield', '#title' => t('Authored on'), '#maxlength' => 25, '#description' => t('Format: %time. Leave blank to use the time of form submission.', array('%time' => $node->date))); 2088 2089 if (isset($node->nid)) { 2090 $form['author']['date']['#default_value'] = $node->date; 2091 } 2092 2093 // Node options for administrators 2094 $form['options'] = array( 2095 '#type' => 'fieldset', 2096 '#access' => user_access('administer nodes'), 2097 '#title' => t('Publishing options'), 2098 '#collapsible' => TRUE, 2099 '#collapsed' => TRUE, 2100 '#weight' => 25, 2101 ); 2102 $form['options']['status'] = array('#type' => 'checkbox', '#title' => t('Published'), '#default_value' => $node->status); 2103 $form['options']['promote'] = array('#type' => 'checkbox', '#title' => t('Promoted to front page'), '#default_value' => $node->promote); 2104 $form['options']['sticky'] = array('#type' => 'checkbox', '#title' => t('Sticky at top of lists'), '#default_value' => $node->sticky); 2105 $form['options']['revision'] = array('#type' => 'checkbox', '#title' => t('Create new revision'), '#default_value' => $node->revision); 2106 // These values are used when the user has no administrator access. 2107 foreach (array('uid', 'created') as $key) { 2108 $form[$key] = array('#type' => 'value', '#value' => $node->$key); 2109 } 2110 2111 // Add the buttons. 2112 $form['preview'] = array('#type' => 'button', '#value' => t('Preview'), '#weight' => 40); 2113 $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'), '#weight' => 45); 2114 if ($node->nid && node_access('delete', $node)) { 2115 $form['delete'] = array('#type' => 'button', '#value' => t('Delete'), '#weight' => 50); 2116 } 2117 $form['#after_build'] = array('node_form_add_preview'); 2118 $form['#base'] = 'node_form'; 2119 return $form; 2120 } 2121 2122 function node_form_add_preview($form) { 2123 global $form_values; 2124 2125 $op = isset($form_values['op']) ? $form_values['op'] : ''; 2126 if ($op == t('Preview')) { 2127 // Invoke full validation for the form, to protect against cross site 2128 // request forgeries (CSRF) and setting arbitrary values for fields such as 2129 // the input format. Preview the node only when form validation does not 2130 // set any errors. 2131 drupal_validate_form($form['form_id']['#value'], $form); 2132 if (!form_get_errors()) { 2133 // Because the node preview may display a form, we must render it 2134 // outside the node submission form tags using the #prefix property 2135 // (i.e. to prevent illegally nested forms). 2136 // If the node form already has a #prefix, we must preserve it. 2137 // In this case, we put the preview before the #prefix so we keep 2138 // the #prefix as "close" to the rest of the form as possible, 2139 // for example, to keep a <div> only around the form, not the 2140 // preview. We pass the global $form_values here to preserve 2141 // changes made during form validation. 2142 $preview = node_preview((object)$form_values); 2143 $form['#prefix'] = isset($form['#prefix']) ? $preview . $form['#prefix'] : $preview; 2144 } 2145 } 2146 if (variable_get('node_preview', 0) && (form_get_errors() || $op != t('Preview'))) { 2147 unset($form['submit']); 2148 } 2149 return $form; 2150 } 2151 2152 function theme_node_form($form) { 2153 $output = "\n<div class=\"node-form\">\n"; 2154 2155 // Admin form fields and submit buttons must be rendered first, because 2156 // they need to go to the bottom of the form, and so should not be part of 2157 // the catch-all call to drupal_render(). 2158 $admin = ''; 2159 if (isset($form['author'])) { 2160 $admin .= " <div class=\"authored\">\n"; 2161 $admin .= drupal_render($form['author']); 2162 $admin .= " </div>\n"; 2163 } 2164 if (isset($form['options'])) { 2165 $admin .= " <div class=\"options\">\n"; 2166 $admin .= drupal_render($form['options']); 2167 $admin .= " </div>\n"; 2168 } 2169 $buttons = drupal_render($form['preview']); 2170 $buttons .= drupal_render($form['submit']); 2171 $buttons .= isset($form['delete']) ? drupal_render($form['delete']) : ''; 2172 2173 // Everything else gets rendered here, and is displayed before the admin form 2174 // field and the submit buttons. 2175 $output .= " <div class=\"standard\">\n"; 2176 $output .= drupal_render($form); 2177 $output .= " </div>\n"; 2178 2179 if (!empty($admin)) { 2180 $output .= " <div class=\"admin\">\n"; 2181 $output .= $admin; 2182 $output .= " </div>\n"; 2183 } 2184 $output .= $buttons; 2185 $output .= "</div>\n"; 2186 2187 return $output; 2188 } 2189 2190 /** 2191 * Present a node submission form or a set of links to such forms. 2192 */ 2193 function node_add($type = NULL) { 2194 global $user; 2195 2196 $types = node_get_types(); 2197 $type = isset($type) ? str_replace('-', '_', $type) : NULL; 2198 // If a node type has been specified, validate its existence. 2199 if (isset($types[$type]) && node_access('create', $type)) { 2200 // Initialize settings: 2201 $node = array('uid' => $user->uid, 'name' => $user->name, 'type' => $type); 2202 2203 drupal_set_title(t('Submit @name', array('@name' => $types[$type]->name))); 2204 $output = drupal_get_form($type .'_node_form', $node); 2205 } 2206 else { 2207 // If no (valid) node type has been provided, display a node type overview. 2208 foreach ($types as $type) { 2209 if (function_exists($type->module .'_form') && node_access('create', $type->type)) { 2210 $type_url_str = str_replace('_', '-', $type->type); 2211 $title = t('Add a new @s.', array('@s' => $type->name)); 2212 $out = '<dt>'. l(drupal_ucfirst($type->name), "node/add/$type_url_str", array('title' => $title)) .'</dt>'; 2213 $out .= '<dd>'. filter_xss_admin($type->description) .'</dd>'; 2214 $item[$type->type] = $out; 2215 } 2216 } 2217 2218 if (isset($item)) { 2219 uksort($item, 'strnatcasecmp'); 2220 $output = t('Choose the appropriate item from the list:') .'<dl>'. implode('', $item) .'</dl>'; 2221 } 2222 else { 2223 $output = t('No content types available.'); 2224 } 2225 } 2226 2227 return $output; 2228 } 2229 2230 /** 2231 * Generate a node preview. 2232 */ 2233 function node_preview($node) { 2234 if (node_access('create', $node) || node_access('update', $node)) { 2235 // Load the user's name when needed: 2236 if (isset($node->name)) { 2237 // The use of isset() is mandatory in the context of user IDs, because 2238 // user ID 0 denotes the anonymous user. 2239 if ($user = user_load(array('name' => $node->name))) { 2240 $node->uid = $user->uid; 2241 $node->picture = $user->picture; 2242 } 2243 else { 2244 $node->uid = 0; // anonymous user 2245 } 2246 } 2247 else if ($node->uid) { 2248 $user = user_load(array('uid' => $node->uid)); 2249 $node->name = $user->name; 2250 $node->picture = $user->picture; 2251 } 2252 2253 // Set the timestamps when needed: 2254 if ($node->date) { 2255 $node->created = strtotime($node->date); 2256 } 2257 $node->changed = time(); 2258 2259 // Extract a teaser, if it hasn't been set (e.g. by a module-provided 2260 // 'teaser' form item). 2261 if (!isset($node->teaser)) { 2262 $node->teaser = node_teaser($node->body, $node->format); 2263 } 2264 2265 // Display a preview of the node: 2266 // Previewing alters $node so it needs to be cloned. 2267 if (!form_get_errors()) { 2268 $cloned_node = drupal_clone($node); 2269 $cloned_node->in_preview = TRUE; 2270 $output = theme('node_preview', $cloned_node); 2271 } 2272 drupal_set_title(t('Preview')); 2273 drupal_set_breadcrumb(array(l(t('Home'), NULL), l(t('Create content'), 'node/add'), l(t('Submit @name', array('@name' => node_get_types('name', $node))), 'node/add/'. $node->type))); 2274 2275 return $output; 2276 } 2277 } 2278 2279 /** 2280 * Display a node preview for display during node creation and editing. 2281 * 2282 * @param $node 2283 * The node object which is being previewed. 2284 */ 2285 function theme_node_preview($node) { 2286 $output = '<div class="preview">'; 2287 if ($node->teaser && $node->teaser != $node->body) { 2288 drupal_set_message(t('The trimmed version of your post shows what your post looks like when promoted to the main page or when exported for syndication. You can insert the delimiter "<!--break-->" (without the quotes) to fine-tune where your post gets split.')); 2289 $output .= '<h3>'. t('Preview trimmed version') .'</h3>'; 2290 $output .= node_view(drupal_clone($node), 1, FALSE, 0); 2291 $output .= '<h3>'. t('Preview full version') .'</h3>'; 2292 $output .= node_view($node, 0, FALSE, 0); 2293 } 2294 else { 2295 $output .= node_view($node, 0, FALSE, 0); 2296 } 2297 $output .= "</div>\n"; 2298 2299 return $output; 2300 } 2301 2302 function theme_node_log_message($log) { 2303 return '<div class="log"><div class="title">'. t('Log') .':</div>'. $log .'</div>'; 2304 } 2305 2306 function node_form_submit($form_id, $form_values) { 2307 global $user; 2308 2309 // Fix up the node when required: 2310 $node = node_submit($form_values); 2311 2312 // Prepare the node's body: 2313 if ($node->nid) { 2314 node_save($node); 2315 watchdog('content', t('@type: updated %title.', array('@type' => t($node->type), '%title' => $node->title)), WATCHDOG_NOTICE, l(t('view'), 'node/'. $node->nid)); 2316 drupal_set_message(t('The %post has been updated.', array('%post' => node_get_types('name', $node)))); 2317 } 2318 else { 2319 node_save($node); 2320 watchdog('content', t('@type: added %title.', array('@type' => t($node->type), '%title' => $node->title)), WATCHDOG_NOTICE, l(t('view'), "node/$node->nid")); 2321 drupal_set_message(t('Your %post has been created.', array('%post' => node_get_types('name', $node)))); 2322 } 2323 if ($node->nid) { 2324 if (node_access('view', $node)) { 2325 return 'node/'. $node->nid; 2326 } 2327 else { 2328 return ''; 2329 } 2330 } 2331 // it is very unlikely we get here 2332 return FALSE; 2333 } 2334 2335 /** 2336 * Menu callback -- ask for confirmation of node deletion 2337 */ 2338 function node_delete_confirm($node) { 2339 $form['nid'] = array('#type' => 'value', '#value' => $node->nid); 2340 2341 return confirm_form($form, 2342 t('Are you sure you want to delete %title?', array('%title' => $node->title)), 2343 isset($_GET['destination']) ? $_GET['destination'] : 'node/'. $node->nid, 2344 t('This action cannot be undone.'), 2345 t('Delete'), t('Cancel')); 2346 } 2347 2348 /** 2349 * Execute node deletion 2350 */ 2351 function node_delete_confirm_submit($form_id, $form_values) { 2352 if ($form_values['confirm']) { 2353 node_delete($form_values['nid']); 2354 } 2355 2356 return '<front>'; 2357 } 2358 2359 /** 2360 * Delete a node. 2361 */ 2362 function node_delete($nid) { 2363 2364 $node = node_load($nid); 2365 2366 if (node_access('delete', $node)) { 2367 db_query('DELETE FROM {node} WHERE nid = %d', $node->nid); 2368 db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid); 2369 2370 // Call the node-specific callback (if any): 2371 node_invoke($node, 'delete'); 2372 node_invoke_nodeapi($node, 'delete'); 2373 2374 // Clear the cache so an anonymous poster can see the node being deleted. 2375 cache_clear_all(); 2376 2377 // Remove this node from the search index if needed. 2378 if (function_exists('search_wipe')) { 2379 search_wipe($node->nid, 'node'); 2380 } 2381 drupal_set_message(t('%title has been deleted.', array('%title' => $node->title))); 2382 watchdog('content', t('@type: deleted %title.', array('@type' => t($node->type), '%title' => $node->title))); 2383 } 2384 } 2385 2386 /** 2387 * Menu callback for revisions related activities. 2388 */ 2389 function node_revisions() { 2390 if (is_numeric(arg(1)) && arg(2) == 'revisions') { 2391 $op = arg(4) ? arg(4) : 'overview'; 2392 switch ($op) { 2393 case 'overview': 2394 $node = node_load(arg(1)); 2395 if ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node)) { 2396 return node_revision_overview($node); 2397 } 2398 drupal_access_denied(); 2399 return; 2400 case 'view': 2401 if (is_numeric(arg(3))) { 2402 $node = node_load(arg(1), arg(3)); 2403 if ($node->nid) { 2404 if ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node)) { 2405 drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp)))); 2406 return node_show($node, arg(2)); 2407 } 2408 drupal_access_denied(); 2409 return; 2410 } 2411 } 2412 break; 2413 } 2414 } 2415 drupal_not_found(); 2416 } 2417 2418 /** 2419 * Menu callback; Generate a listing of promoted nodes. 2420 */ 2421 function node_page_default($arg = NULL) { 2422 // Prevent fallback to this page for node/*. 2423 if (isset($arg)) { 2424 return MENU_NOT_FOUND; 2425 } 2426 2427 $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)); 2428 2429 if (db_num_rows($result)) { 2430 $feed_url = url('rss.xml', NULL, NULL, TRUE); 2431 drupal_add_feed($feed_url, variable_get('site_name', 'Drupal') .' '. t('RSS')); 2432 2433 $output = ''; 2434 while ($node = db_fetch_object($result)) { 2435 $output .= node_view(node_load($node->nid), 1); 2436 } 2437 $output .= theme('pager', NULL, variable_get('default_nodes_main', 10)); 2438 } 2439 else { 2440 // Check for existence of admin account. 2441 $admin = db_result(db_query('SELECT uid FROM {users} WHERE uid = 1')); 2442 2443 $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>'); 2444 $default_message .= '<ol>'; 2445 2446 if (!$admin) { 2447 $default_message .= '<li>'. t('<strong>Create your administrator account</strong> To begin, <a href="@register">create the first account</a>. This account will have full administration rights and will allow you to configure your website.', array('@register' => url('user/register'))) .'</li>'; 2448 } 2449 $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>'; 2450 $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>'; 2451 $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>'; 2452 $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>'; 2453 $default_message .= '</ol>'; 2454 $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>'; 2455 2456 $output = '<div id="first-time">'. $default_message .'</div>'; 2457 } 2458 drupal_set_title(''); 2459 2460 return $output; 2461 } 2462 2463 /** 2464 * Menu callback; view a single node. 2465 */ 2466 function node_page_view($node, $cid = NULL) { 2467 drupal_set_title(check_plain($node->title)); 2468 return node_show($node, $cid); 2469 } 2470 2471 /** 2472 * Menu callback; presents the node editing form, or redirects to delete confirmation. 2473 */ 2474 function node_page_edit($node) { 2475 if ($_POST['op'] == t('Delete')) { 2476 // Note: we redirect from node/nid/edit to node/nid/delete to make the tabs disappear. 2477 if ($_REQUEST['destination']) { 2478 $destination = drupal_get_destination(); 2479 unset($_REQUEST['destination']); 2480 } 2481 drupal_goto('node/'. $node->nid .'/delete', $destination); 2482 } 2483 2484 drupal_set_title(check_plain($node->title)); 2485 return drupal_get_form($node->type .'_node_form', $node); 2486 } 2487 2488 /** 2489 * shutdown function to make sure we always mark the last node processed. 2490 */ 2491 function node_update_shutdown() { 2492 global $last_change, $last_nid; 2493 2494 if ($last_change && $last_nid) { 2495 variable_set('node_cron_last', $last_change); 2496 variable_set('node_cron_last_nid', $last_nid); 2497 } 2498 } 2499 2500 /** 2501 * Implementation of hook_update_index(). 2502 */ 2503 function node_update_index() { 2504 global $last_change, $last_nid; 2505 2506 register_shutdown_function('node_update_shutdown'); 2507 2508 $last = variable_get('node_cron_last', 0); 2509 $last_nid = variable_get('node_cron_last_nid', 0); 2510 $limit = (int)variable_get('search_cron_limit', 100); 2511 2512 // Store the maximum possible comments per thread (used for ranking by reply count) 2513 variable_set('node_cron_comments_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(comment_count) FROM {node_comment_statistics}')))); 2514 variable_set('node_cron_views_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(totalcount) FROM {node_counter}')))); 2515 2516 $result = db_query_range('SELECT GREATEST(IF(c.last_comment_timestamp IS NULL, 0, c.last_comment_timestamp), n.changed) as last_change, n.nid FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND ((GREATEST(n.changed, c.last_comment_timestamp) = %d AND n.nid > %d) OR (n.changed > %d OR c.last_comment_timestamp > %d)) ORDER BY GREATEST(n.changed, c.last_comment_timestamp) ASC, n.nid ASC', $last, $last_nid, $last, $last, $last, 0, $limit); 2517 2518 while ($node = db_fetch_object($result)) { 2519 $last_change = $node->last_change; 2520 $last_nid = $node->nid; 2521 $node = node_load($node->nid); 2522 2523 // Build the node body. 2524 $node = node_build_content($node, FALSE, FALSE); 2525 $node->body = drupal_render($node->content); 2526 2527 // Allow modules to modify the fully-built node. 2528 node_invoke_nodeapi($node, 'alter'); 2529 2530 $text = '<h1>'. check_plain($node->title) .'</h1>'. $node->body; 2531 2532 // Fetch extra data normally not visible 2533 $extra = node_invoke_nodeapi($node, 'update index'); 2534 foreach ($extra as $t) { 2535 $text .= $t; 2536 } 2537 2538 // Update index 2539 search_index($node->nid, 'node', $text); 2540 } 2541 } 2542 2543 /** 2544 * Implementation of hook_form_alter(). 2545 */ 2546 function node_form_alter($form_id, &$form) { 2547 // Advanced node search form 2548 if ($form_id == 'search_form' && arg(1) == 'node' && user_access('use advanced search')) { 2549 // Keyword boxes: 2550 $form['advanced'] = array( 2551 '#type' => 'fieldset', 2552 '#title' => t('Advanced search'), 2553 '#collapsible' => TRUE, 2554 '#collapsed' => TRUE, 2555 '#attributes' => array('class' => 'search-advanced'), 2556 ); 2557 $form['advanced']['keywords'] = array( 2558 '#prefix' => '<div class="criterion">', 2559 '#suffix' => '</div>', 2560 ); 2561 $form['advanced']['keywords']['or'] = array( 2562 '#type' => 'textfield', 2563 '#title' => t('Containing any of the words'), 2564 '#size' => 30, 2565 '#maxlength' => 255, 2566 ); 2567 $form['advanced']['keywords']['phrase'] = array( 2568 '#type' => 'textfield', 2569 '#title' => t('Containing the phrase'), 2570 '#size' => 30, 2571 '#maxlength' => 255, 2572 ); 2573 $form['advanced']['keywords']['negative'] = array( 2574 '#type' => 'textfield', 2575 '#title' => t('Containing none of the words'), 2576 '#size' => 30, 2577 '#maxlength' => 255, 2578 ); 2579 2580 // Taxonomy box: 2581 if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) { 2582 $form['advanced']['category'] = array( 2583 '#type' => 'select', 2584 '#title' => t('Only in the category(s)'), 2585 '#prefix' => '<div class="criterion">', 2586 '#size' => 10, 2587 '#suffix' => '</div>', 2588 '#options' => $taxonomy, 2589 '#multiple' => TRUE, 2590 ); 2591 } 2592 2593 // Node types: 2594 $types = array_map('check_plain', node_get_types('names')); 2595 $form['advanced']['type'] = array( 2596 '#type' => 'checkboxes', 2597 '#title' => t('Only of the type(s)'), 2598 '#prefix' => '<div class="criterion">', 2599 '#suffix' => '</div>', 2600 '#options' => $types, 2601 ); 2602 $form['advanced']['submit'] = array( 2603 '#type' => 'submit', 2604 '#value' => t('Advanced search'), 2605 '#prefix' => '<div class="action">', 2606 '#suffix' => '</div>', 2607 ); 2608 2609 $form['#validate']['node_search_validate'] = array(); 2610 } 2611 } 2612 2613 /** 2614 * Form API callback for the search form. Registered in node_form_alter(). 2615 */ 2616 function node_search_validate($form_id, $form_values, $form) { 2617 // Initialise using any existing basic search keywords. 2618 $keys = $form_values['processed_keys']; 2619 2620 // Insert extra restrictions into the search keywords string. 2621 if (isset($form_values['type']) && is_array($form_values['type'])) { 2622 // Retrieve selected types - Forms API sets the value of unselected checkboxes to 0. 2623 $form_values['type'] = array_filter($form_values['type']); 2624 if (count($form_values['type'])) { 2625 $keys = search_query_insert($keys, 'type', implode(',', array_keys($form_values['type']))); 2626 } 2627 } 2628 2629 if (isset($form_values['category']) && is_array($form_values['category'])) { 2630 $keys = search_query_insert($keys, 'category', implode(',', $form_values['category'])); 2631 } 2632 if ($form_values['or'] != '') { 2633 if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $form_values['or'], $matches)) { 2634 $keys .= ' '. implode(' OR ', $matches[1]); 2635 } 2636 } 2637 if ($form_values['negative'] != '') { 2638 if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $form_values['negative'], $matches)) { 2639 $keys .= ' -'. implode(' -', $matches[1]); 2640 } 2641 } 2642 if ($form_values['phrase'] != '') { 2643 $keys .= ' "'. str_replace('"', ' ', $form_values['phrase']) .'"'; 2644 } 2645 if (!empty($keys)) { 2646 form_set_value($form['basic']['inline']['processed_keys'], trim($keys)); 2647 } 2648 } 2649 2650 /** 2651 * @defgroup node_access Node access rights 2652 * @{ 2653 * The node access system determines who can do what to which nodes. 2654 * 2655 * In determining access rights for a node, node_access() first checks 2656 * whether the user has the "administer nodes" permission. Such users have 2657 * unrestricted access to all nodes. Then the node module's hook_access() 2658 * is called, and a TRUE or FALSE return value will grant or deny access. 2659 * This allows, for example, the blog module to always grant access to the 2660 * blog author, and for the book module to always deny editing access to 2661 * PHP pages. 2662 * 2663 * If node module does not intervene (returns NULL), then the 2664 * node_access table is used to determine access. All node access 2665 * modules are queried using hook_node_grants() to assemble a list of 2666 * "grant IDs" for the user. This list is compared against the table. 2667 * If any row contains the node ID in question (or 0, which stands for "all 2668 * nodes"), one of the grant IDs returned, and a value of TRUE for the 2669 * operation in question, then access is granted. Note that this table is a 2670 * list of grants; any matching row is sufficient to grant access to the 2671 * node. 2672 * 2673 * In node listings, the process above is followed except that 2674 * hook_access() is not called on each node for performance reasons and for 2675 * proper functioning of the pager system. When adding a node listing to your 2676 * module, be sure to use db_rewrite_sql() to add 2677 * the appropriate clauses to your query for access checks. 2678 * 2679 * To see how to write a node access module of your own, see 2680 * node_access_example.module. 2681 */ 2682 2683 /** 2684 * Determine whether the current user may perform the given operation on the 2685 * specified node. 2686 * 2687 * @param $op 2688 * The operation to be performed on the node. Possible values are: 2689 * - "view" 2690 * - "update" 2691 * - "delete" 2692 * - "create" 2693 * @param $node 2694 * The node object (or node array) on which the operation is to be performed, 2695 * or node type (e.g. 'forum') for "create" operation. 2696 * @return 2697 * TRUE if the operation may be performed. 2698 */ 2699 function node_access($op, $node = NULL) { 2700 global $user; 2701 2702 // Convert the node to an object if necessary: 2703 if ($op != 'create') { 2704 $node = (object)$node; 2705 } 2706 // If the node is in a restricted format, disallow editing. 2707 if ($op == 'update' && !filter_access($node->format)) { 2708 return FALSE; 2709 } 2710 2711 if (user_access('administer nodes')) { 2712 return TRUE; 2713 } 2714 2715 if (!user_access('access content')) { 2716 return FALSE; 2717 } 2718 2719 // Can't use node_invoke(), because the access hook takes the $op parameter 2720 // before the $node parameter. 2721 $module = node_get_types('module', $node); 2722 if ($module == 'node') { 2723 $module = 'node_content'; // Avoid function name collisions. 2724 } 2725 $access = module_invoke($module, 'access', $op, $node); 2726 if (!is_null($access)) { 2727 return $access; 2728 } 2729 2730 // If the module did not override the access rights, use those set in the 2731 // node_access table. 2732 if ($op != 'create' && $node->nid && $node->status) { 2733 $grants = array(); 2734 foreach (node_access_grants($op) as $realm => $gids) { 2735 foreach ($gids as $gid) { 2736 $grants[] = "(gid = $gid AND realm = '$realm')"; 2737 } 2738 } 2739 2740 $grants_sql = ''; 2741 if (count($grants)) { 2742 $grants_sql = 'AND ('. implode(' OR ', $grants) .')'; 2743 } 2744 2745 $sql = "SELECT COUNT(*) FROM {node_access} WHERE (nid = 0 OR nid = %d) $grants_sql AND grant_$op >= 1"; 2746 $result = db_query($sql, $node->nid); 2747 return (db_result($result)); 2748 } 2749 2750 // Let authors view their own nodes. 2751 if ($op == 'view' && $user->uid == $node->uid && $user->uid != 0) { 2752 return TRUE; 2753 } 2754 2755 return FALSE; 2756 } 2757 2758 /** 2759 * Generate an SQL join clause for use in fetching a node listing. 2760 * 2761 * @param $node_alias 2762 * If the node table has been given an SQL alias other than the default 2763 * "n", that must be passed here. 2764 * @param $node_access_alias 2765 * If the node_access table has been given an SQL alias other than the default 2766 * "na", that must be passed here. 2767 * @return 2768 * An SQL join clause. 2769 */ 2770 function _node_access_join_sql($node_alias = 'n', $node_access_alias = 'na') { 2771 if (user_access('administer nodes')) { 2772 return ''; 2773 } 2774 2775 return 'INNER JOIN {node_access} '. $node_access_alias .' ON '. $node_access_alias .'.nid = '. $node_alias .'.nid'; 2776 } 2777 2778 /** 2779 * Generate an SQL where clause for use in fetching a node listing. 2780 * 2781 * @param $op 2782 * The operation that must be allowed to return a node. 2783 * @param $node_access_alias 2784 * If the node_access table has been given an SQL alias other than the default 2785 * "na", that must be passed here. 2786 * @return 2787 * An SQL where clause. 2788 */ 2789 function _node_access_where_sql($op = 'view', $node_access_alias = 'na', $uid = NULL) { 2790 if (user_access('administer nodes')) { 2791 return; 2792 } 2793 2794 $grants = array(); 2795 foreach (node_access_grants($op, $uid) as $realm => $gids) { 2796 foreach ($gids as $gid) { 2797 $grants[] = "($node_access_alias.gid = $gid AND $node_access_alias.realm = '$realm')"; 2798 } 2799 } 2800 2801 $grants_sql = ''; 2802 if (count($grants)) { 2803 $grants_sql = 'AND ('. implode(' OR ', $grants) .')'; 2804 } 2805 2806 $sql = "$node_access_alias.grant_$op >= 1 $grants_sql"; 2807 return $sql; 2808 } 2809 2810 /** 2811 * Fetch an array of permission IDs granted to the given user ID. 2812 * 2813 * The implementation here provides only the universal "all" grant. A node 2814 * access module should implement hook_node_grants() to provide a grant 2815 * list for the user. 2816 * 2817 * @param $op 2818 * The operation that the user is trying to perform. 2819 * @param $uid 2820 * The user ID performing the operation. If omitted, the current user is used. 2821 * @return 2822 * An associative array in which the keys are realms, and the values are 2823 * arrays of grants for those realms. 2824 */ 2825 function node_access_grants($op, $uid = NULL) { 2826 global $user; 2827 2828 if (isset($uid)) { 2829 $user_object = user_load(array('uid' => $uid)); 2830 } 2831 else { 2832 $user_object = $user; 2833 } 2834 2835 return array_merge(array('all' => array(0)), module_invoke_all('node_grants', $user_object, $op)); 2836 } 2837 2838 /** 2839 * Determine whether the user has a global viewing grant for all nodes. 2840 */ 2841 function node_access_view_all_nodes() { 2842 static $access; 2843 2844 if (!isset($access)) { 2845 $grants = array(); 2846 foreach (node_access_grants('view') as $realm => $gids) { 2847 foreach ($gids as $gid) { 2848 $grants[] = "(gid = $gid AND realm = '$realm')"; 2849 } 2850 } 2851 2852 $grants_sql = ''; 2853 if (count($grants)) { 2854 $grants_sql = 'AND ('. implode(' OR ', $grants) .')'; 2855 } 2856 2857 $sql = "SELECT COUNT(*) FROM {node_access} WHERE nid = 0 $grants_sql AND grant_view >= 1"; 2858 $result = db_query($sql); 2859 $access = db_result($result); 2860 } 2861 2862 return $access; 2863 } 2864 2865 /** 2866 * Implementation of hook_db_rewrite_sql 2867 */ 2868 function node_db_rewrite_sql($query, $primary_table, $primary_field) { 2869 if ($primary_field == 'nid' && !node_access_view_all_nodes()) { 2870 $return['join'] = _node_access_join_sql($primary_table); 2871 $return['where'] = _node_access_where_sql(); 2872 $return['distinct'] = 1; 2873 return $return; 2874 } 2875 } 2876 2877 /** 2878 * This function will call module invoke to get a list of grants and then 2879 * write them to the database. It is called at node save, and should be 2880 * called by modules whenever something other than a node_save causes 2881 * the permissions on a node to change. 2882 * 2883 * This function is the only function that should write to the node_access 2884 * table. 2885 * 2886 * @param $node 2887 * The $node to acquire grants for. 2888 */ 2889 function node_access_acquire_grants($node) { 2890 $grants = module_invoke_all('node_access_records', $node); 2891 if (!$grants) { 2892 $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0); 2893 } 2894 else { 2895 // retain grants by highest priority 2896 $grant_by_priority = array(); 2897 foreach ($grants as $g) { 2898 $grant_by_priority[intval($g['priority'])][] = $g; 2899 } 2900 krsort($grant_by_priority); 2901 $grants = array_shift($grant_by_priority); 2902 } 2903 2904 node_access_write_grants($node, $grants); 2905 } 2906 2907 /** 2908 * This function will write a list of grants to the database, deleting 2909 * any pre-existing grants. If a realm is provided, it will only 2910 * delete grants from that realm, but it will always delete a grant 2911 * from the 'all' realm. Modules which utilize node_access can 2912 * use this function when doing mass updates due to widespread permission 2913 * changes. 2914 * 2915 * @param $node 2916 * The $node being written to. All that is necessary is that it contain a nid. 2917 * @param $grants 2918 * A list of grants to write. Each grant is an array that must contain the 2919 * following keys: realm, gid, grant_view, grant_update, grant_delete. 2920 * The realm is specified by a particular module; the gid is as well, and 2921 * is a module-defined id to define grant privileges. each grant_* field 2922 * is a boolean value. 2923 * @param $realm 2924 * If provided, only read/write grants for that realm. 2925 * @param $delete 2926 * If false, do not delete records. This is only for optimization purposes, 2927 * and assumes the caller has already performed a mass delete of some form. 2928 */ 2929 function node_access_write_grants($node, $grants, $realm = NULL, $delete = TRUE) { 2930 if ($delete) { 2931 $query = 'DELETE FROM {node_access} WHERE nid = %d'; 2932 if ($realm) { 2933 $query .= " AND realm in ('%s', 'all')"; 2934 } 2935 db_query($query, $node->nid, $realm); 2936 } 2937 2938 // Only perform work when node_access modules are active. 2939 if (count(module_implements('node_grants'))) { 2940 foreach ($grants as $grant) { 2941 if ($realm && $realm != $grant['realm']) { 2942 continue; 2943 } 2944 // Only write grants; denies are implicit. 2945 if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) { 2946 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']); 2947 } 2948 } 2949 } 2950 } 2951 2952 /** 2953 * Rebuild the node access database. This is occasionally needed by modules 2954 * that make system-wide changes to access levels. 2955 */ 2956 function node_access_rebuild() { 2957 db_query("DELETE FROM {node_access}"); 2958 // only recalculate if site is using a node_access module 2959 if (count(module_implements('node_grants'))) { 2960 // If not in 'safe mode', increase the maximum execution time: 2961 if (!ini_get('safe_mode')) { 2962 set_time_limit(240); 2963 } 2964 $result = db_query("SELECT nid FROM {node}"); 2965 while ($node = db_fetch_object($result)) { 2966 $loaded_node = node_load($node->nid, NULL, TRUE); 2967 // To preserve database integrity, only aquire grants if the node 2968 // loads successfully. 2969 if (!empty($loaded_node)) { 2970 node_access_acquire_grants($loaded_node); 2971 } 2972 } 2973 } 2974 else { 2975 // not using any node_access modules. add the default grant. 2976 db_query("INSERT INTO {node_access} VALUES (0, 0, 'all', 1, 0, 0)"); 2977 } 2978 cache_clear_all(); 2979 } 2980 /** 2981 * @} End of "defgroup node_access". 2982 */ 2983 2984 2985 /** 2986 * @defgroup node_content Hook implementations for user-created content types. 2987 * @{ 2988 */ 2989 2990 /** 2991 * Implementation of hook_access(). 2992 */ 2993 function node_content_access($op, $node) { 2994 global $user; 2995 $type = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type); 2996 2997 if ($op == 'create') { 2998 return user_access('create '. $type .' content'); 2999 } 3000 3001 if ($op == 'update' || $op == 'delete') { 3002 if (user_access('edit '. $type .' content') || (user_access('edit own '. $type .' content') && ($user->uid == $node->uid))) { 3003 return TRUE; 3004 } 3005 } 3006 } 3007 3008 /** 3009 * Implementation of hook_form(). 3010 */ 3011 function node_content_form($node) { 3012 $type = node_get_types('type', $node); 3013 $form = array(); 3014 3015 if ($type->has_title) { 3016 $form['title'] = array( 3017 '#type' => 'textfield', 3018 '#title' => check_plain($type->title_label), 3019 '#required' => TRUE, 3020 '#default_value' => $node->title, 3021 '#weight' => -5, 3022 ); 3023 } 3024 3025 if ($type->has_body) { 3026 $form['body_filter']['body'] = array( 3027 '#type' => 'textarea', 3028 '#title' => check_plain($type->body_label), 3029 '#default_value' => $node->body, 3030 '#rows' => 20, 3031 '#required' => ($type->min_word_count > 0)); 3032 $form['body_filter']['format'] = filter_form($node->format); 3033 } 3034 3035 return $form; 3036 } 3037 3038 /** 3039 * @} End of "defgroup node_content". 3040 */ 3041 3042 /** 3043 * Implementation of hook_forms(). All node forms share the same form handler 3044 */ 3045 function node_forms() { 3046 foreach (array_keys(node_get_types()) as $type) { 3047 $forms[$type .'_node_form']['callback'] = 'node_form'; 3048 } 3049 return $forms; 3050 }
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Fri Nov 30 16:20:15 2007 | par Balluche grâce à PHPXref 0.7 |
![]() |