[ Index ] |
|
Code source de Drupal 5.3 |
1 <?php 2 // $Id: common.inc,v 1.611.2.12 2007/10/17 21:28:59 drumm Exp $ 3 4 /** 5 * @file 6 * Common functions that many Drupal modules will need to reference. 7 * 8 * The functions that are critical and need to be available even when serving 9 * a cached page are instead located in bootstrap.inc. 10 */ 11 12 /** 13 * Return status for saving which involved creating a new item. 14 */ 15 define('SAVED_NEW', 1); 16 17 /** 18 * Return status for saving which involved an update to an existing item. 19 */ 20 define('SAVED_UPDATED', 2); 21 22 /** 23 * Return status for saving which deleted an existing item. 24 */ 25 define('SAVED_DELETED', 3); 26 27 /** 28 * Set content for a specified region. 29 * 30 * @param $region 31 * Page region the content is assigned to. 32 * 33 * @param $data 34 * Content to be set. 35 */ 36 function drupal_set_content($region = NULL, $data = NULL) { 37 static $content = array(); 38 39 if (!is_null($region) && !is_null($data)) { 40 $content[$region][] = $data; 41 } 42 return $content; 43 } 44 45 /** 46 * Get assigned content. 47 * 48 * @param $region 49 * A specified region to fetch content for. If NULL, all regions will be returned. 50 * 51 * @param $delimiter 52 * Content to be inserted between exploded array elements. 53 */ 54 function drupal_get_content($region = NULL, $delimiter = ' ') { 55 $content = drupal_set_content(); 56 if (isset($region)) { 57 if (isset($content[$region]) && is_array($content[$region])) { 58 return implode($delimiter, $content[$region]); 59 } 60 } 61 else { 62 foreach (array_keys($content) as $region) { 63 if (is_array($content[$region])) { 64 $content[$region] = implode($delimiter, $content[$region]); 65 } 66 } 67 return $content; 68 } 69 } 70 71 /** 72 * Set the breadcrumb trail for the current page. 73 * 74 * @param $breadcrumb 75 * Array of links, starting with "home" and proceeding up to but not including 76 * the current page. 77 */ 78 function drupal_set_breadcrumb($breadcrumb = NULL) { 79 static $stored_breadcrumb; 80 81 if (!is_null($breadcrumb)) { 82 $stored_breadcrumb = $breadcrumb; 83 } 84 return $stored_breadcrumb; 85 } 86 87 /** 88 * Get the breadcrumb trail for the current page. 89 */ 90 function drupal_get_breadcrumb() { 91 $breadcrumb = drupal_set_breadcrumb(); 92 93 if (is_null($breadcrumb)) { 94 $breadcrumb = menu_get_active_breadcrumb(); 95 } 96 97 return $breadcrumb; 98 } 99 100 /** 101 * Add output to the head tag of the HTML page. 102 * This function can be called as long the headers aren't sent. 103 */ 104 function drupal_set_html_head($data = NULL) { 105 static $stored_head = ''; 106 107 if (!is_null($data)) { 108 $stored_head .= $data ."\n"; 109 } 110 return $stored_head; 111 } 112 113 /** 114 * Retrieve output to be displayed in the head tag of the HTML page. 115 */ 116 function drupal_get_html_head() { 117 $output = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"; 118 return $output . drupal_set_html_head(); 119 } 120 121 /** 122 * Reset the static variable which holds the aliases mapped for this request. 123 */ 124 function drupal_clear_path_cache() { 125 drupal_lookup_path('wipe'); 126 } 127 128 /** 129 * Set an HTTP response header for the current page. 130 * 131 * Note: when sending a Content-Type header, always include a 'charset' type 132 * too. This is necessary to avoid security bugs (e.g. UTF-7 XSS). 133 */ 134 function drupal_set_header($header = NULL) { 135 // We use an array to guarantee there are no leading or trailing delimiters. 136 // Otherwise, header('') could get called when serving the page later, which 137 // ends HTTP headers prematurely on some PHP versions. 138 static $stored_headers = array(); 139 140 if (strlen($header)) { 141 header($header); 142 $stored_headers[] = $header; 143 } 144 return implode("\n", $stored_headers); 145 } 146 147 /** 148 * Get the HTTP response headers for the current page. 149 */ 150 function drupal_get_headers() { 151 return drupal_set_header(); 152 } 153 154 /** 155 * Add a feed URL for the current page. 156 * 157 * @param $url 158 * The url for the feed 159 * @param $title 160 * The title of the feed 161 */ 162 function drupal_add_feed($url = NULL, $title = '') { 163 static $stored_feed_links = array(); 164 165 if (!is_null($url)) { 166 $stored_feed_links[$url] = theme('feed_icon', $url); 167 168 drupal_add_link(array('rel' => 'alternate', 169 'type' => 'application/rss+xml', 170 'title' => $title, 171 'href' => $url)); 172 } 173 return $stored_feed_links; 174 } 175 176 /** 177 * Get the feed URLs for the current page. 178 * 179 * @param $delimiter 180 * The delimiter to split feeds by 181 */ 182 function drupal_get_feeds($delimiter = "\n") { 183 $feeds = drupal_add_feed(); 184 return implode($feeds, $delimiter); 185 } 186 187 /** 188 * @name HTTP handling 189 * @{ 190 * Functions to properly handle HTTP responses. 191 */ 192 193 /** 194 * Parse an array into a valid urlencoded query string. 195 * 196 * @param $query 197 * The array to be processed e.g. $_GET 198 * @param $exclude 199 * The array filled with keys to be excluded. Use parent[child] to exclude nested items. 200 * @param $parent 201 * Should not be passed, only used in recursive calls 202 * @return 203 * urlencoded string which can be appended to/as the URL query string 204 */ 205 function drupal_query_string_encode($query, $exclude = array(), $parent = '') { 206 $params = array(); 207 208 foreach ($query as $key => $value) { 209 $key = drupal_urlencode($key); 210 if ($parent) { 211 $key = $parent .'['. $key .']'; 212 } 213 214 if (in_array($key, $exclude)) { 215 continue; 216 } 217 218 if (is_array($value)) { 219 $params[] = drupal_query_string_encode($value, $exclude, $key); 220 } 221 else { 222 $params[] = $key .'='. drupal_urlencode($value); 223 } 224 } 225 226 return implode('&', $params); 227 } 228 229 /** 230 * Prepare a destination query string for use in combination with 231 * drupal_goto(). Used to direct the user back to the referring page 232 * after completing a form. By default the current URL is returned. 233 * If a destination exists in the previous request, that destination 234 * is returned. As such, a destination can persist across multiple 235 * pages. 236 * 237 * @see drupal_goto() 238 */ 239 function drupal_get_destination() { 240 if (isset($_REQUEST['destination'])) { 241 return 'destination='. urlencode($_REQUEST['destination']); 242 } 243 else { 244 // Use $_GET here to retrieve the original path in source form. 245 $path = isset($_GET['q']) ? $_GET['q'] : ''; 246 $query = drupal_query_string_encode($_GET, array('q')); 247 if ($query != '') { 248 $path .= '?'. $query; 249 } 250 return 'destination='. urlencode($path); 251 } 252 } 253 254 /** 255 * Send the user to a different Drupal page. 256 * 257 * This issues an on-site HTTP redirect. The function makes sure the redirected 258 * URL is formatted correctly. 259 * 260 * Usually the redirected URL is constructed from this function's input 261 * parameters. However you may override that behavior by setting a 262 * <em>destination</em> in either the $_REQUEST-array (i.e. by using 263 * the query string of an URI) or the $_REQUEST['edit']-array (i.e. by 264 * using a hidden form field). This is used to direct the user back to 265 * the proper page after completing a form. For example, after editing 266 * a post on the 'admin/content/node'-page or after having logged on using the 267 * 'user login'-block in a sidebar. The function drupal_get_destination() 268 * can be used to help set the destination URL. 269 * 270 * Drupal will ensure that messages set by drupal_set_message() and other 271 * session data are written to the database before the user is redirected. 272 * 273 * This function ends the request; use it rather than a print theme('page') 274 * statement in your menu callback. 275 * 276 * @param $path 277 * A Drupal path or a full URL. 278 * @param $query 279 * The query string component, if any. 280 * @param $fragment 281 * The destination fragment identifier (named anchor). 282 * @param $http_response_code 283 * Valid values for an actual "goto" as per RFC 2616 section 10.3 are: 284 * - 301 Moved Permanently (the recommended value for most redirects) 285 * - 302 Found (default in Drupal and PHP, sometimes used for spamming search 286 * engines) 287 * - 303 See Other 288 * - 304 Not Modified 289 * - 305 Use Proxy 290 * - 307 Temporary Redirect (an alternative to "503 Site Down for Maintenance") 291 * Note: Other values are defined by RFC 2616, but are rarely used and poorly 292 * supported. 293 * @see drupal_get_destination() 294 */ 295 function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) { 296 if (isset($_REQUEST['destination'])) { 297 extract(parse_url(urldecode($_REQUEST['destination']))); 298 } 299 else if (isset($_REQUEST['edit']['destination'])) { 300 extract(parse_url(urldecode($_REQUEST['edit']['destination']))); 301 } 302 303 $url = url($path, $query, $fragment, TRUE); 304 // Remove newlines from the URL to avoid header injection attacks. 305 $url = str_replace(array("\n", "\r"), '', $url); 306 307 // Before the redirect, allow modules to react to the end of the page request. 308 module_invoke_all('exit', $url); 309 310 // Even though session_write_close() is registered as a shutdown function, we 311 // need all session data written to the database before redirecting. 312 session_write_close(); 313 314 header('Location: '. $url, TRUE, $http_response_code); 315 316 // The "Location" header sends a REDIRECT status code to the http 317 // daemon. In some cases this can go wrong, so we make sure none 318 // of the code below the drupal_goto() call gets executed when we redirect. 319 exit(); 320 } 321 322 /** 323 * Generates a site off-line message 324 */ 325 function drupal_site_offline() { 326 drupal_set_header('HTTP/1.1 503 Service unavailable'); 327 drupal_set_title(t('Site off-line')); 328 print theme('maintenance_page', filter_xss_admin(variable_get('site_offline_message', 329 t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))); 330 } 331 332 /** 333 * Generates a 404 error if the request can not be handled. 334 */ 335 function drupal_not_found() { 336 drupal_set_header('HTTP/1.1 404 Not Found'); 337 338 watchdog('page not found', check_plain($_GET['q']), WATCHDOG_WARNING); 339 340 // Keep old path for reference 341 if (!isset($_REQUEST['destination'])) { 342 $_REQUEST['destination'] = $_GET['q']; 343 } 344 345 $path = drupal_get_normal_path(variable_get('site_404', '')); 346 if ($path && $path != $_GET['q']) { 347 menu_set_active_item($path); 348 $return = menu_execute_active_handler(); 349 } 350 else { 351 // Redirect to a non-existent menu item to make possible tabs disappear. 352 menu_set_active_item(''); 353 } 354 355 if (empty($return)) { 356 drupal_set_title(t('Page not found')); 357 } 358 // To conserve CPU and bandwidth, omit the blocks 359 print theme('page', $return, FALSE); 360 } 361 362 /** 363 * Generates a 403 error if the request is not allowed. 364 */ 365 function drupal_access_denied() { 366 drupal_set_header('HTTP/1.1 403 Forbidden'); 367 watchdog('access denied', check_plain($_GET['q']), WATCHDOG_WARNING); 368 369 // Keep old path for reference 370 if (!isset($_REQUEST['destination'])) { 371 $_REQUEST['destination'] = $_GET['q']; 372 } 373 374 $path = drupal_get_normal_path(variable_get('site_403', '')); 375 if ($path && $path != $_GET['q']) { 376 menu_set_active_item($path); 377 $return = menu_execute_active_handler(); 378 } 379 else { 380 // Redirect to a non-existent menu item to make possible tabs disappear. 381 menu_set_active_item(''); 382 } 383 384 if (empty($return)) { 385 drupal_set_title(t('Access denied')); 386 $return = t('You are not authorized to access this page.'); 387 } 388 print theme('page', $return); 389 } 390 391 /** 392 * Perform an HTTP request. 393 * 394 * This is a flexible and powerful HTTP client implementation. Correctly handles 395 * GET, POST, PUT or any other HTTP requests. Handles redirects. 396 * 397 * @param $url 398 * A string containing a fully qualified URI. 399 * @param $headers 400 * An array containing an HTTP header => value pair. 401 * @param $method 402 * A string defining the HTTP request to use. 403 * @param $data 404 * A string containing data to include in the request. 405 * @param $retry 406 * An integer representing how many times to retry the request in case of a 407 * redirect. 408 * @return 409 * An object containing the HTTP request headers, response code, headers, 410 * data, and redirect status. 411 */ 412 function drupal_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3) { 413 $result = new stdClass(); 414 415 // Parse the URL, and make sure we can handle the schema. 416 $uri = parse_url($url); 417 418 switch ($uri['scheme']) { 419 case 'http': 420 $port = isset($uri['port']) ? $uri['port'] : 80; 421 $host = $uri['host'] . ($port != 80 ? ':'. $port : ''); 422 $fp = @fsockopen($uri['host'], $port, $errno, $errstr, 15); 423 break; 424 case 'https': 425 // Note: Only works for PHP 4.3 compiled with OpenSSL. 426 $port = isset($uri['port']) ? $uri['port'] : 443; 427 $host = $uri['host'] . ($port != 443 ? ':'. $port : ''); 428 $fp = @fsockopen('ssl://'. $uri['host'], $port, $errno, $errstr, 20); 429 break; 430 default: 431 $result->error = 'invalid schema '. $uri['scheme']; 432 return $result; 433 } 434 435 // Make sure the socket opened properly. 436 if (!$fp) { 437 $result->error = trim($errno .' '. $errstr); 438 $result->code = -$errno; 439 return $result; 440 } 441 442 // Construct the path to act on. 443 $path = isset($uri['path']) ? $uri['path'] : '/'; 444 if (isset($uri['query'])) { 445 $path .= '?'. $uri['query']; 446 } 447 448 // Create HTTP request. 449 $defaults = array( 450 // RFC 2616: "non-standard ports MUST, default ports MAY be included". 451 // We don't add the port to prevent from breaking rewrite rules checking 452 // the host that do not take into account the port number. 453 'Host' => "Host: $host", 454 'User-Agent' => 'User-Agent: Drupal (+http://drupal.org/)', 455 'Content-Length' => 'Content-Length: '. strlen($data) 456 ); 457 458 foreach ($headers as $header => $value) { 459 $defaults[$header] = $header .': '. $value; 460 } 461 462 $request = $method .' '. $path ." HTTP/1.0\r\n"; 463 $request .= implode("\r\n", $defaults); 464 $request .= "\r\n\r\n"; 465 if ($data) { 466 $request .= $data ."\r\n"; 467 } 468 $result->request = $request; 469 470 fwrite($fp, $request); 471 472 // Fetch response. 473 $response = ''; 474 while (!feof($fp) && $chunk = fread($fp, 1024)) { 475 $response .= $chunk; 476 } 477 fclose($fp); 478 479 // Parse response. 480 list($split, $result->data) = explode("\r\n\r\n", $response, 2); 481 $split = preg_split("/\r\n|\n|\r/", $split); 482 483 list($protocol, $code, $text) = explode(' ', trim(array_shift($split)), 3); 484 $result->headers = array(); 485 486 // Parse headers. 487 while ($line = trim(array_shift($split))) { 488 list($header, $value) = explode(':', $line, 2); 489 if (isset($result->headers[$header]) && $header == 'Set-Cookie') { 490 // RFC 2109: the Set-Cookie response header comprises the token Set- 491 // Cookie:, followed by a comma-separated list of one or more cookies. 492 $result->headers[$header] .= ','. trim($value); 493 } 494 else { 495 $result->headers[$header] = trim($value); 496 } 497 } 498 499 $responses = array( 500 100 => 'Continue', 101 => 'Switching Protocols', 501 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 502 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', 503 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 504 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported' 505 ); 506 // RFC 2616 states that all unknown HTTP codes must be treated the same as 507 // the base code in their class. 508 if (!isset($responses[$code])) { 509 $code = floor($code / 100) * 100; 510 } 511 512 switch ($code) { 513 case 200: // OK 514 case 304: // Not modified 515 break; 516 case 301: // Moved permanently 517 case 302: // Moved temporarily 518 case 307: // Moved temporarily 519 $location = $result->headers['Location']; 520 521 if ($retry) { 522 $result = drupal_http_request($result->headers['Location'], $headers, $method, $data, --$retry); 523 $result->redirect_code = $result->code; 524 } 525 $result->redirect_url = $location; 526 527 break; 528 default: 529 $result->error = $text; 530 } 531 532 $result->code = $code; 533 return $result; 534 } 535 /** 536 * @} End of "HTTP handling". 537 */ 538 539 /** 540 * Log errors as defined by administrator 541 * Error levels: 542 * 0 = Log errors to database. 543 * 1 = Log errors to database and to screen. 544 */ 545 function error_handler($errno, $message, $filename, $line) { 546 // If the @ error suppression operator was used, error_reporting is temporarily set to 0 547 if (error_reporting() == 0) { 548 return; 549 } 550 551 if ($errno & (E_ALL ^ E_NOTICE)) { 552 $types = array(1 => 'error', 2 => 'warning', 4 => 'parse error', 8 => 'notice', 16 => 'core error', 32 => 'core warning', 64 => 'compile error', 128 => 'compile warning', 256 => 'user error', 512 => 'user warning', 1024 => 'user notice', 2048 => 'strict warning'); 553 $entry = $types[$errno] .': '. $message .' in '. $filename .' on line '. $line .'.'; 554 555 // Force display of error messages in update.php 556 if (variable_get('error_level', 1) == 1 || strstr($_SERVER['SCRIPT_NAME'], 'update.php')) { 557 drupal_set_message($entry, 'error'); 558 } 559 560 watchdog('php', t('%message in %file on line %line.', array('%error' => $types[$errno], '%message' => $message, '%file' => $filename, '%line' => $line)), WATCHDOG_ERROR); 561 } 562 } 563 564 function _fix_gpc_magic(&$item) { 565 if (is_array($item)) { 566 array_walk($item, '_fix_gpc_magic'); 567 } 568 else { 569 $item = stripslashes($item); 570 } 571 } 572 573 /** 574 * Helper function to strip slashes from $_FILES skipping over the tmp_name keys 575 * since PHP generates single backslashes for file paths on Windows systems. 576 * 577 * tmp_name does not have backslashes added see 578 * http://php.net/manual/en/features.file-upload.php#42280 579 */ 580 function _fix_gpc_magic_files(&$item, $key) { 581 if ($key != 'tmp_name') { 582 if (is_array($item)) { 583 array_walk($item, '_fix_gpc_magic_files'); 584 } 585 else { 586 $item = stripslashes($item); 587 } 588 } 589 } 590 591 /** 592 * Correct double-escaping problems caused by "magic quotes" in some PHP 593 * installations. 594 */ 595 function fix_gpc_magic() { 596 static $fixed = FALSE; 597 if (!$fixed && ini_get('magic_quotes_gpc')) { 598 array_walk($_GET, '_fix_gpc_magic'); 599 array_walk($_POST, '_fix_gpc_magic'); 600 array_walk($_COOKIE, '_fix_gpc_magic'); 601 array_walk($_REQUEST, '_fix_gpc_magic'); 602 array_walk($_FILES, '_fix_gpc_magic_files'); 603 $fixed = TRUE; 604 } 605 } 606 607 /** 608 * Initialize the localization system. 609 */ 610 function locale_initialize() { 611 global $user; 612 613 if (function_exists('i18n_get_lang')) { 614 return i18n_get_lang(); 615 } 616 617 if (function_exists('locale')) { 618 $languages = locale_supported_languages(); 619 $languages = $languages['name']; 620 } 621 else { 622 // Ensure the locale/language is correctly returned, even without locale.module. 623 // Useful for e.g. XML/HTML 'lang' attributes. 624 $languages = array('en' => 'English'); 625 } 626 if ($user->uid && isset($languages[$user->language])) { 627 return $user->language; 628 } 629 else { 630 return key($languages); 631 } 632 } 633 634 /** 635 * Translate strings to the current locale. 636 * 637 * All human-readable text that will be displayed somewhere within a page should be 638 * run through the t() function. 639 * 640 * Examples: 641 * @code 642 * if (!$info || !$info['extension']) { 643 * form_set_error('picture_upload', t('The uploaded file was not an image.')); 644 * } 645 * 646 * $form['submit'] = array( 647 * '#type' => 'submit', 648 * '#value' => t('Log in'), 649 * ); 650 * @endcode 651 * 652 * Any text within t() can be extracted by translators and changed into 653 * the equivalent text in their native language. 654 * 655 * Special variables called "placeholders" are used to signal dynamic 656 * information in a string which should not be translated. Placeholders 657 * can also be used for text that may change from time to time 658 * (such as link paths) to be changed without requiring updates to translations. 659 * 660 * For example: 661 * @code 662 * $output = t('There are currently %members and %visitors online.', array( 663 * '%members' => format_plural($total_users, '1 user', '@count users'), 664 * '%visitors' => format_plural($guests->count, '1 guest', '@count guests'))); 665 * @endcode 666 * 667 * There are three styles of placeholders: 668 * - !variable, which indicates that the text should be inserted as-is. This is 669 * useful for inserting variables into things like e-mail. 670 * @code 671 * $message[] = t("If you don't want to receive such e-mails, you can change your settings at !url.", array('!url' => url("user/$account->uid", NULL, NULL, TRUE))); 672 * @endcode 673 * 674 * - @variable, which indicates that the text should be run through check_plain, 675 * to strip out HTML characters. Use this for any output that's displayed within 676 * a Drupal page. 677 * @code 678 * drupal_set_title($title = t("@name's blog", array('@name' => $account->name))); 679 * @endcode 680 * 681 * - %variable, which indicates that the string should be highlighted with 682 * theme_placeholder() which shows up by default as <em>emphasized</em>. 683 * @code 684 * watchdog('mail', t('%name-from sent %name-to an e-mail.', array('%name-from' => $user->name, '%name-to' => $account->name))); 685 * @endcode 686 * 687 * When using t(), try to put entire sentences and strings in one t() call. 688 * This makes it easier for translators, as it provides context as to what 689 * each word refers to. HTML markup within translation strings is allowed, 690 * but should be avoided if possible. The exception is embedded links; link 691 * titles add additional context for translators so should be kept in the main 692 * string. 693 * 694 * Here is an example of an incorrect use if t(): 695 * @code 696 * $output .= t('<p>Go to the @contact-page.</p>', array('@contact-page' => l(t('contact page'), 'contact'))); 697 * @endcode 698 * 699 * Here is an example of t() used correctly: 700 * @code 701 * $output .= '<p>'. t('Go to the <a href="@contact-page">contact page</a>.', array('@contact-page' => url('contact'))) .'</p>'; 702 * @endcode 703 * 704 * Also avoid escaping quotation marks wherever possible. 705 * 706 * Incorrect: 707 * @code 708 * $output .= t('Don\'t click me.'); 709 * @endcode 710 * 711 * Correct: 712 * @code 713 * $output .= t("Don't click me."); 714 * @endcode 715 * 716 * @param $string 717 * A string containing the English string to translate. 718 * @param $args 719 * An associative array of replacements to make after translation. Incidences 720 * of any key in this array are replaced with the corresponding value. 721 * Based on the first character of the key, the value is escaped and/or themed: 722 * - !variable: inserted as is 723 * - @variable: escape plain text to HTML (check_plain) 724 * - %variable: escape text and theme as a placeholder for user-submitted 725 * content (check_plain + theme_placeholder) 726 * @return 727 * The translated string. 728 */ 729 function t($string, $args = 0) { 730 global $locale; 731 if (function_exists('locale') && $locale != 'en') { 732 $string = locale($string); 733 } 734 if (!$args) { 735 return $string; 736 } 737 else { 738 // Transform arguments before inserting them 739 foreach ($args as $key => $value) { 740 switch ($key[0]) { 741 // Escaped only 742 case '@': 743 $args[$key] = check_plain($value); 744 break; 745 // Escaped and placeholder 746 case '%': 747 default: 748 $args[$key] = theme('placeholder', $value); 749 break; 750 // Pass-through 751 case '!': 752 } 753 } 754 return strtr($string, $args); 755 } 756 } 757 758 /** 759 * @defgroup validation Input validation 760 * @{ 761 * Functions to validate user input. 762 */ 763 764 /** 765 * Verify the syntax of the given e-mail address. 766 * 767 * Empty e-mail addresses are allowed. See RFC 2822 for details. 768 * 769 * @param $mail 770 * A string containing an e-mail address. 771 * @return 772 * TRUE if the address is in a valid format. 773 */ 774 function valid_email_address($mail) { 775 $user = '[a-zA-Z0-9_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\']+'; 776 $domain = '(?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.?)+'; 777 $ipv4 = '[0-9]{1,3}(\.[0-9]{1,3}){3}'; 778 $ipv6 = '[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7}'; 779 780 return preg_match("/^$user@($domain|(\[($ipv4|$ipv6)\]))$/", $mail); 781 } 782 783 /** 784 * Verify the syntax of the given URL. 785 * 786 * This function should only be used on actual URLs. It should not be used for 787 * Drupal menu paths, which can contain arbitrary characters. 788 * 789 * @param $url 790 * The URL to verify. 791 * @param $absolute 792 * Whether the URL is absolute (beginning with a scheme such as "http:"). 793 * @return 794 * TRUE if the URL is in a valid format. 795 */ 796 function valid_url($url, $absolute = FALSE) { 797 $allowed_characters = '[a-z0-9\/:_\-_\.\?\$,;~=#&%\+]'; 798 if ($absolute) { 799 return preg_match("/^(http|https|ftp):\/\/". $allowed_characters ."+$/i", $url); 800 } 801 else { 802 return preg_match("/^". $allowed_characters ."+$/i", $url); 803 } 804 } 805 806 /** 807 * Register an event for the current visitor (hostname/IP) to the flood control mechanism. 808 * 809 * @param $name 810 * The name of the event. 811 */ 812 function flood_register_event($name) { 813 db_query("INSERT INTO {flood} (event, hostname, timestamp) VALUES ('%s', '%s', %d)", $name, $_SERVER['REMOTE_ADDR'], time()); 814 } 815 816 /** 817 * Check if the current visitor (hostname/IP) is allowed to proceed with the specified event. 818 * The user is allowed to proceed if he did not trigger the specified event more than 819 * $threshold times per hour. 820 * 821 * @param $name 822 * The name of the event. 823 * @param $number 824 * The maximum number of the specified event per hour (per visitor). 825 * @return 826 * True if the user did not exceed the hourly threshold. False otherwise. 827 */ 828 function flood_is_allowed($name, $threshold) { 829 $number = db_num_rows(db_query("SELECT event FROM {flood} WHERE event = '%s' AND hostname = '%s' AND timestamp > %d", $name, $_SERVER['REMOTE_ADDR'], time() - 3600)); 830 return ($number < $threshold ? TRUE : FALSE); 831 } 832 833 function check_file($filename) { 834 return is_uploaded_file($filename); 835 } 836 837 /** 838 * Prepare a URL for use in an HTML attribute. Strips harmful protocols. 839 * 840 */ 841 function check_url($uri) { 842 return filter_xss_bad_protocol($uri, FALSE); 843 } 844 845 /** 846 * @defgroup format Formatting 847 * @{ 848 * Functions to format numbers, strings, dates, etc. 849 */ 850 851 /** 852 * Formats an RSS channel. 853 * 854 * Arbitrary elements may be added using the $args associative array. 855 */ 856 function format_rss_channel($title, $link, $description, $items, $language = 'en', $args = array()) { 857 // arbitrary elements may be added using the $args associative array 858 859 $output = "<channel>\n"; 860 $output .= ' <title>'. check_plain($title) ."</title>\n"; 861 $output .= ' <link>'. check_url($link) ."</link>\n"; 862 863 // The RSS 2.0 "spec" doesn't indicate HTML can be used in the description. 864 // We strip all HTML tags, but need to prevent double encoding from properly 865 // escaped source data (such as & becoming &amp;). 866 $output .= ' <description>'. check_plain(decode_entities(strip_tags($description))) ."</description>\n"; 867 $output .= ' <language>'. check_plain($language) ."</language>\n"; 868 $output .= format_xml_elements($args); 869 $output .= $items; 870 $output .= "</channel>\n"; 871 872 return $output; 873 } 874 875 /** 876 * Format a single RSS item. 877 * 878 * Arbitrary elements may be added using the $args associative array. 879 */ 880 function format_rss_item($title, $link, $description, $args = array()) { 881 $output = "<item>\n"; 882 $output .= ' <title>'. check_plain($title) ."</title>\n"; 883 $output .= ' <link>'. check_url($link) ."</link>\n"; 884 $output .= ' <description>'. check_plain($description) ."</description>\n"; 885 $output .= format_xml_elements($args); 886 $output .= "</item>\n"; 887 888 return $output; 889 } 890 891 /** 892 * Format XML elements. 893 * 894 * @param $array 895 * An array where each item represent an element and is either a: 896 * - (key => value) pair (<key>value</key>) 897 * - Associative array with fields: 898 * - 'key': element name 899 * - 'value': element contents 900 * - 'attributes': associative array of element attributes 901 * 902 * In both cases, 'value' can be a simple string, or it can be another array 903 * with the same format as $array itself for nesting. 904 */ 905 function format_xml_elements($array) { 906 foreach ($array as $key => $value) { 907 if (is_numeric($key)) { 908 if ($value['key']) { 909 $output .= ' <'. $value['key']; 910 if (isset($value['attributes']) && is_array($value['attributes'])) { 911 $output .= drupal_attributes($value['attributes']); 912 } 913 914 if ($value['value'] != '') { 915 $output .= '>'. (is_array($value['value']) ? format_xml_elements($value['value']) : check_plain($value['value'])) .'</'. $value['key'] .">\n"; 916 } 917 else { 918 $output .= " />\n"; 919 } 920 } 921 } 922 else { 923 $output .= ' <'. $key .'>'. (is_array($value) ? format_xml_elements($value) : check_plain($value)) ."</$key>\n"; 924 } 925 } 926 return $output; 927 } 928 929 /** 930 * Format a string containing a count of items. 931 * 932 * This function ensures that the string is pluralized correctly. Since t() is 933 * called by this function, make sure not to pass already-localized strings to it. 934 * 935 * @param $count 936 * The item count to display. 937 * @param $singular 938 * The string for the singular case. Please make sure it is clear this is 939 * singular, to ease translation (e.g. use "1 new comment" instead of "1 new"). 940 * @param $plural 941 * The string for the plural case. Please make sure it is clear this is plural, 942 * to ease translation. Use @count in place of the item count, as in "@count 943 * new comments". 944 * @return 945 * A translated string. 946 */ 947 function format_plural($count, $singular, $plural) { 948 if ($count == 1) return t($singular, array("@count" => $count)); 949 950 // get the plural index through the gettext formula 951 $index = (function_exists('locale_get_plural')) ? locale_get_plural($count) : -1; 952 if ($index < 0) { // backward compatibility 953 return t($plural, array("@count" => $count)); 954 } 955 else { 956 switch ($index) { 957 case "0": 958 return t($singular, array("@count" => $count)); 959 case "1": 960 return t($plural, array("@count" => $count)); 961 default: 962 return t(strtr($plural, array("@count" => '@count['. $index .']')), array('@count['. $index .']' => $count)); 963 } 964 } 965 } 966 967 /** 968 * Parse a given byte count. 969 * 970 * @param $size 971 * The size expressed as a number of bytes with optional SI size and unit 972 * suffix (e.g. 2, 3K, 5MB, 10G). 973 * @return 974 * An integer representation of the size. 975 */ 976 function parse_size($size) { 977 $suffixes = array( 978 '' => 1, 979 'k' => 1024, 980 'm' => 1048576, // 1024 * 1024 981 'g' => 1073741824, // 1024 * 1024 * 1024 982 ); 983 if (preg_match('/([0-9]+)\s*(k|m|g)?(b?(ytes?)?)/i', $size, $match)) { 984 return $match[1] * $suffixes[drupal_strtolower($match[2])]; 985 } 986 } 987 988 /** 989 * Generate a string representation for the given byte count. 990 * 991 * @param $size 992 * The size in bytes. 993 * @return 994 * A translated string representation of the size. 995 */ 996 function format_size($size) { 997 if ($size < 1024) { 998 return format_plural($size, '1 byte', '@count bytes'); 999 } 1000 else { 1001 $size = round($size / 1024, 2); 1002 $suffix = t('KB'); 1003 if ($size >= 1024) { 1004 $size = round($size / 1024, 2); 1005 $suffix = t('MB'); 1006 } 1007 return t('@size @suffix', array('@size' => $size, '@suffix' => $suffix)); 1008 } 1009 } 1010 1011 /** 1012 * Format a time interval with the requested granularity. 1013 * 1014 * @param $timestamp 1015 * The length of the interval in seconds. 1016 * @param $granularity 1017 * How many different units to display in the string. 1018 * @return 1019 * A translated string representation of the interval. 1020 */ 1021 function format_interval($timestamp, $granularity = 2) { 1022 $units = array('1 year|@count years' => 31536000, '1 week|@count weeks' => 604800, '1 day|@count days' => 86400, '1 hour|@count hours' => 3600, '1 min|@count min' => 60, '1 sec|@count sec' => 1); 1023 $output = ''; 1024 foreach ($units as $key => $value) { 1025 $key = explode('|', $key); 1026 if ($timestamp >= $value) { 1027 $output .= ($output ? ' ' : '') . format_plural(floor($timestamp / $value), $key[0], $key[1]); 1028 $timestamp %= $value; 1029 $granularity--; 1030 } 1031 1032 if ($granularity == 0) { 1033 break; 1034 } 1035 } 1036 return $output ? $output : t('0 sec'); 1037 } 1038 1039 /** 1040 * Format a date with the given configured format or a custom format string. 1041 * 1042 * Drupal allows administrators to select formatting strings for 'small', 1043 * 'medium' and 'large' date formats. This function can handle these formats, 1044 * as well as any custom format. 1045 * 1046 * @param $timestamp 1047 * The exact date to format, as a UNIX timestamp. 1048 * @param $type 1049 * The format to use. Can be "small", "medium" or "large" for the preconfigured 1050 * date formats. If "custom" is specified, then $format is required as well. 1051 * @param $format 1052 * A PHP date format string as required by date(). A backslash should be used 1053 * before a character to avoid interpreting the character as part of a date 1054 * format. 1055 * @param $timezone 1056 * Time zone offset in seconds; if omitted, the user's time zone is used. 1057 * @return 1058 * A translated date string in the requested format. 1059 */ 1060 function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL) { 1061 if (!isset($timezone)) { 1062 global $user; 1063 if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) { 1064 $timezone = $user->timezone; 1065 } 1066 else { 1067 $timezone = variable_get('date_default_timezone', 0); 1068 } 1069 } 1070 1071 $timestamp += $timezone; 1072 1073 switch ($type) { 1074 case 'small': 1075 $format = variable_get('date_format_short', 'm/d/Y - H:i'); 1076 break; 1077 case 'large': 1078 $format = variable_get('date_format_long', 'l, F j, Y - H:i'); 1079 break; 1080 case 'custom': 1081 // No change to format 1082 break; 1083 case 'medium': 1084 default: 1085 $format = variable_get('date_format_medium', 'D, m/d/Y - H:i'); 1086 } 1087 1088 $max = strlen($format); 1089 $date = ''; 1090 for ($i = 0; $i < $max; $i++) { 1091 $c = $format[$i]; 1092 if (strpos('AaDFlM', $c) !== FALSE) { 1093 $date .= t(gmdate($c, $timestamp)); 1094 } 1095 else if (strpos('BdgGhHiIjLmnsStTUwWYyz', $c) !== FALSE) { 1096 $date .= gmdate($c, $timestamp); 1097 } 1098 else if ($c == 'r') { 1099 $date .= format_date($timestamp - $timezone, 'custom', 'D, d M Y H:i:s O', $timezone); 1100 } 1101 else if ($c == 'O') { 1102 $date .= sprintf('%s%02d%02d', ($timezone < 0 ? '-' : '+'), abs($timezone / 3600), abs($timezone % 3600) / 60); 1103 } 1104 else if ($c == 'Z') { 1105 $date .= $timezone; 1106 } 1107 else if ($c == '\\') { 1108 $date .= $format[++$i]; 1109 } 1110 else { 1111 $date .= $c; 1112 } 1113 } 1114 1115 return $date; 1116 } 1117 1118 /** 1119 * @} End of "defgroup format". 1120 */ 1121 1122 /** 1123 * Generate a URL from a Drupal menu path. Will also pass-through existing URLs. 1124 * 1125 * @param $path 1126 * The Drupal path being linked to, such as "admin/content/node", or an existing URL 1127 * like "http://drupal.org/". 1128 * @param $query 1129 * A query string to append to the link or URL. 1130 * @param $fragment 1131 * A fragment identifier (named anchor) to append to the link. If an existing 1132 * URL with a fragment identifier is used, it will be replaced. Note, do not 1133 * include the '#'. 1134 * @param $absolute 1135 * Whether to force the output to be an absolute link (beginning with http:). 1136 * Useful for links that will be displayed outside the site, such as in an 1137 * RSS feed. 1138 * @return 1139 * a string containing a URL to the given path. 1140 * 1141 * When creating links in modules, consider whether l() could be a better 1142 * alternative than url(). 1143 */ 1144 function url($path = NULL, $query = NULL, $fragment = NULL, $absolute = FALSE) { 1145 if (isset($fragment)) { 1146 $fragment = '#'. $fragment; 1147 } 1148 1149 // Return an external link if $path contains an allowed absolute URL. 1150 // Only call the slow filter_xss_bad_protocol if $path contains a ':' before any / ? or #. 1151 $colonpos = strpos($path, ':'); 1152 if ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path)) { 1153 // Split off the fragment 1154 if (strpos($path, '#') !== FALSE) { 1155 list($path, $old_fragment) = explode('#', $path, 2); 1156 if (isset($old_fragment) && !isset($fragment)) { 1157 $fragment = '#'. $old_fragment; 1158 } 1159 } 1160 // Append the query 1161 if (isset($query)) { 1162 $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . $query; 1163 } 1164 // Reassemble 1165 return $path . $fragment; 1166 } 1167 1168 global $base_url; 1169 static $script; 1170 static $clean_url; 1171 1172 if (!isset($script)) { 1173 // On some web servers, such as IIS, we can't omit "index.php". So, we 1174 // generate "index.php?q=foo" instead of "?q=foo" on anything that is not 1175 // Apache. 1176 $script = (strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') === FALSE) ? 'index.php' : ''; 1177 } 1178 1179 // Cache the clean_url variable to improve performance. 1180 if (!isset($clean_url)) { 1181 $clean_url = (bool)variable_get('clean_url', '0'); 1182 } 1183 1184 $base = ($absolute ? $base_url . '/' : base_path()); 1185 1186 // The special path '<front>' links to the default front page. 1187 if (!empty($path) && $path != '<front>') { 1188 $path = drupal_get_path_alias($path); 1189 $path = drupal_urlencode($path); 1190 if (!$clean_url) { 1191 if (isset($query)) { 1192 return $base . $script .'?q='. $path .'&'. $query . $fragment; 1193 } 1194 else { 1195 return $base . $script .'?q='. $path . $fragment; 1196 } 1197 } 1198 else { 1199 if (isset($query)) { 1200 return $base . $path .'?'. $query . $fragment; 1201 } 1202 else { 1203 return $base . $path . $fragment; 1204 } 1205 } 1206 } 1207 else { 1208 if (isset($query)) { 1209 return $base . $script .'?'. $query . $fragment; 1210 } 1211 else { 1212 return $base . $fragment; 1213 } 1214 } 1215 } 1216 1217 /** 1218 * Format an attribute string to insert in a tag. 1219 * 1220 * @param $attributes 1221 * An associative array of HTML attributes. 1222 * @return 1223 * An HTML string ready for insertion in a tag. 1224 */ 1225 function drupal_attributes($attributes = array()) { 1226 if (is_array($attributes)) { 1227 $t = ''; 1228 foreach ($attributes as $key => $value) { 1229 $t .= " $key=".'"'. check_plain($value) .'"'; 1230 } 1231 return $t; 1232 } 1233 } 1234 1235 /** 1236 * Format an internal Drupal link. 1237 * 1238 * This function correctly handles aliased paths, and allows themes to highlight 1239 * links to the current page correctly, so all internal links output by modules 1240 * should be generated by this function if possible. 1241 * 1242 * @param $text 1243 * The text to be enclosed with the anchor tag. 1244 * @param $path 1245 * The Drupal path being linked to, such as "admin/content/node". Can be an external 1246 * or internal URL. 1247 * - If you provide the full URL, it will be considered an 1248 * external URL. 1249 * - If you provide only the path (e.g. "admin/content/node"), it is considered an 1250 * internal link. In this case, it must be a system URL as the url() function 1251 * will generate the alias. 1252 * @param $attributes 1253 * An associative array of HTML attributes to apply to the anchor tag. 1254 * @param $query 1255 * A query string to append to the link. 1256 * @param $fragment 1257 * A fragment identifier (named anchor) to append to the link. 1258 * @param $absolute 1259 * Whether to force the output to be an absolute link (beginning with http:). 1260 * Useful for links that will be displayed outside the site, such as in an RSS 1261 * feed. 1262 * @param $html 1263 * Whether the title is HTML, or just plain-text. For example for making an 1264 * image a link, this must be set to TRUE, or else you will see the encoded 1265 * HTML. 1266 * @return 1267 * an HTML string containing a link to the given path. 1268 */ 1269 function l($text, $path, $attributes = array(), $query = NULL, $fragment = NULL, $absolute = FALSE, $html = FALSE) { 1270 if ($path == $_GET['q']) { 1271 if (isset($attributes['class'])) { 1272 $attributes['class'] .= ' active'; 1273 } 1274 else { 1275 $attributes['class'] = 'active'; 1276 } 1277 } 1278 return '<a href="'. check_url(url($path, $query, $fragment, $absolute)) .'"'. drupal_attributes($attributes) .'>'. ($html ? $text : check_plain($text)) .'</a>'; 1279 } 1280 1281 /** 1282 * Perform end-of-request tasks. 1283 * 1284 * This function sets the page cache if appropriate, and allows modules to 1285 * react to the closing of the page by calling hook_exit(). 1286 */ 1287 function drupal_page_footer() { 1288 if (variable_get('cache', 0)) { 1289 page_set_cache(); 1290 } 1291 1292 module_invoke_all('exit'); 1293 } 1294 1295 /** 1296 * Form an associative array from a linear array. 1297 * 1298 * This function walks through the provided array and constructs an associative 1299 * array out of it. The keys of the resulting array will be the values of the 1300 * input array. The values will be the same as the keys unless a function is 1301 * specified, in which case the output of the function is used for the values 1302 * instead. 1303 * 1304 * @param $array 1305 * A linear array. 1306 * @param $function 1307 * The name of a function to apply to all values before output. 1308 * @result 1309 * An associative array. 1310 */ 1311 function drupal_map_assoc($array, $function = NULL) { 1312 if (!isset($function)) { 1313 $result = array(); 1314 foreach ($array as $value) { 1315 $result[$value] = $value; 1316 } 1317 return $result; 1318 } 1319 elseif (function_exists($function)) { 1320 $result = array(); 1321 foreach ($array as $value) { 1322 $result[$value] = $function($value); 1323 } 1324 return $result; 1325 } 1326 } 1327 1328 /** 1329 * Evaluate a string of PHP code. 1330 * 1331 * This is a wrapper around PHP's eval(). It uses output buffering to capture both 1332 * returned and printed text. Unlike eval(), we require code to be surrounded by 1333 * <?php ?> tags; in other words, we evaluate the code as if it were a stand-alone 1334 * PHP file. 1335 * 1336 * Using this wrapper also ensures that the PHP code which is evaluated can not 1337 * overwrite any variables in the calling code, unlike a regular eval() call. 1338 * 1339 * @param $code 1340 * The code to evaluate. 1341 * @return 1342 * A string containing the printed output of the code, followed by the returned 1343 * output of the code. 1344 */ 1345 function drupal_eval($code) { 1346 ob_start(); 1347 print eval('?>'. $code); 1348 $output = ob_get_contents(); 1349 ob_end_clean(); 1350 return $output; 1351 } 1352 1353 /** 1354 * Returns the path to a system item (module, theme, etc.). 1355 * 1356 * @param $type 1357 * The type of the item (i.e. theme, theme_engine, module). 1358 * @param $name 1359 * The name of the item for which the path is requested. 1360 * 1361 * @return 1362 * The path to the requested item. 1363 */ 1364 function drupal_get_path($type, $name) { 1365 return dirname(drupal_get_filename($type, $name)); 1366 } 1367 1368 /** 1369 * Returns the base URL path of the Drupal installation. 1370 * At the very least, this will always default to /. 1371 */ 1372 function base_path() { 1373 return $GLOBALS['base_path']; 1374 } 1375 1376 /** 1377 * Provide a substitute clone() function for PHP4. 1378 */ 1379 function drupal_clone($object) { 1380 return version_compare(phpversion(), '5.0') < 0 ? $object : clone($object); 1381 } 1382 1383 /** 1384 * Add a <link> tag to the page's HEAD. 1385 */ 1386 function drupal_add_link($attributes) { 1387 drupal_set_html_head('<link'. drupal_attributes($attributes) ." />\n"); 1388 } 1389 1390 /** 1391 * Adds a CSS file to the stylesheet queue. 1392 * 1393 * @param $path 1394 * (optional) The path to the CSS file relative to the base_path(), e.g., 1395 * /modules/devel/devel.css. 1396 * @param $type 1397 * (optional) The type of stylesheet that is being added. Types are: module 1398 * or theme. 1399 * @param $media 1400 * (optional) The media type for the stylesheet, e.g., all, print, screen. 1401 * @param $preprocess 1402 * (optional) Should this CSS file be aggregated and compressed if this 1403 * feature has been turned on under the performance section? 1404 * 1405 * What does this actually mean? 1406 * CSS preprocessing is the process of aggregating a bunch of separate CSS 1407 * files into one file that is then compressed by removing all extraneous 1408 * white space. 1409 * 1410 * The reason for merging the CSS files is outlined quite thoroughly here: 1411 * http://www.die.net/musings/page_load_time/ 1412 * "Load fewer external objects. Due to request overhead, one bigger file 1413 * just loads faster than two smaller ones half its size." 1414 * 1415 * However, you should *not* preprocess every file as this can lead to 1416 * redundant caches. You should set $preprocess = FALSE when: 1417 * 1418 * - Your styles are only used rarely on the site. This could be a special 1419 * admin page, the homepage, or a handful of pages that does not represent 1420 * the majority of the pages on your site. 1421 * 1422 * Typical candidates for caching are for example styles for nodes across 1423 * the site, or used in the theme. 1424 * @return 1425 * An array of CSS files. 1426 */ 1427 function drupal_add_css($path = NULL, $type = 'module', $media = 'all', $preprocess = TRUE) { 1428 static $css = array(); 1429 1430 // Create an array of CSS files for each media type first, since each type needs to be served 1431 // to the browser differently. 1432 if (isset($path)) { 1433 // This check is necessary to ensure proper cascading of styles and is faster than an asort(). 1434 if (!isset($css[$media])) { 1435 $css[$media] = array('module' => array(), 'theme' => array()); 1436 } 1437 $css[$media][$type][$path] = $preprocess; 1438 } 1439 1440 return $css; 1441 } 1442 1443 /** 1444 * Returns a themed representation of all stylesheets that should be attached to the page. 1445 * It loads the CSS in order, with 'core' CSS first, then 'module' CSS, then 'theme' CSS files. 1446 * This ensures proper cascading of styles for easy overriding in modules and themes. 1447 * 1448 * @param $css 1449 * (optional) An array of CSS files. If no array is provided, the default stylesheets array is used instead. 1450 * @return 1451 * A string of XHTML CSS tags. 1452 */ 1453 function drupal_get_css($css = NULL) { 1454 $output = ''; 1455 if (!isset($css)) { 1456 $css = drupal_add_css(); 1457 } 1458 1459 $preprocess_css = variable_get('preprocess_css', FALSE); 1460 $directory = file_directory_path(); 1461 $is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC); 1462 1463 foreach ($css as $media => $types) { 1464 // If CSS preprocessing is off, we still need to output the styles. 1465 // Additionally, go through any remaining styles if CSS preprocessing is on and output the non-cached ones. 1466 foreach ($types as $type => $files) { 1467 foreach ($types[$type] as $file => $preprocess) { 1468 if (!$preprocess || !($is_writable && $preprocess_css)) { 1469 // If a CSS file is not to be preprocessed and it's a module CSS file, it needs to *always* appear at the *top*, 1470 // regardless of whether preprocessing is on or off. 1471 if (!$preprocess && $type == 'module') { 1472 $no_module_preprocess .= '<style type="text/css" media="'. $media .'">@import "'. base_path() . $file .'";</style>' ."\n"; 1473 } 1474 // If a CSS file is not to be preprocessed and it's a theme CSS file, it needs to *always* appear at the *bottom*, 1475 // regardless of whether preprocessing is on or off. 1476 else if (!$preprocess && $type == 'theme') { 1477 $no_theme_preprocess .= '<style type="text/css" media="'. $media .'">@import "'. base_path() . $file .'";</style>' ."\n"; 1478 } 1479 else { 1480 $output .= '<style type="text/css" media="'. $media .'">@import "'. base_path() . $file .'";</style>' ."\n"; 1481 } 1482 } 1483 } 1484 } 1485 1486 if ($is_writable && $preprocess_css) { 1487 $filename = md5(serialize($types)) .'.css'; 1488 $preprocess_file = drupal_build_css_cache($types, $filename); 1489 $output .= '<style type="text/css" media="'. $media .'">@import "'. base_path() . $preprocess_file .'";</style>'. "\n"; 1490 } 1491 } 1492 1493 return $no_module_preprocess . $output . $no_theme_preprocess; 1494 } 1495 1496 /** 1497 * Aggregate and optimize CSS files, putting them in the files directory. 1498 * 1499 * @param $types 1500 * An array of types of CSS files (e.g., screen, print) to aggregate and compress into one file. 1501 * @param $filename 1502 * The name of the aggregate CSS file. 1503 * @return 1504 * The name of the CSS file. 1505 */ 1506 function drupal_build_css_cache($types, $filename) { 1507 $data = ''; 1508 1509 // Create the css/ within the files folder. 1510 $csspath = file_create_path('css'); 1511 file_check_directory($csspath, FILE_CREATE_DIRECTORY); 1512 1513 if (!file_exists($csspath .'/'. $filename)) { 1514 // Build aggregate CSS file. 1515 foreach ($types as $type) { 1516 foreach ($type as $file => $cache) { 1517 if ($cache) { 1518 $contents = file_get_contents($file); 1519 // Remove multiple charset declarations for standards compliance (and fixing Safari problems) 1520 $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents); 1521 // Return the path to where this CSS file originated from, stripping off the name of the file at the end of the path. 1522 $path = base_path() . substr($file, 0, strrpos($file, '/')) .'/'; 1523 // Wraps all @import arguments in url(). 1524 $contents = preg_replace('/@import\s+(?!url)[\'"]?(\S*)\b[\'"]?/i', '@import url("\1")', $contents); 1525 // Fix all paths within this CSS file, ignoring absolute paths. 1526 $data .= preg_replace('/url\(([\'"]?)(?![a-z]+:)/i', 'url(\1'. $path . '\2', $contents); 1527 } 1528 } 1529 } 1530 1531 // @import rules must proceed any other style, so we move those to the top. 1532 $regexp = '/@import[^;]+;/i'; 1533 preg_match_all($regexp, $data, $matches); 1534 $data = preg_replace($regexp, '', $data); 1535 $data = implode('', $matches[0]) . $data; 1536 1537 // Perform some safe CSS optimizations. 1538 $data = preg_replace('< 1539 \s*([@{}:;,]|\)\s|\s\()\s* | # Remove whitespace around separators, but keep space around parentheses. 1540 /\*([^*\\\\]|\*(?!/))+\*/ | # Remove comments that are not CSS hacks. 1541 [\n\r] # Remove line breaks. 1542 >x', '\1', $data); 1543 1544 // Create the CSS file. 1545 file_save_data($data, $csspath .'/'. $filename, FILE_EXISTS_REPLACE); 1546 } 1547 return $csspath .'/'. $filename; 1548 } 1549 1550 /** 1551 * Delete all cached CSS files. 1552 */ 1553 function drupal_clear_css_cache() { 1554 file_scan_directory(file_create_path('css'), '.*', array('.', '..', 'CVS'), 'file_delete', TRUE); 1555 } 1556 1557 /** 1558 * Add a JavaScript file, setting or inline code to the page. 1559 * 1560 * The behavior of this function depends on the parameters it is called with. 1561 * Generally, it handles the addition of JavaScript to the page, either as 1562 * reference to an existing file or as inline code. The following actions can be 1563 * performed using this function: 1564 * 1565 * - Add a file ('core', 'module' and 'theme'): 1566 * Adds a reference to a JavaScript file to the page. JavaScript files 1567 * are placed in a certain order, from 'core' first, to 'module' and finally 1568 * 'theme' so that files, that are added later, can override previously added 1569 * files with ease. 1570 * 1571 * - Add inline JavaScript code ('inline'): 1572 * Executes a piece of JavaScript code on the current page by placing the code 1573 * directly in the page. This can, for example, be useful to tell the user that 1574 * a new message arrived, by opening a pop up, alert box etc. 1575 * 1576 * - Add settings ('setting'): 1577 * Adds a setting to Drupal's global storage of JavaScript settings. Per-page 1578 * settings are required by some modules to function properly. The settings 1579 * will be accessible at Drupal.settings. 1580 * 1581 * @param $data 1582 * (optional) If given, the value depends on the $type parameter: 1583 * - 'core', 'module' or 'theme': Path to the file relative to base_path(). 1584 * - 'inline': The JavaScript code that should be placed in the given scope. 1585 * - 'setting': An array with configuration options as associative array. The 1586 * array is directly placed in Drupal.settings. You might want to wrap your 1587 * actual configuration settings in another variable to prevent the pollution 1588 * of the Drupal.settings namespace. 1589 * @param $type 1590 * (optional) The type of JavaScript that should be added to the page. Allowed 1591 * values are 'core', 'module', 'theme', 'inline' and 'setting'. You 1592 * can, however, specify any value. It is treated as a reference to a JavaScript 1593 * file. Defaults to 'module'. 1594 * @param $scope 1595 * (optional) The location in which you want to place the script. Possible 1596 * values are 'header' and 'footer' by default. If your theme implements 1597 * different locations, however, you can also use these. 1598 * @param $defer 1599 * (optional) If set to TRUE, the defer attribute is set on the <script> tag. 1600 * Defaults to FALSE. This parameter is not used with $type == 'setting'. 1601 * @param $cache 1602 * (optional) If set to FALSE, the JavaScript file is loaded anew on every page 1603 * call, that means, it is not cached. Defaults to TRUE. Used only when $type 1604 * references a JavaScript file. 1605 * @return 1606 * If the first parameter is NULL, the JavaScript array that has been built so 1607 * far for $scope is returned. 1608 */ 1609 function drupal_add_js($data = NULL, $type = 'module', $scope = 'header', $defer = FALSE, $cache = TRUE) { 1610 if (!is_null($data)) { 1611 _drupal_add_js('misc/jquery.js', 'core', 'header', FALSE, $cache); 1612 _drupal_add_js('misc/drupal.js', 'core', 'header', FALSE, $cache); 1613 } 1614 return _drupal_add_js($data, $type, $scope, $defer, $cache); 1615 } 1616 1617 /** 1618 * Helper function for drupal_add_js(). 1619 */ 1620 function _drupal_add_js($data, $type, $scope, $defer, $cache) { 1621 static $javascript = array(); 1622 1623 if (!isset($javascript[$scope])) { 1624 $javascript[$scope] = array('core' => array(), 'module' => array(), 'theme' => array(), 'setting' => array(), 'inline' => array()); 1625 } 1626 1627 if (!isset($javascript[$scope][$type])) { 1628 $javascript[$scope][$type] = array(); 1629 } 1630 1631 if (!is_null($data)) { 1632 switch ($type) { 1633 case 'setting': 1634 $javascript[$scope][$type][] = $data; 1635 break; 1636 case 'inline': 1637 $javascript[$scope][$type][] = array('code' => $data, 'defer' => $defer); 1638 break; 1639 default: 1640 $javascript[$scope][$type][$data] = array('cache' => $cache, 'defer' => $defer); 1641 } 1642 } 1643 1644 return $javascript[$scope]; 1645 } 1646 1647 /** 1648 * Returns a themed presentation of all JavaScript code for the current page. 1649 * References to JavaScript files are placed in a certain order: first, all 1650 * 'core' files, then all 'module' and finally all 'theme' JavaScript files 1651 * are added to the page. Then, all settings are output, followed by 'inline' 1652 * JavaScript code. 1653 * 1654 * @parameter $scope 1655 * (optional) The scope for which the JavaScript rules should be returned. 1656 * Defaults to 'header'. 1657 * @parameter $javascript 1658 * (optional) An array with all JavaScript code. Defaults to the default 1659 * JavaScript array for the given scope. 1660 * @return 1661 * All JavaScript code segments and includes for the scope as HTML tags. 1662 */ 1663 function drupal_get_js($scope = 'header', $javascript = NULL) { 1664 $output = ''; 1665 if (is_null($javascript)) { 1666 $javascript = drupal_add_js(NULL, NULL, $scope); 1667 } 1668 1669 foreach ($javascript as $type => $data) { 1670 if (!$data) continue; 1671 1672 switch ($type) { 1673 case 'setting': 1674 $output .= '<script type="text/javascript">Drupal.extend({ settings: '. drupal_to_js(call_user_func_array('array_merge_recursive', $data)) ." });</script>\n"; 1675 break; 1676 case 'inline': 1677 foreach ($data as $info) { 1678 $output .= '<script type="text/javascript"'. ($info['defer'] ? ' defer="defer"' : '') .'>'. $info['code'] ."</script>\n"; 1679 } 1680 break; 1681 default: 1682 foreach ($data as $path => $info) { 1683 $output .= '<script type="text/javascript"'. ($info['defer'] ? ' defer="defer"' : '') .' src="'. check_url(base_path() . $path) . ($info['cache'] ? '' : '?'. time()) ."\"></script>\n"; 1684 } 1685 } 1686 } 1687 1688 return $output; 1689 } 1690 1691 /** 1692 * Converts a PHP variable into its Javascript equivalent. 1693 * 1694 * We use HTML-safe strings, i.e. with <, > and & escaped. 1695 */ 1696 function drupal_to_js($var) { 1697 switch (gettype($var)) { 1698 case 'boolean': 1699 return $var ? 'true' : 'false'; // Lowercase necessary! 1700 case 'integer': 1701 case 'double': 1702 return $var; 1703 case 'resource': 1704 case 'string': 1705 return '"'. str_replace(array("\r", "\n", "<", ">", "&"), 1706 array('\r', '\n', '\x3c', '\x3e', '\x26'), 1707 addslashes($var)) .'"'; 1708 case 'array': 1709 // Arrays in JSON can't be associative. If the array is empty or if it 1710 // has sequential whole number keys starting with 0, it's not associative 1711 // so we can go ahead and convert it as an array. 1712 if (empty ($var) || array_keys($var) === range(0, sizeof($var) - 1)) { 1713 $output = array(); 1714 foreach ($var as $v) { 1715 $output[] = drupal_to_js($v); 1716 } 1717 return '[ '. implode(', ', $output) .' ]'; 1718 } 1719 // Otherwise, fall through to convert the array as an object. 1720 case 'object': 1721 $output = array(); 1722 foreach ($var as $k => $v) { 1723 $output[] = drupal_to_js(strval($k)) .': '. drupal_to_js($v); 1724 } 1725 return '{ '. implode(', ', $output) .' }'; 1726 default: 1727 return 'null'; 1728 } 1729 } 1730 1731 /** 1732 * Wrapper around urlencode() which avoids Apache quirks. 1733 * 1734 * Should be used when placing arbitrary data in an URL. Note that Drupal paths 1735 * are urlencoded() when passed through url() and do not require urlencoding() 1736 * of individual components. 1737 * 1738 * Notes: 1739 * - For esthetic reasons, we do not escape slashes. This also avoids a 'feature' 1740 * in Apache where it 404s on any path containing '%2F'. 1741 * - mod_rewrite unescapes %-encoded ampersands, hashes, and slashes when clean 1742 * URLs are used, which are interpreted as delimiters by PHP. These 1743 * characters are double escaped so PHP will still see the encoded version. 1744 * - With clean URLs, Apache changes '//' to '/', so every second slash is 1745 * double escaped. 1746 * 1747 * @param $text 1748 * String to encode 1749 */ 1750 function drupal_urlencode($text) { 1751 if (variable_get('clean_url', '0')) { 1752 return str_replace(array('%2F', '%26', '%23', '//'), 1753 array('/', '%2526', '%2523', '/%252F'), 1754 urlencode($text)); 1755 } 1756 else { 1757 return str_replace('%2F', '/', urlencode($text)); 1758 } 1759 } 1760 1761 /** 1762 * Ensure the private key variable used to generate tokens is set. 1763 * 1764 * @return 1765 * The private key 1766 */ 1767 function drupal_get_private_key() { 1768 if (!($key = variable_get('drupal_private_key', 0))) { 1769 $key = md5(uniqid(mt_rand(), true)) . md5(uniqid(mt_rand(), true)); 1770 variable_set('drupal_private_key', $key); 1771 } 1772 return $key; 1773 } 1774 1775 /** 1776 * Generate a token based on $value, the current user session and private key. 1777 * 1778 * @param $value 1779 * An additional value to base the token on 1780 */ 1781 function drupal_get_token($value = '') { 1782 $private_key = drupal_get_private_key(); 1783 return md5(session_id() . $value . $private_key); 1784 } 1785 1786 /** 1787 * Validate a token based on $value, the current user session and private key. 1788 * 1789 * @param $token 1790 * The token to be validated. 1791 * @param $value 1792 * An additional value to base the token on. 1793 * @param $skip_anonymous 1794 * Set to true to skip token validation for anonymous users. 1795 * @return 1796 * True for a valid token, false for an invalid token. When $skip_anonymous is true, the return value will always be true for anonymous users. 1797 */ 1798 function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) { 1799 global $user; 1800 return (($skip_anonymous && $user->uid == 0) || ($token == md5(session_id() . $value . variable_get('drupal_private_key', '')))); 1801 } 1802 1803 /** 1804 * Performs one or more XML-RPC request(s). 1805 * 1806 * @param $url 1807 * An absolute URL of the XML-RPC endpoint. 1808 * Example: 1809 * http://www.example.com/xmlrpc.php 1810 * @param ... 1811 * For one request: 1812 * The method name followed by a variable number of arguments to the method. 1813 * For multiple requests (system.multicall): 1814 * An array of call arrays. Each call array follows the pattern of the single 1815 * request: method name followed by the arguments to the method. 1816 * @return 1817 * For one request: 1818 * Either the return value of the method on success, or FALSE. 1819 * If FALSE is returned, see xmlrpc_errno() and xmlrpc_error_msg(). 1820 * For multiple requests: 1821 * An array of results. Each result will either be the result 1822 * returned by the method called, or an xmlrpc_error object if the call 1823 * failed. See xmlrpc_error(). 1824 */ 1825 function xmlrpc($url) { 1826 require_once './includes/xmlrpc.inc'; 1827 $args = func_get_args(); 1828 return call_user_func_array('_xmlrpc', $args); 1829 } 1830 1831 function _drupal_bootstrap_full() { 1832 static $called; 1833 global $locale; 1834 1835 if ($called) { 1836 return; 1837 } 1838 $called = 1; 1839 require_once './includes/theme.inc'; 1840 require_once './includes/pager.inc'; 1841 require_once './includes/menu.inc'; 1842 require_once './includes/tablesort.inc'; 1843 require_once './includes/file.inc'; 1844 require_once './includes/unicode.inc'; 1845 require_once './includes/image.inc'; 1846 require_once './includes/form.inc'; 1847 // Set the Drupal custom error handler. 1848 set_error_handler('error_handler'); 1849 // Emit the correct charset HTTP header. 1850 drupal_set_header('Content-Type: text/html; charset=utf-8'); 1851 // Detect string handling method 1852 unicode_check(); 1853 // Undo magic quotes 1854 fix_gpc_magic(); 1855 // Load all enabled modules 1856 module_load_all(); 1857 // Initialize the localization system. Depends on i18n.module being loaded already. 1858 $locale = locale_initialize(); 1859 // Let all modules take action before menu system handles the reqest 1860 module_invoke_all('init'); 1861 1862 } 1863 1864 /** 1865 * Store the current page in the cache. 1866 * 1867 * We try to store a gzipped version of the cache. This requires the 1868 * PHP zlib extension (http://php.net/manual/en/ref.zlib.php). 1869 * Presence of the extension is checked by testing for the function 1870 * gzencode. There are two compression algorithms: gzip and deflate. 1871 * The majority of all modern browsers support gzip or both of them. 1872 * We thus only deal with the gzip variant and unzip the cache in case 1873 * the browser does not accept gzip encoding. 1874 * 1875 * @see drupal_page_header 1876 */ 1877 function page_set_cache() { 1878 global $user, $base_root; 1879 1880 if (!$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET' && count(drupal_get_messages(NULL, FALSE)) == 0) { 1881 // This will fail in some cases, see page_get_cache() for the explanation. 1882 if ($data = ob_get_contents()) { 1883 $cache = TRUE; 1884 if (function_exists('gzencode')) { 1885 // We do not store the data in case the zlib mode is deflate. 1886 // This should be rarely happening. 1887 if (zlib_get_coding_type() == 'deflate') { 1888 $cache = FALSE; 1889 } 1890 else if (zlib_get_coding_type() == FALSE) { 1891 $data = gzencode($data, 9, FORCE_GZIP); 1892 } 1893 // The remaining case is 'gzip' which means the data is 1894 // already compressed and nothing left to do but to store it. 1895 } 1896 ob_end_flush(); 1897 if ($cache && $data) { 1898 cache_set($base_root . request_uri(), 'cache_page', $data, CACHE_TEMPORARY, drupal_get_headers()); 1899 } 1900 } 1901 } 1902 } 1903 1904 /** 1905 * Send an e-mail message, using Drupal variables and default settings. 1906 * More information in the PHP function reference for mail() 1907 * @param $mailkey 1908 * A key to identify the mail sent, for altering. 1909 * @param $to 1910 * The mail address or addresses where the message will be send to. The 1911 * formatting of this string must comply with RFC 2822. Some examples are: 1912 * user@example.com 1913 * user@example.com, anotheruser@example.com 1914 * User <user@example.com> 1915 * User <user@example.com>, Another User <anotheruser@example.com> 1916 * @param $subject 1917 * Subject of the e-mail to be sent. This must not contain any newline 1918 * characters, or the mail may not be sent properly. 1919 * @param $body 1920 * Message to be sent. Drupal will format the correct line endings for you. 1921 * @param $from 1922 * Sets From, Reply-To, Return-Path and Error-To to this value, if given. 1923 * @param $headers 1924 * Associative array containing the headers to add. This is typically 1925 * used to add extra headers (From, Cc, and Bcc). 1926 * <em>When sending mail, the mail must contain a From header.</em> 1927 * @return Returns TRUE if the mail was successfully accepted for delivery, 1928 * FALSE otherwise. 1929 */ 1930 function drupal_mail($mailkey, $to, $subject, $body, $from = NULL, $headers = array()) { 1931 $defaults = array( 1932 'MIME-Version' => '1.0', 1933 'Content-Type' => 'text/plain; charset=UTF-8; format=flowed', 1934 'Content-Transfer-Encoding' => '8Bit', 1935 'X-Mailer' => 'Drupal' 1936 ); 1937 // To prevent e-mail from looking like spam, the addresses in the Sender and 1938 // Return-Path headers should have a domain authorized to use the originating 1939 // SMTP server. Errors-To is redundant, but shouldn't hurt. 1940 $default_from = variable_get('site_mail', ini_get('sendmail_from')); 1941 if ($default_from) { 1942 $defaults['From'] = $defaults['Reply-To'] = $defaults['Sender'] = $defaults['Return-Path'] = $defaults['Errors-To'] = $default_from; 1943 } 1944 if ($from) { 1945 $defaults['From'] = $defaults['Reply-To'] = $from; 1946 } 1947 $headers = array_merge($defaults, $headers); 1948 // Custom hook traversal to allow pass by reference 1949 foreach (module_implements('mail_alter') AS $module) { 1950 $function = $module .'_mail_alter'; 1951 $function($mailkey, $to, $subject, $body, $from, $headers); 1952 } 1953 // Allow for custom mail backend 1954 if (variable_get('smtp_library', '') && file_exists(variable_get('smtp_library', ''))) { 1955 include_once './' . variable_get('smtp_library', ''); 1956 return drupal_mail_wrapper($mailkey, $to, $subject, $body, $from, $headers); 1957 } 1958 else { 1959 // Note: if you are having problems with sending mail, or mails look wrong 1960 // when they are received you may have to modify the str_replace to suit 1961 // your systems. 1962 // - \r\n will work under dos and windows. 1963 // - \n will work for linux, unix and BSDs. 1964 // - \r will work for macs. 1965 // 1966 // According to RFC 2646, it's quite rude to not wrap your e-mails: 1967 // 1968 // "The Text/Plain media type is the lowest common denominator of 1969 // Internet e-mail, with lines of no more than 997 characters (by 1970 // convention usually no more than 80), and where the CRLF sequence 1971 // represents a line break [MIME-IMT]." 1972 // 1973 // CRLF === \r\n 1974 // 1975 // http://www.rfc-editor.org/rfc/rfc2646.txt 1976 1977 $mimeheaders = array(); 1978 foreach ($headers as $name => $value) { 1979 $mimeheaders[] = $name .': '. mime_header_encode($value); 1980 } 1981 return mail( 1982 $to, 1983 mime_header_encode($subject), 1984 str_replace("\r", '', $body), 1985 join("\n", $mimeheaders) 1986 ); 1987 } 1988 } 1989 1990 /** 1991 * Executes a cron run when called 1992 * @return 1993 * Returns TRUE if ran successfully 1994 */ 1995 function drupal_cron_run() { 1996 // If not in 'safe mode', increase the maximum execution time: 1997 if (!ini_get('safe_mode')) { 1998 set_time_limit(240); 1999 } 2000 2001 // Fetch the cron semaphore 2002 $semaphore = variable_get('cron_semaphore', FALSE); 2003 2004 if ($semaphore) { 2005 if (time() - $semaphore > 3600) { 2006 // Either cron has been running for more than an hour or the semaphore 2007 // was not reset due to a database error. 2008 watchdog('cron', t('Cron has been running for more than an hour and is most likely stuck.'), WATCHDOG_ERROR); 2009 2010 // Release cron semaphore 2011 variable_del('cron_semaphore'); 2012 } 2013 else { 2014 // Cron is still running normally. 2015 watchdog('cron', t('Attempting to re-run cron while it is already running.'), WATCHDOG_WARNING); 2016 } 2017 } 2018 else { 2019 // Register shutdown callback 2020 register_shutdown_function('drupal_cron_cleanup'); 2021 2022 // Lock cron semaphore 2023 variable_set('cron_semaphore', time()); 2024 2025 // Iterate through the modules calling their cron handlers (if any): 2026 module_invoke_all('cron'); 2027 2028 // Record cron time 2029 variable_set('cron_last', time()); 2030 watchdog('cron', t('Cron run completed.'), WATCHDOG_NOTICE); 2031 2032 // Release cron semaphore 2033 variable_del('cron_semaphore'); 2034 2035 // Return TRUE so other functions can check if it did run successfully 2036 return TRUE; 2037 } 2038 } 2039 2040 /** 2041 * Shutdown function for cron cleanup. 2042 */ 2043 function drupal_cron_cleanup() { 2044 // See if the semaphore is still locked. 2045 if (variable_get('cron_semaphore', FALSE)) { 2046 watchdog('cron', t('Cron run exceeded the time limit and was aborted.'), WATCHDOG_WARNING); 2047 2048 // Release cron semaphore 2049 variable_del('cron_semaphore'); 2050 } 2051 } 2052 2053 /** 2054 * Returns an array of files objects of the given type from the site-wide 2055 * directory (i.e. modules/), the all-sites directory (i.e. 2056 * sites/all/modules/), the profiles directory, and site-specific directory 2057 * (i.e. sites/somesite/modules/). The returned array will be keyed using the 2058 * key specified (name, basename, filename). Using name or basename will cause 2059 * site-specific files to be prioritized over similar files in the default 2060 * directories. That is, if a file with the same name appears in both the 2061 * site-wide directory and site-specific directory, only the site-specific 2062 * version will be included. 2063 * 2064 * @param $mask 2065 * The regular expression of the files to find. 2066 * @param $directory 2067 * The subdirectory name in which the files are found. For example, 2068 * 'modules' will search in both modules/ and 2069 * sites/somesite/modules/. 2070 * @param $key 2071 * The key to be passed to file_scan_directory(). 2072 * @param $min_depth 2073 * Minimum depth of directories to return files from. 2074 * 2075 * @return 2076 * An array of file objects of the specified type. 2077 */ 2078 function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) { 2079 global $profile; 2080 $config = conf_path(); 2081 2082 // When this function is called during Drupal's initial installation process, 2083 // the name of the profile that's about to be installed is stored in the global 2084 // $profile variable. At all other times, the standard Drupal systems variable 2085 // table contains the name of the current profile, and we can call variable_get() 2086 // to determine what one is active. 2087 if (!isset($profile)) { 2088 $profile = variable_get('install_profile', 'default'); 2089 } 2090 $searchdir = array($directory); 2091 $files = array(); 2092 2093 // Always search sites/all/* as well as the global directories 2094 $searchdir[] = 'sites/all/'. $directory; 2095 2096 // The 'profiles' directory contains pristine collections of modules and 2097 // themes as organized by a distribution. It is pristine in the same way 2098 // that /modules is pristine for core; users should avoid changing anything 2099 // there in favor of sites/all or sites/<domain> directories. 2100 if (file_exists("profiles/$profile/$directory")) { 2101 $searchdir[] = "profiles/$profile/$directory"; 2102 } 2103 2104 if (file_exists("$config/$directory")) { 2105 $searchdir[] = "$config/$directory"; 2106 } 2107 2108 // Get current list of items 2109 foreach ($searchdir as $dir) { 2110 $files = array_merge($files, file_scan_directory($dir, $mask, array('.', '..', 'CVS'), 0, TRUE, $key, $min_depth)); 2111 } 2112 2113 return $files; 2114 } 2115 2116 /** 2117 * Renders HTML given a structured array tree. Recursively iterates over each 2118 * of the array elements, generating HTML code. This function is usually 2119 * called from within a another function, like drupal_get_form() or node_view(). 2120 * 2121 * @param $elements 2122 * The structured array describing the data to be rendered. 2123 * @return 2124 * The rendered HTML. 2125 */ 2126 function drupal_render(&$elements) { 2127 if (!isset($elements) || (isset($elements['#access']) && !$elements['#access'])) { 2128 return NULL; 2129 } 2130 2131 $content = ''; 2132 // Either the elements did not go through form_builder or one of the children 2133 // has a #weight. 2134 if (!isset($elements['#sorted'])) { 2135 uasort($elements, "_element_sort"); 2136 } 2137 if (!isset($elements['#children'])) { 2138 $children = element_children($elements); 2139 /* Render all the children that use a theme function */ 2140 if (isset($elements['#theme']) && empty($elements['#theme_used'])) { 2141 $elements['#theme_used'] = TRUE; 2142 2143 $previous = array(); 2144 foreach (array('#value', '#type', '#prefix', '#suffix') as $key) { 2145 $previous[$key] = isset($elements[$key]) ? $elements[$key] : NULL; 2146 } 2147 // If we rendered a single element, then we will skip the renderer. 2148 if (empty($children)) { 2149 $elements['#printed'] = TRUE; 2150 } 2151 else { 2152 $elements['#value'] = ''; 2153 } 2154 $elements['#type'] = 'markup'; 2155 2156 unset($elements['#prefix'], $elements['#suffix']); 2157 $content = theme($elements['#theme'], $elements); 2158 2159 foreach (array('#value', '#type', '#prefix', '#suffix') as $key) { 2160 $elements[$key] = isset($previous[$key]) ? $previous[$key] : NULL; 2161 } 2162 } 2163 /* render each of the children using drupal_render and concatenate them */ 2164 if (!isset($content) || $content === '') { 2165 foreach ($children as $key) { 2166 $content .= drupal_render($elements[$key]); 2167 } 2168 } 2169 } 2170 if (isset($content) && $content !== '') { 2171 $elements['#children'] = $content; 2172 } 2173 2174 // Until now, we rendered the children, here we render the element itself 2175 if (!isset($elements['#printed'])) { 2176 $content = theme(!empty($elements['#type']) ? $elements['#type'] : 'markup', $elements); 2177 $elements['#printed'] = TRUE; 2178 } 2179 2180 if (isset($content) && $content !== '') { 2181 $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : ''; 2182 $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : ''; 2183 return $prefix . $content . $suffix; 2184 } 2185 } 2186 2187 /** 2188 * Function used by uasort in drupal_render() to sort structured arrays 2189 * by weight. 2190 */ 2191 function _element_sort($a, $b) { 2192 $a_weight = (is_array($a) && isset($a['#weight'])) ? $a['#weight'] : 0; 2193 $b_weight = (is_array($b) && isset($b['#weight'])) ? $b['#weight'] : 0; 2194 if ($a_weight == $b_weight) { 2195 return 0; 2196 } 2197 return ($a_weight < $b_weight) ? -1 : 1; 2198 } 2199 2200 /** 2201 * Check if the key is a property. 2202 */ 2203 function element_property($key) { 2204 return $key[0] == '#'; 2205 } 2206 2207 /** 2208 * Get properties of a structured array element. Properties begin with '#'. 2209 */ 2210 function element_properties($element) { 2211 return array_filter(array_keys((array) $element), 'element_property'); 2212 } 2213 2214 /** 2215 * Check if the key is a child. 2216 */ 2217 function element_child($key) { 2218 return $key[0] != '#'; 2219 } 2220 2221 /** 2222 * Get keys of a structured array tree element that are not properties 2223 * (i.e., do not begin with '#'). 2224 */ 2225 function element_children($element) { 2226 return array_filter(array_keys((array) $element), 'element_child'); 2227 }
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 |
![]() |