| [ Index ] |
PHP Cross Reference of Drupal 6 (yi-drupal) |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @file 5 * Common functions that many Drupal modules will need to reference. 6 * 7 * The functions that are critical and need to be available even when serving 8 * a cached page are instead located in bootstrap.inc. 9 */ 10 11 /** 12 * Return status for saving which involved creating a new item. 13 */ 14 define('SAVED_NEW', 1); 15 16 /** 17 * Return status for saving which involved an update to an existing item. 18 */ 19 define('SAVED_UPDATED', 2); 20 21 /** 22 * Return status for saving which deleted an existing item. 23 */ 24 define('SAVED_DELETED', 3); 25 26 /** 27 * Create E_DEPRECATED constant for older PHP versions (<5.3). 28 */ 29 if (!defined('E_DEPRECATED')) { 30 define('E_DEPRECATED', 8192); 31 } 32 33 /** 34 * Error code indicating that the request made by drupal_http_request() exceeded 35 * the specified timeout. 36 */ 37 define('HTTP_REQUEST_TIMEOUT', -1); 38 39 /** 40 * Set content for a specified region. 41 * 42 * @param $region 43 * Page region the content is assigned to. 44 * @param $data 45 * Content to be set. 46 */ 47 function drupal_set_content($region = NULL, $data = NULL) { 48 static $content = array(); 49 50 if (!is_null($region) && !is_null($data)) { 51 $content[$region][] = $data; 52 } 53 return $content; 54 } 55 56 /** 57 * Get assigned content. 58 * 59 * @param $region 60 * A specified region to fetch content for. If NULL, all regions will be 61 * returned. 62 * @param $delimiter 63 * Content to be inserted between imploded array elements. 64 */ 65 function drupal_get_content($region = NULL, $delimiter = ' ') { 66 $content = drupal_set_content(); 67 if (isset($region)) { 68 if (isset($content[$region]) && is_array($content[$region])) { 69 return implode($delimiter, $content[$region]); 70 } 71 } 72 else { 73 foreach (array_keys($content) as $region) { 74 if (is_array($content[$region])) { 75 $content[$region] = implode($delimiter, $content[$region]); 76 } 77 } 78 return $content; 79 } 80 } 81 82 /** 83 * Set the breadcrumb trail for the current page. 84 * 85 * @param $breadcrumb 86 * Array of links, starting with "home" and proceeding up to but not including 87 * the current page. 88 */ 89 function drupal_set_breadcrumb($breadcrumb = NULL) { 90 static $stored_breadcrumb; 91 92 if (!is_null($breadcrumb)) { 93 $stored_breadcrumb = $breadcrumb; 94 } 95 return $stored_breadcrumb; 96 } 97 98 /** 99 * Get the breadcrumb trail for the current page. 100 */ 101 function drupal_get_breadcrumb() { 102 $breadcrumb = drupal_set_breadcrumb(); 103 104 if (is_null($breadcrumb)) { 105 $breadcrumb = menu_get_active_breadcrumb(); 106 } 107 108 return $breadcrumb; 109 } 110 111 /** 112 * Add output to the head tag of the HTML page. 113 * 114 * This function can be called as long the headers aren't sent. 115 */ 116 function drupal_set_html_head($data = NULL) { 117 static $stored_head = ''; 118 119 if (!is_null($data)) { 120 $stored_head .= $data ."\n"; 121 } 122 return $stored_head; 123 } 124 125 /** 126 * Retrieve output to be displayed in the head tag of the HTML page. 127 */ 128 function drupal_get_html_head() { 129 $output = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"; 130 return $output . drupal_set_html_head(); 131 } 132 133 /** 134 * Reset the static variable which holds the aliases mapped for this request. 135 */ 136 function drupal_clear_path_cache() { 137 drupal_lookup_path('wipe'); 138 } 139 140 /** 141 * Set an HTTP response header for the current page. 142 * 143 * Note: When sending a Content-Type header, always include a 'charset' type, 144 * too. This is necessary to avoid security bugs (e.g. UTF-7 XSS). 145 */ 146 function drupal_set_header($header = NULL) { 147 // We use an array to guarantee there are no leading or trailing delimiters. 148 // Otherwise, header('') could get called when serving the page later, which 149 // ends HTTP headers prematurely on some PHP versions. 150 static $stored_headers = array(); 151 152 if (strlen($header)) { 153 header($header); 154 $stored_headers[] = $header; 155 } 156 return implode("\n", $stored_headers); 157 } 158 159 /** 160 * Get the HTTP response headers for the current page. 161 */ 162 function drupal_get_headers() { 163 return drupal_set_header(); 164 } 165 166 /** 167 * Make any final alterations to the rendered xhtml. 168 */ 169 function drupal_final_markup($content) { 170 // Make sure that the charset is always specified as the first element of the 171 // head region to prevent encoding-based attacks. 172 return preg_replace('/<head[^>]*>/i', "\$0\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />", $content, 1); 173 } 174 175 /** 176 * Add a feed URL for the current page. 177 * 178 * @param $url 179 * A url for the feed. 180 * @param $title 181 * The title of the feed. 182 */ 183 function drupal_add_feed($url = NULL, $title = '') { 184 static $stored_feed_links = array(); 185 186 if (!is_null($url) && !isset($stored_feed_links[$url])) { 187 $stored_feed_links[$url] = theme('feed_icon', $url, $title); 188 189 drupal_add_link(array('rel' => 'alternate', 190 'type' => 'application/rss+xml', 191 'title' => $title, 192 'href' => $url)); 193 } 194 return $stored_feed_links; 195 } 196 197 /** 198 * Get the feed URLs for the current page. 199 * 200 * @param $delimiter 201 * A delimiter to split feeds by. 202 */ 203 function drupal_get_feeds($delimiter = "\n") { 204 $feeds = drupal_add_feed(); 205 return implode($feeds, $delimiter); 206 } 207 208 /** 209 * @defgroup http_handling HTTP handling 210 * @{ 211 * Functions to properly handle HTTP responses. 212 */ 213 214 /** 215 * Parse an array into a valid urlencoded query string. 216 * 217 * @param $query 218 * The array to be processed e.g. $_GET. 219 * @param $exclude 220 * The array filled with keys to be excluded. Use parent[child] to exclude 221 * nested items. 222 * @param $parent 223 * Should not be passed, only used in recursive calls. 224 * @return 225 * An urlencoded string which can be appended to/as the URL query string. 226 */ 227 function drupal_query_string_encode($query, $exclude = array(), $parent = '') { 228 $params = array(); 229 230 foreach ($query as $key => $value) { 231 $key = rawurlencode($key); 232 if ($parent) { 233 $key = $parent .'['. $key .']'; 234 } 235 236 if (in_array($key, $exclude)) { 237 continue; 238 } 239 240 if (is_array($value)) { 241 $params[] = drupal_query_string_encode($value, $exclude, $key); 242 } 243 else { 244 $params[] = $key .'='. rawurlencode($value); 245 } 246 } 247 248 return implode('&', $params); 249 } 250 251 /** 252 * Prepare a destination query string for use in combination with drupal_goto(). 253 * 254 * Used to direct the user back to the referring page after completing a form. 255 * By default the current URL is returned. If a destination exists in the 256 * previous request, that destination is returned. As such, a destination can 257 * persist across multiple pages. 258 * 259 * @see drupal_goto() 260 */ 261 function drupal_get_destination() { 262 if (isset($_REQUEST['destination'])) { 263 return 'destination='. urlencode($_REQUEST['destination']); 264 } 265 else { 266 // Use $_GET here to retrieve the original path in source form. 267 $path = isset($_GET['q']) ? $_GET['q'] : ''; 268 $query = drupal_query_string_encode($_GET, array('q')); 269 if ($query != '') { 270 $path .= '?'. $query; 271 } 272 return 'destination='. urlencode($path); 273 } 274 } 275 276 /** 277 * Send the user to a different Drupal page. 278 * 279 * This issues an on-site HTTP redirect. The function makes sure the redirected 280 * URL is formatted correctly. 281 * 282 * Usually the redirected URL is constructed from this function's input 283 * parameters. However you may override that behavior by setting a 284 * destination in either the $_REQUEST-array (i.e. by using 285 * the query string of an URI) or the $_REQUEST['edit']-array (i.e. by 286 * using a hidden form field). This is used to direct the user back to 287 * the proper page after completing a form. For example, after editing 288 * a post on the 'admin/content/node'-page or after having logged on using the 289 * 'user login'-block in a sidebar. The function drupal_get_destination() 290 * can be used to help set the destination URL. 291 * 292 * Drupal will ensure that messages set by drupal_set_message() and other 293 * session data are written to the database before the user is redirected. 294 * 295 * This function ends the request; use it rather than a print theme('page') 296 * statement in your menu callback. 297 * 298 * @param $path 299 * A Drupal path or a full URL. 300 * @param $query 301 * A query string component, if any. 302 * @param $fragment 303 * A destination fragment identifier (named anchor). 304 * @param $http_response_code 305 * Valid values for an actual "goto" as per RFC 2616 section 10.3 are: 306 * - 301 Moved Permanently (the recommended value for most redirects) 307 * - 302 Found (default in Drupal and PHP, sometimes used for spamming search 308 * engines) 309 * - 303 See Other 310 * - 304 Not Modified 311 * - 305 Use Proxy 312 * - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance") 313 * Note: Other values are defined by RFC 2616, but are rarely used and poorly 314 * supported. 315 * @see drupal_get_destination() 316 */ 317 function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) { 318 319 $destination = FALSE; 320 if (isset($_REQUEST['destination'])) { 321 $destination = $_REQUEST['destination']; 322 } 323 else if (isset($_REQUEST['edit']['destination'])) { 324 $destination = $_REQUEST['edit']['destination']; 325 } 326 327 if ($destination) { 328 // Do not redirect to an absolute URL originating from user input. 329 $colonpos = strpos($destination, ':'); 330 $absolute = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($destination, 0, $colonpos))); 331 if (!$absolute) { 332 extract(parse_url(urldecode($destination))); 333 } 334 } 335 336 $url = url($path, array('query' => $query, 'fragment' => $fragment, 'absolute' => TRUE)); 337 // Remove newlines from the URL to avoid header injection attacks. 338 $url = str_replace(array("\n", "\r"), '', $url); 339 340 // Allow modules to react to the end of the page request before redirecting. 341 // We do not want this while running update.php. 342 if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { 343 module_invoke_all('exit', $url); 344 } 345 346 // Even though session_write_close() is registered as a shutdown function, we 347 // need all session data written to the database before redirecting. 348 session_write_close(); 349 350 header('Location: '. $url, TRUE, $http_response_code); 351 352 // The "Location" header sends a redirect status code to the HTTP daemon. In 353 // some cases this can be wrong, so we make sure none of the code below the 354 // drupal_goto() call gets executed upon redirection. 355 exit(); 356 } 357 358 /** 359 * Generates a site off-line message. 360 */ 361 function drupal_site_offline() { 362 drupal_maintenance_theme(); 363 drupal_set_header($_SERVER['SERVER_PROTOCOL'] .' 503 Service unavailable'); 364 drupal_set_title(t('Site off-line')); 365 print theme('maintenance_page', filter_xss_admin(variable_get('site_offline_message', 366 t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))); 367 } 368 369 /** 370 * Generates a 404 error if the request can not be handled. 371 */ 372 function drupal_not_found() { 373 drupal_set_header($_SERVER['SERVER_PROTOCOL'] .' 404 Not Found'); 374 375 watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); 376 377 // Keep old path for reference, and to allow forms to redirect to it. 378 if (!isset($_REQUEST['destination'])) { 379 $_REQUEST['destination'] = $_GET['q']; 380 } 381 382 $path = drupal_get_normal_path(variable_get('site_404', '')); 383 if ($path && $path != $_GET['q']) { 384 // Set the active item in case there are tabs to display, or other 385 // dependencies on the path. 386 menu_set_active_item($path); 387 $return = menu_execute_active_handler($path); 388 } 389 390 if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) { 391 drupal_set_title(t('Page not found')); 392 $return = t('The requested page could not be found.'); 393 } 394 395 // To conserve CPU and bandwidth, omit the blocks. 396 print theme('page', $return, FALSE); 397 } 398 399 /** 400 * Generates a 403 error if the request is not allowed. 401 */ 402 function drupal_access_denied() { 403 drupal_set_header($_SERVER['SERVER_PROTOCOL'] .' 403 Forbidden'); 404 405 watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); 406 407 // Keep old path for reference, and to allow forms to redirect to it. 408 if (!isset($_REQUEST['destination'])) { 409 $_REQUEST['destination'] = $_GET['q']; 410 } 411 412 $path = drupal_get_normal_path(variable_get('site_403', '')); 413 if ($path && $path != $_GET['q']) { 414 // Set the active item in case there are tabs to display or other 415 // dependencies on the path. 416 menu_set_active_item($path); 417 $return = menu_execute_active_handler($path); 418 } 419 420 if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) { 421 drupal_set_title(t('Access denied')); 422 $return = t('You are not authorized to access this page.'); 423 } 424 print theme('page', $return); 425 } 426 427 /** 428 * Perform an HTTP request. 429 * 430 * This is a flexible and powerful HTTP client implementation. Correctly handles 431 * GET, POST, PUT or any other HTTP requests. Handles redirects. 432 * 433 * @param $url 434 * A string containing a fully qualified URI. 435 * @param $headers 436 * An array containing an HTTP header => value pair. 437 * @param $method 438 * A string defining the HTTP request to use. 439 * @param $data 440 * A string containing data to include in the request. 441 * @param $retry 442 * An integer representing how many times to retry the request in case of a 443 * redirect. 444 * @param $timeout 445 * A float representing the maximum number of seconds the function call may 446 * take. The default is 30 seconds. If a timeout occurs, the error code is set 447 * to the HTTP_REQUEST_TIMEOUT constant. 448 * @return 449 * An object containing the HTTP request headers, response code, protocol, 450 * status message, headers, data and redirect status. 451 */ 452 function drupal_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3, $timeout = 30.0) { 453 global $db_prefix; 454 455 $result = new stdClass(); 456 457 // Parse the URL and make sure we can handle the schema. 458 $uri = parse_url($url); 459 460 if ($uri == FALSE) { 461 $result->error = 'unable to parse URL'; 462 $result->code = -1001; 463 return $result; 464 } 465 466 if (!isset($uri['scheme'])) { 467 $result->error = 'missing schema'; 468 $result->code = -1002; 469 return $result; 470 } 471 472 timer_start(__FUNCTION__); 473 474 switch ($uri['scheme']) { 475 case 'http': 476 case 'feed': 477 $port = isset($uri['port']) ? $uri['port'] : 80; 478 $host = $uri['host'] . ($port != 80 ? ':'. $port : ''); 479 $fp = @fsockopen($uri['host'], $port, $errno, $errstr, $timeout); 480 break; 481 case 'https': 482 // Note: Only works for PHP 4.3 compiled with OpenSSL. 483 $port = isset($uri['port']) ? $uri['port'] : 443; 484 $host = $uri['host'] . ($port != 443 ? ':'. $port : ''); 485 $fp = @fsockopen('ssl://'. $uri['host'], $port, $errno, $errstr, $timeout); 486 break; 487 default: 488 $result->error = 'invalid schema '. $uri['scheme']; 489 $result->code = -1003; 490 return $result; 491 } 492 493 // Make sure the socket opened properly. 494 if (!$fp) { 495 // When a network error occurs, we use a negative number so it does not 496 // clash with the HTTP status codes. 497 $result->code = -$errno; 498 $result->error = trim($errstr); 499 500 // Mark that this request failed. This will trigger a check of the web 501 // server's ability to make outgoing HTTP requests the next time that 502 // requirements checking is performed. 503 // @see system_requirements() 504 variable_set('drupal_http_request_fails', TRUE); 505 506 return $result; 507 } 508 509 // Construct the path to act on. 510 $path = isset($uri['path']) ? $uri['path'] : '/'; 511 if (isset($uri['query'])) { 512 $path .= '?'. $uri['query']; 513 } 514 515 // Create HTTP request. 516 $defaults = array( 517 // RFC 2616: "non-standard ports MUST, default ports MAY be included". 518 // We don't add the port to prevent from breaking rewrite rules checking the 519 // host that do not take into account the port number. 520 'Host' => "Host: $host", 521 'User-Agent' => 'User-Agent: Drupal (+http://drupal.org/)', 522 ); 523 524 // Only add Content-Length if we actually have any content or if it is a POST 525 // or PUT request. Some non-standard servers get confused by Content-Length in 526 // at least HEAD/GET requests, and Squid always requires Content-Length in 527 // POST/PUT requests. 528 $content_length = strlen($data); 529 if ($content_length > 0 || $method == 'POST' || $method == 'PUT') { 530 $defaults['Content-Length'] = 'Content-Length: '. $content_length; 531 } 532 533 // If the server url has a user then attempt to use basic authentication 534 if (isset($uri['user'])) { 535 $defaults['Authorization'] = 'Authorization: Basic '. base64_encode($uri['user'] . (!empty($uri['pass']) ? ":". $uri['pass'] : '')); 536 } 537 538 // If the database prefix is being used by SimpleTest to run the tests in a copied 539 // database then set the user-agent header to the database prefix so that any 540 // calls to other Drupal pages will run the SimpleTest prefixed database. The 541 // user-agent is used to ensure that multiple testing sessions running at the 542 // same time won't interfere with each other as they would if the database 543 // prefix were stored statically in a file or database variable. 544 if (is_string($db_prefix) && preg_match("/^simpletest\d+$/", $db_prefix, $matches)) { 545 $defaults['User-Agent'] = 'User-Agent: ' . $matches[0]; 546 } 547 548 foreach ($headers as $header => $value) { 549 $defaults[$header] = $header .': '. $value; 550 } 551 552 $request = $method .' '. $path ." HTTP/1.0\r\n"; 553 $request .= implode("\r\n", $defaults); 554 $request .= "\r\n\r\n"; 555 $request .= $data; 556 557 $result->request = $request; 558 559 // Calculate how much time is left of the original timeout value. 560 $time_left = $timeout - timer_read(__FUNCTION__) / 1000; 561 if ($time_left > 0) { 562 stream_set_timeout($fp, floor($time_left), floor(1000000 * fmod($time_left, 1))); 563 fwrite($fp, $request); 564 } 565 566 // Fetch response. 567 $response = ''; 568 while (!feof($fp)) { 569 // Calculate how much time is left of the original timeout value. 570 $time_left = $timeout - timer_read(__FUNCTION__) / 1000; 571 if ($time_left <= 0) { 572 $result->code = HTTP_REQUEST_TIMEOUT; 573 $result->error = 'request timed out'; 574 return $result; 575 } 576 stream_set_timeout($fp, floor($time_left), floor(1000000 * fmod($time_left, 1))); 577 $chunk = fread($fp, 1024); 578 $response .= $chunk; 579 } 580 fclose($fp); 581 582 // Parse response. 583 list($split, $result->data) = explode("\r\n\r\n", $response, 2); 584 $split = preg_split("/\r\n|\n|\r/", $split); 585 586 list($protocol, $code, $status_message) = explode(' ', trim(array_shift($split)), 3); 587 $result->protocol = $protocol; 588 $result->status_message = $status_message; 589 590 $result->headers = array(); 591 592 // Parse headers. 593 while ($line = trim(array_shift($split))) { 594 list($header, $value) = explode(':', $line, 2); 595 if (isset($result->headers[$header]) && $header == 'Set-Cookie') { 596 // RFC 2109: the Set-Cookie response header comprises the token Set- 597 // Cookie:, followed by a comma-separated list of one or more cookies. 598 $result->headers[$header] .= ','. trim($value); 599 } 600 else { 601 $result->headers[$header] = trim($value); 602 } 603 } 604 605 $responses = array( 606 100 => 'Continue', 101 => 'Switching Protocols', 607 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 608 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', 609 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', 610 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported' 611 ); 612 // RFC 2616 states that all unknown HTTP codes must be treated the same as the 613 // base code in their class. 614 if (!isset($responses[$code])) { 615 $code = floor($code / 100) * 100; 616 } 617 618 switch ($code) { 619 case 200: // OK 620 case 304: // Not modified 621 break; 622 case 301: // Moved permanently 623 case 302: // Moved temporarily 624 case 307: // Moved temporarily 625 $location = $result->headers['Location']; 626 $timeout -= timer_read(__FUNCTION__) / 1000; 627 if ($timeout <= 0) { 628 $result->code = HTTP_REQUEST_TIMEOUT; 629 $result->error = 'request timed out'; 630 } 631 elseif ($retry) { 632 $result = drupal_http_request($result->headers['Location'], $headers, $method, $data, --$retry, $timeout); 633 $result->redirect_code = $result->code; 634 } 635 $result->redirect_url = $location; 636 637 break; 638 default: 639 $result->error = $status_message; 640 } 641 642 $result->code = $code; 643 return $result; 644 } 645 /** 646 * @} End of "HTTP handling". 647 */ 648 649 /** 650 * Log errors as defined by administrator. 651 * 652 * Error levels: 653 * - 0 = Log errors to database. 654 * - 1 = Log errors to database and to screen. 655 */ 656 function drupal_error_handler($errno, $message, $filename, $line, $context) { 657 // If the @ error suppression operator was used, error_reporting will have 658 // been temporarily set to 0. 659 if (error_reporting() == 0) { 660 return; 661 } 662 663 if ($errno & (E_ALL ^ E_DEPRECATED ^ E_NOTICE)) { 664 $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', 4096 => 'recoverable fatal error'); 665 666 // For database errors, we want the line number/file name of the place that 667 // the query was originally called, not _db_query(). 668 if (isset($context[DB_ERROR])) { 669 $backtrace = array_reverse(debug_backtrace()); 670 671 // List of functions where SQL queries can originate. 672 $query_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql'); 673 674 // Determine where query function was called, and adjust line/file 675 // accordingly. 676 foreach ($backtrace as $index => $function) { 677 if (in_array($function['function'], $query_functions)) { 678 $line = $backtrace[$index]['line']; 679 $filename = $backtrace[$index]['file']; 680 break; 681 } 682 } 683 } 684 685 // Try to use filter_xss(). If it's too early in the bootstrap process for 686 // filter_xss() to be loaded, use check_plain() instead. 687 $entry = check_plain($types[$errno]) .': '. (function_exists('filter_xss') ? filter_xss($message) : check_plain($message)) .' in '. check_plain($filename) .' on line '. check_plain($line) .'.'; 688 689 // Force display of error messages in update.php. 690 if (variable_get('error_level', 1) == 1 || strstr($_SERVER['SCRIPT_NAME'], 'update.php')) { 691 drupal_set_message($entry, 'error'); 692 } 693 694 watchdog('php', '%message in %file on line %line.', array('%error' => $types[$errno], '%message' => $message, '%file' => $filename, '%line' => $line), WATCHDOG_ERROR); 695 } 696 } 697 698 function _fix_gpc_magic(&$item) { 699 if (is_array($item)) { 700 array_walk($item, '_fix_gpc_magic'); 701 } 702 else { 703 $item = stripslashes($item); 704 } 705 } 706 707 /** 708 * Helper function to strip slashes from $_FILES skipping over the tmp_name keys 709 * since PHP generates single backslashes for file paths on Windows systems. 710 * 711 * tmp_name does not have backslashes added see 712 * http://php.net/manual/en/features.file-upload.php#42280 713 */ 714 function _fix_gpc_magic_files(&$item, $key) { 715 if ($key != 'tmp_name') { 716 if (is_array($item)) { 717 array_walk($item, '_fix_gpc_magic_files'); 718 } 719 else { 720 $item = stripslashes($item); 721 } 722 } 723 } 724 725 /** 726 * Fix double-escaping problems caused by "magic quotes" in some PHP installations. 727 */ 728 function fix_gpc_magic() { 729 static $fixed = FALSE; 730 if (!$fixed && ini_get('magic_quotes_gpc')) { 731 array_walk($_GET, '_fix_gpc_magic'); 732 array_walk($_POST, '_fix_gpc_magic'); 733 array_walk($_COOKIE, '_fix_gpc_magic'); 734 array_walk($_REQUEST, '_fix_gpc_magic'); 735 array_walk($_FILES, '_fix_gpc_magic_files'); 736 $fixed = TRUE; 737 } 738 } 739 740 /** 741 * Translate strings to the page language or a given language. 742 * 743 * Human-readable text that will be displayed somewhere within a page should 744 * be run through the t() function. 745 * 746 * Examples: 747 * @code 748 * if (!$info || !$info['extension']) { 749 * form_set_error('picture_upload', t('The uploaded file was not an image.')); 750 * } 751 * 752 * $form['submit'] = array( 753 * '#type' => 'submit', 754 * '#value' => t('Log in'), 755 * ); 756 * @endcode 757 * 758 * Any text within t() can be extracted by translators and changed into 759 * the equivalent text in their native language. 760 * 761 * Special variables called "placeholders" are used to signal dynamic 762 * information in a string which should not be translated. Placeholders 763 * can also be used for text that may change from time to time (such as 764 * link paths) to be changed without requiring updates to translations. 765 * 766 * For example: 767 * @code 768 * $output = t('There are currently %members and %visitors online.', array( 769 * '%members' => format_plural($total_users, '1 user', '@count users'), 770 * '%visitors' => format_plural($guests->count, '1 guest', '@count guests'))); 771 * @endcode 772 * 773 * There are three styles of placeholders: 774 * - !variable, which indicates that the text should be inserted as-is. This is 775 * useful for inserting variables into things like e-mail. 776 * @code 777 * $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", array('absolute' => TRUE)))); 778 * @endcode 779 * 780 * - @variable, which indicates that the text should be run through 781 * check_plain, to escape HTML characters. Use this for any output that's 782 * displayed within a Drupal page. 783 * @code 784 * drupal_set_title($title = t("@name's blog", array('@name' => $account->name))); 785 * @endcode 786 * 787 * - %variable, which indicates that the string should be HTML escaped and 788 * highlighted with theme_placeholder() which shows up by default as 789 * <em>emphasized</em>. 790 * @code 791 * $message = t('%name-from sent %name-to an e-mail.', array('%name-from' => $user->name, '%name-to' => $account->name)); 792 * @endcode 793 * 794 * When using t(), try to put entire sentences and strings in one t() call. 795 * This makes it easier for translators, as it provides context as to what 796 * each word refers to. HTML markup within translation strings is allowed, but 797 * should be avoided if possible. The exception are embedded links; link 798 * titles add a context for translators, so should be kept in the main string. 799 * 800 * Here is an example of incorrect usage of t(): 801 * @code 802 * $output .= t('<p>Go to the @contact-page.</p>', array('@contact-page' => l(t('contact page'), 'contact'))); 803 * @endcode 804 * 805 * Here is an example of t() used correctly: 806 * @code 807 * $output .= '<p>'. t('Go to the <a href="@contact-page">contact page</a>.', array('@contact-page' => url('contact'))) .'</p>'; 808 * @endcode 809 * 810 * Avoid escaping quotation marks wherever possible. 811 * 812 * Incorrect: 813 * @code 814 * $output .= t('Don\'t click me.'); 815 * @endcode 816 * 817 * Correct: 818 * @code 819 * $output .= t("Don't click me."); 820 * @endcode 821 * 822 * Because t() is designed for handling code-based strings, in almost all 823 * cases, the actual string and not a variable must be passed through t(). 824 * 825 * Extraction of translations is done based on the strings contained in t() 826 * calls. If a variable is passed through t(), the content of the variable 827 * cannot be extracted from the file for translation. 828 * 829 * Incorrect: 830 * @code 831 * $message = 'An error occurred.'; 832 * drupal_set_message(t($message), 'error'); 833 * $output .= t($message); 834 * @endcode 835 * 836 * Correct: 837 * @code 838 * $message = t('An error occurred.'); 839 * drupal_set_message($message, 'error'); 840 * $output .= $message; 841 * @endcode 842 * 843 * The only case in which variables can be passed safely through t() is when 844 * code-based versions of the same strings will be passed through t() (or 845 * otherwise extracted) elsewhere. 846 * 847 * In some cases, modules may include strings in code that can't use t() 848 * calls. For example, a module may use an external PHP application that 849 * produces strings that are loaded into variables in Drupal for output. 850 * In these cases, module authors may include a dummy file that passes the 851 * relevant strings through t(). This approach will allow the strings to be 852 * extracted. 853 * 854 * Sample external (non-Drupal) code: 855 * @code 856 * class Time { 857 * public $yesterday = 'Yesterday'; 858 * public $today = 'Today'; 859 * public $tomorrow = 'Tomorrow'; 860 * } 861 * @endcode 862 * 863 * Sample dummy file. 864 * @code 865 * // Dummy function included in example.potx.inc. 866 * function example_potx() { 867 * $strings = array( 868 * t('Yesterday'), 869 * t('Today'), 870 * t('Tomorrow'), 871 * ); 872 * // No return value needed, since this is a dummy function. 873 * } 874 * @endcode 875 * 876 * Having passed strings through t() in a dummy function, it is then 877 * okay to pass variables through t(). 878 * 879 * Correct (if a dummy file was used): 880 * @code 881 * $time = new Time(); 882 * $output .= t($time->today); 883 * @endcode 884 * 885 * However tempting it is, custom data from user input or other non-code 886 * sources should not be passed through t(). Doing so leads to the following 887 * problems and errors: 888 * - The t() system doesn't support updates to existing strings. When user 889 * data is updated, the next time it's passed through t() a new record is 890 * created instead of an update. The database bloats over time and any 891 * existing translations are orphaned with each update. 892 * - The t() system assumes any data it receives is in English. User data may 893 * be in another language, producing translation errors. 894 * - The "Built-in interface" text group in the locale system is used to 895 * produce translations for storage in .po files. When non-code strings are 896 * passed through t(), they are added to this text group, which is rendered 897 * inaccurate since it is a mix of actual interface strings and various user 898 * input strings of uncertain origin. 899 * 900 * Incorrect: 901 * @code 902 * $item = item_load(); 903 * $output .= check_plain(t($item['title'])); 904 * @endcode 905 * 906 * Instead, translation of these data can be done through the locale system, 907 * either directly or through helper functions provided by contributed 908 * modules. 909 * @see hook_locale() 910 * 911 * During installation, st() is used in place of t(). Code that may be called 912 * during installation or during normal operation should use the get_t() 913 * helper function. 914 * @see st() 915 * @see get_t() 916 * 917 * @param $string 918 * A string containing the English string to translate. 919 * @param $args 920 * An associative array of replacements to make after translation. Incidences 921 * of any key in this array are replaced with the corresponding value. Based 922 * on the first character of the key, the value is escaped and/or themed: 923 * - !variable: inserted as is 924 * - @variable: escape plain text to HTML (check_plain) 925 * - %variable: escape text and theme as a placeholder for user-submitted 926 * content (check_plain + theme_placeholder) 927 * @param $langcode 928 * Optional language code to translate to a language other than what is used 929 * to display the page. 930 * @return 931 * The translated string. 932 */ 933 function t($string, $args = array(), $langcode = NULL) { 934 global $language; 935 static $custom_strings; 936 937 $langcode = isset($langcode) ? $langcode : $language->language; 938 939 // First, check for an array of customized strings. If present, use the array 940 // *instead of* database lookups. This is a high performance way to provide a 941 // handful of string replacements. See settings.php for examples. 942 // Cache the $custom_strings variable to improve performance. 943 if (!isset($custom_strings[$langcode])) { 944 $custom_strings[$langcode] = variable_get('locale_custom_strings_'. $langcode, array()); 945 } 946 // Custom strings work for English too, even if locale module is disabled. 947 if (isset($custom_strings[$langcode][$string])) { 948 $string = $custom_strings[$langcode][$string]; 949 } 950 // Translate with locale module if enabled. 951 elseif (function_exists('locale') && $langcode != 'en') { 952 $string = locale($string, $langcode); 953 } 954 if (empty($args)) { 955 return $string; 956 } 957 else { 958 // Transform arguments before inserting them. 959 foreach ($args as $key => $value) { 960 switch ($key[0]) { 961 case '@': 962 // Escaped only. 963 $args[$key] = check_plain($value); 964 break; 965 966 case '%': 967 default: 968 // Escaped and placeholder. 969 $args[$key] = theme('placeholder', $value); 970 break; 971 972 case '!': 973 // Pass-through. 974 } 975 } 976 return strtr($string, $args); 977 } 978 } 979 980 /** 981 * @defgroup validation Input validation 982 * @{ 983 * Functions to validate user input. 984 */ 985 986 /** 987 * Verifies the syntax of the given e-mail address. 988 * 989 * See RFC 2822 for details. 990 * 991 * @param $mail 992 * A string containing an e-mail address. 993 * @return 994 * 1 if the email address is valid, 0 if it is invalid or empty, and FALSE if 995 * there is an input error (such as passing in an array instead of a string). 996 */ 997 function valid_email_address($mail) { 998 $user = '[a-zA-Z0-9_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\']+'; 999 $domain = '(?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])(\.[a-zA-Z0-9]+)*)+'; 1000 $ipv4 = '[0-9]{1,3}(\.[0-9]{1,3}){3}'; 1001 $ipv6 = '[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7}'; 1002 1003 return preg_match("/^$user@($domain|(\[($ipv4|$ipv6)\]))$/", $mail); 1004 } 1005 1006 /** 1007 * Verify the syntax of the given URL. 1008 * 1009 * This function should only be used on actual URLs. It should not be used for 1010 * Drupal menu paths, which can contain arbitrary characters. 1011 * Valid values per RFC 3986. 1012 * 1013 * @param $url 1014 * The URL to verify. 1015 * @param $absolute 1016 * Whether the URL is absolute (beginning with a scheme such as "http:"). 1017 * @return 1018 * TRUE if the URL is in a valid format. 1019 */ 1020 function valid_url($url, $absolute = FALSE) { 1021 if ($absolute) { 1022 return (bool)preg_match(" 1023 /^ # Start at the beginning of the text 1024 (?:ftp|https?|feed):\/\/ # Look for ftp, http, https or feed schemes 1025 (?: # Userinfo (optional) which is typically 1026 (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password 1027 (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination 1028 )? 1029 (?: 1030 (?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address 1031 |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address 1032 ) 1033 (?::[0-9]+)? # Server port number (optional) 1034 (?:[\/|\?] 1035 (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional) 1036 *)? 1037 $/xi", $url); 1038 } 1039 else { 1040 return (bool)preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url); 1041 } 1042 } 1043 1044 1045 /** 1046 * @} End of "defgroup validation". 1047 */ 1048 1049 /** 1050 * Register an event for the current visitor (hostname/IP) to the flood control mechanism. 1051 * 1052 * @param $name 1053 * The name of an event. 1054 */ 1055 function flood_register_event($name) { 1056 db_query("INSERT INTO {flood} (event, hostname, timestamp) VALUES ('%s', '%s', %d)", $name, ip_address(), time()); 1057 } 1058 1059 /** 1060 * Check if the current visitor (hostname/IP) is allowed to proceed with the specified event. 1061 * 1062 * The user is allowed to proceed if he did not trigger the specified event more 1063 * than $threshold times per hour. 1064 * 1065 * @param $name 1066 * The name of the event. 1067 * @param $threshold 1068 * The maximum number of the specified event per hour (per visitor). 1069 * @return 1070 * True if the user did not exceed the hourly threshold. False otherwise. 1071 */ 1072 function flood_is_allowed($name, $threshold) { 1073 $number = db_result(db_query("SELECT COUNT(*) FROM {flood} WHERE event = '%s' AND hostname = '%s' AND timestamp > %d", $name, ip_address(), time() - 3600)); 1074 return ($number < $threshold ? TRUE : FALSE); 1075 } 1076 1077 function check_file($filename) { 1078 return is_uploaded_file($filename); 1079 } 1080 1081 /** 1082 * Prepare a URL for use in an HTML attribute. Strips harmful protocols. 1083 */ 1084 function check_url($uri) { 1085 return filter_xss_bad_protocol($uri, FALSE); 1086 } 1087 1088 /** 1089 * @defgroup format Formatting 1090 * @{ 1091 * Functions to format numbers, strings, dates, etc. 1092 */ 1093 1094 /** 1095 * Formats an RSS channel. 1096 * 1097 * Arbitrary elements may be added using the $args associative array. 1098 */ 1099 function format_rss_channel($title, $link, $description, $items, $langcode = NULL, $args = array()) { 1100 global $language; 1101 $langcode = $langcode ? $langcode : $language->language; 1102 1103 $output = "<channel>\n"; 1104 $output .= ' <title>'. check_plain($title) ."</title>\n"; 1105 $output .= ' <link>'. check_url($link) ."</link>\n"; 1106 1107 // The RSS 2.0 "spec" doesn't indicate HTML can be used in the description. 1108 // We strip all HTML tags, but need to prevent double encoding from properly 1109 // escaped source data (such as & becoming &amp;). 1110 $output .= ' <description>'. check_plain(decode_entities(strip_tags($description))) ."</description>\n"; 1111 $output .= ' <language>'. check_plain($langcode) ."</language>\n"; 1112 $output .= format_xml_elements($args); 1113 $output .= $items; 1114 $output .= "</channel>\n"; 1115 1116 return $output; 1117 } 1118 1119 /** 1120 * Format a single RSS item. 1121 * 1122 * Arbitrary elements may be added using the $args associative array. 1123 */ 1124 function format_rss_item($title, $link, $description, $args = array()) { 1125 $output = "<item>\n"; 1126 $output .= ' <title>'. check_plain($title) ."</title>\n"; 1127 $output .= ' <link>'. check_url($link) ."</link>\n"; 1128 $output .= ' <description>'. check_plain($description) ."</description>\n"; 1129 $output .= format_xml_elements($args); 1130 $output .= "</item>\n"; 1131 1132 return $output; 1133 } 1134 1135 /** 1136 * Format XML elements. 1137 * 1138 * @param $array 1139 * An array where each item represent an element and is either a: 1140 * - (key => value) pair (<key>value</key>) 1141 * - Associative array with fields: 1142 * - 'key': element name 1143 * - 'value': element contents 1144 * - 'attributes': associative array of element attributes 1145 * 1146 * In both cases, 'value' can be a simple string, or it can be another array 1147 * with the same format as $array itself for nesting. 1148 */ 1149 function format_xml_elements($array) { 1150 $output = ''; 1151 foreach ($array as $key => $value) { 1152 if (is_numeric($key)) { 1153 if ($value['key']) { 1154 $output .= ' <'. $value['key']; 1155 if (isset($value['attributes']) && is_array($value['attributes'])) { 1156 $output .= drupal_attributes($value['attributes']); 1157 } 1158 1159 if (isset($value['value']) && $value['value'] != '') { 1160 $output .= '>'. (is_array($value['value']) ? format_xml_elements($value['value']) : check_plain($value['value'])) .'</'. $value['key'] .">\n"; 1161 } 1162 else { 1163 $output .= " />\n"; 1164 } 1165 } 1166 } 1167 else { 1168 $output .= ' <'. $key .'>'. (is_array($value) ? format_xml_elements($value) : check_plain($value)) ."</$key>\n"; 1169 } 1170 } 1171 return $output; 1172 } 1173 1174 /** 1175 * Format a string containing a count of items. 1176 * 1177 * This function ensures that the string is pluralized correctly. Since t() is 1178 * called by this function, make sure not to pass already-localized strings to 1179 * it. 1180 * 1181 * For example: 1182 * @code 1183 * $output = format_plural($node->comment_count, '1 comment', '@count comments'); 1184 * @endcode 1185 * 1186 * Example with additional replacements: 1187 * @code 1188 * $output = format_plural($update_count, 1189 * 'Changed the content type of 1 post from %old-type to %new-type.', 1190 * 'Changed the content type of @count posts from %old-type to %new-type.', 1191 * array('%old-type' => $info->old_type, '%new-type' => $info->new_type))); 1192 * @endcode 1193 * 1194 * @param $count 1195 * The item count to display. 1196 * @param $singular 1197 * The string for the singular case. Please make sure it is clear this is 1198 * singular, to ease translation (e.g. use "1 new comment" instead of "1 new"). 1199 * Do not use @count in the singular string. 1200 * @param $plural 1201 * The string for the plural case. Please make sure it is clear this is plural, 1202 * to ease translation. Use @count in place of the item count, as in "@count 1203 * new comments". 1204 * @param $args 1205 * An associative array of replacements to make after translation. Incidences 1206 * of any key in this array are replaced with the corresponding value. 1207 * Based on the first character of the key, the value is escaped and/or themed: 1208 * - !variable: inserted as is 1209 * - @variable: escape plain text to HTML (check_plain) 1210 * - %variable: escape text and theme as a placeholder for user-submitted 1211 * content (check_plain + theme_placeholder) 1212 * Note that you do not need to include @count in this array. 1213 * This replacement is done automatically for the plural case. 1214 * @param $langcode 1215 * Optional language code to translate to a language other than 1216 * what is used to display the page. 1217 * @return 1218 * A translated string. 1219 */ 1220 function format_plural($count, $singular, $plural, $args = array(), $langcode = NULL) { 1221 $args['@count'] = $count; 1222 if ($count == 1) { 1223 return t($singular, $args, $langcode); 1224 } 1225 1226 // Get the plural index through the gettext formula. 1227 $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, $langcode) : -1; 1228 // Backwards compatibility. 1229 if ($index < 0) { 1230 return t($plural, $args, $langcode); 1231 } 1232 else { 1233 switch ($index) { 1234 case "0": 1235 return t($singular, $args, $langcode); 1236 case "1": 1237 return t($plural, $args, $langcode); 1238 default: 1239 unset($args['@count']); 1240 $args['@count['. $index .']'] = $count; 1241 return t(strtr($plural, array('@count' => '@count['. $index .']')), $args, $langcode); 1242 } 1243 } 1244 } 1245 1246 /** 1247 * Parse a given byte count. 1248 * 1249 * @param $size 1250 * A size expressed as a number of bytes with optional SI size and unit 1251 * suffix (e.g. 2, 3K, 5MB, 10G). 1252 * @return 1253 * An integer representation of the size. 1254 */ 1255 function parse_size($size) { 1256 $suffixes = array( 1257 '' => 1, 1258 'k' => 1024, 1259 'm' => 1048576, // 1024 * 1024 1260 'g' => 1073741824, // 1024 * 1024 * 1024 1261 ); 1262 if (preg_match('/([0-9]+)\s*(k|m|g)?(b?(ytes?)?)/i', $size, $match)) { 1263 return $match[1] * $suffixes[drupal_strtolower($match[2])]; 1264 } 1265 } 1266 1267 /** 1268 * Generate a string representation for the given byte count. 1269 * 1270 * @param $size 1271 * A size in bytes. 1272 * @param $langcode 1273 * Optional language code to translate to a language other than what is used 1274 * to display the page. 1275 * @return 1276 * A translated string representation of the size. 1277 */ 1278 function format_size($size, $langcode = NULL) { 1279 if ($size < 1024) { 1280 return format_plural($size, '1 byte', '@count bytes', array(), $langcode); 1281 } 1282 else { 1283 $size = round($size / 1024, 2); 1284 $suffix = t('KB', array(), $langcode); 1285 if ($size >= 1024) { 1286 $size = round($size / 1024, 2); 1287 $suffix = t('MB', array(), $langcode); 1288 } 1289 return t('@size @suffix', array('@size' => $size, '@suffix' => $suffix), $langcode); 1290 } 1291 } 1292 1293 /** 1294 * Format a time interval with the requested granularity. 1295 * 1296 * @param $timestamp 1297 * The length of the interval in seconds. 1298 * @param $granularity 1299 * How many different units to display in the string. 1300 * @param $langcode 1301 * Optional language code to translate to a language other than 1302 * what is used to display the page. 1303 * @return 1304 * A translated string representation of the interval. 1305 */ 1306 function format_interval($timestamp, $granularity = 2, $langcode = NULL) { 1307 $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); 1308 $output = ''; 1309 foreach ($units as $key => $value) { 1310 $key = explode('|', $key); 1311 if ($timestamp >= $value) { 1312 $output .= ($output ? ' ' : '') . format_plural(floor($timestamp / $value), $key[0], $key[1], array(), $langcode); 1313 $timestamp %= $value; 1314 $granularity--; 1315 } 1316 1317 if ($granularity == 0) { 1318 break; 1319 } 1320 } 1321 return $output ? $output : t('0 sec', array(), $langcode); 1322 } 1323 1324 /** 1325 * Format a date with the given configured format or a custom format string. 1326 * 1327 * Drupal allows administrators to select formatting strings for 'small', 1328 * 'medium' and 'large' date formats. This function can handle these formats, 1329 * as well as any custom format. 1330 * 1331 * @param $timestamp 1332 * The exact date to format, as a UNIX timestamp. 1333 * @param $type 1334 * The format to use. Can be "small", "medium" or "large" for the preconfigured 1335 * date formats. If "custom" is specified, then $format is required as well. 1336 * @param $format 1337 * A PHP date format string as required by date(). A backslash should be used 1338 * before a character to avoid interpreting the character as part of a date 1339 * format. 1340 * @param $timezone 1341 * Time zone offset in seconds; if omitted, the user's time zone is used. 1342 * @param $langcode 1343 * Optional language code to translate to a language other than what is used 1344 * to display the page. 1345 * @return 1346 * A translated date string in the requested format. 1347 */ 1348 function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) { 1349 if (!isset($timezone)) { 1350 global $user; 1351 if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) { 1352 $timezone = $user->timezone; 1353 } 1354 else { 1355 $timezone = variable_get('date_default_timezone', 0); 1356 } 1357 } 1358 1359 $timestamp += $timezone; 1360 1361 switch ($type) { 1362 case 'small': 1363 $format = variable_get('date_format_short', 'm/d/Y - H:i'); 1364 break; 1365 case 'large': 1366 $format = variable_get('date_format_long', 'l, F j, Y - H:i'); 1367 break; 1368 case 'custom': 1369 // No change to format. 1370 break; 1371 case 'medium': 1372 default: 1373 $format = variable_get('date_format_medium', 'D, m/d/Y - H:i'); 1374 } 1375 1376 $max = strlen($format); 1377 $date = ''; 1378 for ($i = 0; $i < $max; $i++) { 1379 $c = $format[$i]; 1380 if (strpos('AaDlM', $c) !== FALSE) { 1381 $date .= t(gmdate($c, $timestamp), array(), $langcode); 1382 } 1383 else if ($c == 'F') { 1384 // Special treatment for long month names: May is both an abbreviation 1385 // and a full month name in English, but other languages have 1386 // different abbreviations. 1387 $date .= trim(t('!long-month-name '. gmdate($c, $timestamp), array('!long-month-name' => ''), $langcode)); 1388 } 1389 else if (strpos('BdgGhHiIjLmnsStTUwWYyz', $c) !== FALSE) { 1390 $date .= gmdate($c, $timestamp); 1391 } 1392 else if ($c == 'r') { 1393 $date .= format_date($timestamp - $timezone, 'custom', 'D, d M Y H:i:s O', $timezone, $langcode); 1394 } 1395 else if ($c == 'O') { 1396 $date .= sprintf('%s%02d%02d', ($timezone < 0 ? '-' : '+'), abs($timezone / 3600), abs($timezone % 3600) / 60); 1397 } 1398 else if ($c == 'Z') { 1399 $date .= $timezone; 1400 } 1401 else if ($c == '\\') { 1402 $date .= $format[++$i]; 1403 } 1404 else { 1405 $date .= $c; 1406 } 1407 } 1408 1409 return $date; 1410 } 1411 1412 /** 1413 * @} End of "defgroup format". 1414 */ 1415 1416 /** 1417 * Generates an internal or external URL. 1418 * 1419 * When creating links in modules, consider whether l() could be a better 1420 * alternative than url(). 1421 * 1422 * @param $path 1423 * The internal path or external URL being linked to, such as "node/34" or 1424 * "http://example.com/foo". A few notes: 1425 * - If you provide a full URL, it will be considered an external URL. 1426 * - If you provide only the path (e.g. "node/34"), it will be 1427 * considered an internal link. In this case, it should be a system URL, 1428 * and it will be replaced with the alias, if one exists. Additional query 1429 * arguments for internal paths must be supplied in $options['query'], not 1430 * included in $path. 1431 * - If you provide an internal path and $options['alias'] is set to TRUE, the 1432 * path is assumed already to be the correct path alias, and the alias is 1433 * not looked up. 1434 * - The special string '<front>' generates a link to the site's base URL. 1435 * - If your external URL contains a query (e.g. http://example.com/foo?a=b), 1436 * then you can either URL encode the query keys and values yourself and 1437 * include them in $path, or use $options['query'] to let this function 1438 * URL encode them. 1439 * @param $options 1440 * An associative array of additional options, with the following elements: 1441 * - 'query': A URL-encoded query string to append to the link, or an array of 1442 * query key/value-pairs without any URL-encoding. 1443 * - 'fragment': A fragment identifier (named anchor) to append to the URL. 1444 * Do not include the leading '#' character. 1445 * - 'absolute' (default FALSE): Whether to force the output to be an absolute 1446 * link (beginning with http:). Useful for links that will be displayed 1447 * outside the site, such as in an RSS feed. 1448 * - 'alias' (default FALSE): Whether the given path is a URL alias already. 1449 * - 'external': Whether the given path is an external URL. 1450 * - 'language': An optional language object. Used to build the URL to link 1451 * to and look up the proper alias for the link. 1452 * - 'base_url': Only used internally, to modify the base URL when a language 1453 * dependent URL requires so. 1454 * - 'prefix': Only used internally, to modify the path when a language 1455 * dependent URL requires so. 1456 * 1457 * @return 1458 * A string containing a URL to the given path. 1459 */ 1460 function url($path = NULL, $options = array()) { 1461 // Merge in defaults. 1462 $options += array( 1463 'fragment' => '', 1464 'query' => '', 1465 'absolute' => FALSE, 1466 'alias' => FALSE, 1467 'prefix' => '' 1468 ); 1469 if (!isset($options['external'])) { 1470 // Return an external link if $path contains an allowed absolute URL. 1471 // Only call the slow filter_xss_bad_protocol if $path contains a ':' before 1472 // any / ? or #. 1473 $colonpos = strpos($path, ':'); 1474 $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path)); 1475 } 1476 1477 // May need language dependent rewriting if language.inc is present. 1478 if (function_exists('language_url_rewrite')) { 1479 language_url_rewrite($path, $options); 1480 } 1481 if ($options['fragment']) { 1482 $options['fragment'] = '#'. $options['fragment']; 1483 } 1484 if (is_array($options['query'])) { 1485 $options['query'] = drupal_query_string_encode($options['query']); 1486 } 1487 1488 if ($options['external']) { 1489 // Split off the fragment. 1490 if (strpos($path, '#') !== FALSE) { 1491 list($path, $old_fragment) = explode('#', $path, 2); 1492 if (isset($old_fragment) && !$options['fragment']) { 1493 $options['fragment'] = '#'. $old_fragment; 1494 } 1495 } 1496 // Append the query. 1497 if ($options['query']) { 1498 $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . $options['query']; 1499 } 1500 // Reassemble. 1501 return $path . $options['fragment']; 1502 } 1503 1504 global $base_url; 1505 static $script; 1506 1507 if (!isset($script)) { 1508 // On some web servers, such as IIS, we can't omit "index.php". So, we 1509 // generate "index.php?q=foo" instead of "?q=foo" on anything that is not 1510 // Apache. 1511 $script = (strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') === FALSE) ? 'index.php' : ''; 1512 } 1513 1514 if (!isset($options['base_url'])) { 1515 // The base_url might be rewritten from the language rewrite in domain mode. 1516 $options['base_url'] = $base_url; 1517 } 1518 1519 // Preserve the original path before aliasing. 1520 $original_path = $path; 1521 1522 // The special path '<front>' links to the default front page. 1523 if ($path == '<front>') { 1524 $path = ''; 1525 } 1526 elseif (!empty($path) && !$options['alias']) { 1527 $path = drupal_get_path_alias($path, isset($options['language']) ? $options['language']->language : ''); 1528 } 1529 1530 if (function_exists('custom_url_rewrite_outbound')) { 1531 // Modules may alter outbound links by reference. 1532 custom_url_rewrite_outbound($path, $options, $original_path); 1533 } 1534 1535 $base = $options['absolute'] ? $options['base_url'] .'/' : base_path(); 1536 $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix']; 1537 $path = drupal_urlencode($prefix . $path); 1538 1539 if (variable_get('clean_url', '0')) { 1540 // With Clean URLs. 1541 if ($options['query']) { 1542 return $base . $path .'?'. $options['query'] . $options['fragment']; 1543 } 1544 else { 1545 return $base . $path . $options['fragment']; 1546 } 1547 } 1548 else { 1549 // Without Clean URLs. 1550 $variables = array(); 1551 if (!empty($path)) { 1552 $variables[] = 'q='. $path; 1553 } 1554 if (!empty($options['query'])) { 1555 $variables[] = $options['query']; 1556 } 1557 if ($query = join('&', $variables)) { 1558 return $base . $script .'?'. $query . $options['fragment']; 1559 } 1560 else { 1561 return $base . $options['fragment']; 1562 } 1563 } 1564 } 1565 1566 /** 1567 * Format an attribute string to insert in a tag. 1568 * 1569 * @param $attributes 1570 * An associative array of HTML attributes. 1571 * @return 1572 * An HTML string ready for insertion in a tag. 1573 */ 1574 function drupal_attributes($attributes = array()) { 1575 if (is_array($attributes)) { 1576 $t = ''; 1577 foreach ($attributes as $key => $value) { 1578 $t .= " $key=".'"'. check_plain($value) .'"'; 1579 } 1580 return $t; 1581 } 1582 } 1583 1584 /** 1585 * Formats an internal or external URL link as an HTML anchor tag. 1586 * 1587 * This function correctly handles aliased paths, and adds an 'active' class 1588 * attribute to links that point to the current page (for theming), so all 1589 * internal links output by modules should be generated by this function if 1590 * possible. 1591 * 1592 * @param $text 1593 * The link text for the anchor tag. 1594 * @param $path 1595 * The internal path or external URL being linked to, such as "node/34" or 1596 * "http://example.com/foo". After the url() function is called to construct 1597 * the URL from $path and $options, the resulting URL is passed through 1598 * check_url() before it is inserted into the HTML anchor tag, to ensure 1599 * well-formed HTML. See url() for more information and notes. 1600 * @param $options 1601 * An associative array of additional options, with the following elements: 1602 * - 'attributes': An associative array of HTML attributes to apply to the 1603 * anchor tag. 1604 * - 'html' (default FALSE): Whether $text is HTML or just plain-text. For 1605 * example, to make an image tag into a link, this must be set to TRUE, or 1606 * you will see the escaped HTML image tag. 1607 * - 'language': An optional language object. If the path being linked to is 1608 * internal to the site, $options['language'] is used to look up the alias 1609 * for the URL, and to determine whether the link is "active", or pointing 1610 * to the current page (the language as well as the path must match).This 1611 * element is also used by url(). 1612 * - Additional $options elements used by the url() function. 1613 * 1614 * @return 1615 * An HTML string containing a link to the given path. 1616 */ 1617 function l($text, $path, $options = array()) { 1618 global $language; 1619 1620 // Merge in defaults. 1621 $options += array( 1622 'attributes' => array(), 1623 'html' => FALSE, 1624 ); 1625 1626 // Append active class. 1627 if (($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) && 1628 (empty($options['language']) || $options['language']->language == $language->language)) { 1629 if (isset($options['attributes']['class'])) { 1630 $options['attributes']['class'] .= ' active'; 1631 } 1632 else { 1633 $options['attributes']['class'] = 'active'; 1634 } 1635 } 1636 1637 // Remove all HTML and PHP tags from a tooltip. For best performance, we act only 1638 // if a quick strpos() pre-check gave a suspicion (because strip_tags() is expensive). 1639 if (isset($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE) { 1640 $options['attributes']['title'] = strip_tags($options['attributes']['title']); 1641 } 1642 1643 return '<a href="'. check_url(url($path, $options)) .'"'. drupal_attributes($options['attributes']) .'>'. ($options['html'] ? $text : check_plain($text)) .'</a>'; 1644 } 1645 1646 /** 1647 * Perform end-of-request tasks. 1648 * 1649 * This function sets the page cache if appropriate, and allows modules to 1650 * react to the closing of the page by calling hook_exit(). 1651 */ 1652 function drupal_page_footer() { 1653 if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) { 1654 page_set_cache(); 1655 } 1656 1657 module_invoke_all('exit'); 1658 } 1659 1660 /** 1661 * Form an associative array from a linear array. 1662 * 1663 * This function walks through the provided array and constructs an associative 1664 * array out of it. The keys of the resulting array will be the values of the 1665 * input array. The values will be the same as the keys unless a function is 1666 * specified, in which case the output of the function is used for the values 1667 * instead. 1668 * 1669 * @param $array 1670 * A linear array. 1671 * @param $function 1672 * A name of a function to apply to all values before output. 1673 * 1674 * @return 1675 * An associative array. 1676 */ 1677 function drupal_map_assoc($array, $function = NULL) { 1678 if (!isset($function)) { 1679 $result = array(); 1680 foreach ($array as $value) { 1681 $result[$value] = $value; 1682 } 1683 return $result; 1684 } 1685 elseif (function_exists($function)) { 1686 $result = array(); 1687 foreach ($array as $value) { 1688 $result[$value] = $function($value); 1689 } 1690 return $result; 1691 } 1692 } 1693 1694 /** 1695 * Evaluate a string of PHP code. 1696 * 1697 * This is a wrapper around PHP's eval(). It uses output buffering to capture both 1698 * returned and printed text. Unlike eval(), we require code to be surrounded by 1699 * <?php ?> tags; in other words, we evaluate the code as if it were a stand-alone 1700 * PHP file. 1701 * 1702 * Using this wrapper also ensures that the PHP code which is evaluated can not 1703 * overwrite any variables in the calling code, unlike a regular eval() call. 1704 * 1705 * @param $code 1706 * The code to evaluate. 1707 * @return 1708 * A string containing the printed output of the code, followed by the returned 1709 * output of the code. 1710 */ 1711 function drupal_eval($code) { 1712 global $theme_path, $theme_info, $conf; 1713 1714 // Store current theme path. 1715 $old_theme_path = $theme_path; 1716 1717 // Restore theme_path to the theme, as long as drupal_eval() executes, 1718 // so code evaluted will not see the caller module as the current theme. 1719 // If theme info is not initialized get the path from theme_default. 1720 if (!isset($theme_info)) { 1721 $theme_path = drupal_get_path('theme', $conf['theme_default']); 1722 } 1723 else { 1724 $theme_path = dirname($theme_info->filename); 1725 } 1726 1727 ob_start(); 1728 print eval('?>'. $code); 1729 $output = ob_get_contents(); 1730 ob_end_clean(); 1731 1732 // Recover original theme path. 1733 $theme_path = $old_theme_path; 1734 1735 return $output; 1736 } 1737 1738 /** 1739 * Returns the path to a system item (module, theme, etc.). 1740 * 1741 * @param $type 1742 * The type of the item (i.e. theme, theme_engine, module, profile). 1743 * @param $name 1744 * The name of the item for which the path is requested. 1745 * 1746 * @return 1747 * The path to the requested item. 1748 */ 1749 function drupal_get_path($type, $name) { 1750 return dirname(drupal_get_filename($type, $name)); 1751 } 1752 1753 /** 1754 * Returns the base URL path of the Drupal installation. 1755 * At the very least, this will always default to /. 1756 */ 1757 function base_path() { 1758 return $GLOBALS['base_path']; 1759 } 1760 1761 /** 1762 * Provide a substitute clone() function for PHP4. 1763 */ 1764 function drupal_clone($object) { 1765 return version_compare(phpversion(), '5.0') < 0 ? $object : clone($object); 1766 } 1767 1768 /** 1769 * Add a <link> tag to the page's HEAD. 1770 */ 1771 function drupal_add_link($attributes) { 1772 drupal_set_html_head('<link'. drupal_attributes($attributes) .' />'); 1773 } 1774 1775 /** 1776 * Adds a CSS file to the stylesheet queue. 1777 * 1778 * @param $path 1779 * (optional) The path to the CSS file relative to the base_path(), e.g., 1780 * modules/devel/devel.css. 1781 * 1782 * Modules should always prefix the names of their CSS files with the module 1783 * name, for example: system-menus.css rather than simply menus.css. Themes 1784 * can override module-supplied CSS files based on their filenames, and this 1785 * prefixing helps prevent confusing name collisions for theme developers. 1786 * See drupal_get_css where the overrides are performed. 1787 * 1788 * If the direction of the current language is right-to-left (Hebrew, 1789 * Arabic, etc.), the function will also look for an RTL CSS file and append 1790 * it to the list. The name of this file should have an '-rtl.css' suffix. 1791 * For example a CSS file called 'name.css' will have a 'name-rtl.css' 1792 * file added to the list, if exists in the same directory. This CSS file 1793 * should contain overrides for properties which should be reversed or 1794 * otherwise different in a right-to-left display. 1795 * @param $type 1796 * (optional) The type of stylesheet that is being added. Types are: module 1797 * or theme. 1798 * @param $media 1799 * (optional) The media type for the stylesheet, e.g., all, print, screen. 1800 * @param $preprocess 1801 * (optional) Should this CSS file be aggregated and compressed if this 1802 * feature has been turned on under the performance section? 1803 * 1804 * What does this actually mean? 1805 * CSS preprocessing is the process of aggregating a bunch of separate CSS 1806 * files into one file that is then compressed by removing all extraneous 1807 * white space. 1808 * 1809 * The reason for merging the CSS files is outlined quite thoroughly here: 1810 * http://www.die.net/musings/page_load_time/ 1811 * "Load fewer external objects. Due to request overhead, one bigger file 1812 * just loads faster than two smaller ones half its size." 1813 * 1814 * However, you should *not* preprocess every file as this can lead to 1815 * redundant caches. You should set $preprocess = FALSE when: 1816 * 1817 * - Your styles are only used rarely on the site. This could be a special 1818 * admin page, the homepage, or a handful of pages that does not represent 1819 * the majority of the pages on your site. 1820 * 1821 * Typical candidates for caching are for example styles for nodes across 1822 * the site, or used in the theme. 1823 * 1824 * @return 1825 * An array of CSS files. 1826 * 1827 * @see drupal_get_css() 1828 */ 1829 function drupal_add_css($path = NULL, $type = 'module', $media = 'all', $preprocess = TRUE) { 1830 static $css = array(); 1831 global $language; 1832 1833 // Create an array of CSS files for each media type first, since each type needs to be served 1834 // to the browser differently. 1835 if (isset($path)) { 1836 // This check is necessary to ensure proper cascading of styles and is faster than an asort(). 1837 if (!isset($css[$media])) { 1838 $css[$media] = array('module' => array(), 'theme' => array()); 1839 } 1840 $css[$media][$type][$path] = $preprocess; 1841 1842 // If the current language is RTL, add the CSS file with RTL overrides. 1843 if ($language->direction == LANGUAGE_RTL) { 1844 $rtl_path = str_replace('.css', '-rtl.css', $path); 1845 if (file_exists($rtl_path)) { 1846 $css[$media][$type][$rtl_path] = $preprocess; 1847 } 1848 } 1849 } 1850 1851 return $css; 1852 } 1853 1854 /** 1855 * Returns a themed representation of all stylesheets that should be attached to the page. 1856 * 1857 * It loads the CSS in order, with 'module' first, then 'theme' afterwards. 1858 * This ensures proper cascading of styles so themes can easily override 1859 * module styles through CSS selectors. 1860 * 1861 * Themes may replace module-defined CSS files by adding a stylesheet with the 1862 * same filename. For example, themes/garland/system-menus.css would replace 1863 * modules/system/system-menus.css. This allows themes to override complete 1864 * CSS files, rather than specific selectors, when necessary. 1865 * 1866 * If the original CSS file is being overridden by a theme, the theme is 1867 * responsible for supplying an accompanying RTL CSS file to replace the 1868 * module's. 1869 * 1870 * @param $css 1871 * (optional) An array of CSS files. If no array is provided, the default 1872 * stylesheets array is used instead. 1873 * 1874 * @return 1875 * A string of XHTML CSS tags. 1876 * 1877 * @see drupal_add_css() 1878 */ 1879 function drupal_get_css($css = NULL) { 1880 $output = ''; 1881 if (!isset($css)) { 1882 $css = drupal_add_css(); 1883 } 1884 $no_module_preprocess = ''; 1885 $no_theme_preprocess = ''; 1886 1887 $preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')); 1888 $directory = file_directory_path(); 1889 $is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC); 1890 1891 // A dummy query-string is added to filenames, to gain control over 1892 // browser-caching. The string changes on every update or full cache 1893 // flush, forcing browsers to load a new copy of the files, as the 1894 // URL changed. 1895 $query_string = '?'. substr(variable_get('css_js_query_string', '0'), 0, 1); 1896 1897 foreach ($css as $media => $types) { 1898 // If CSS preprocessing is off, we still need to output the styles. 1899 // Additionally, go through any remaining styles if CSS preprocessing is on and output the non-cached ones. 1900 foreach ($types as $type => $files) { 1901 if ($type == 'module') { 1902 // Setup theme overrides for module styles. 1903 $theme_styles = array(); 1904 foreach (array_keys($css[$media]['theme']) as $theme_style) { 1905 $theme_styles[] = basename($theme_style); 1906 } 1907 } 1908 foreach ($types[$type] as $file => $preprocess) { 1909 // If the theme supplies its own style using the name of the module style, skip its inclusion. 1910 // This includes any RTL styles associated with its main LTR counterpart. 1911 if ($type == 'module' && in_array(str_replace('-rtl.css', '.css', basename($file)), $theme_styles)) { 1912 // Unset the file to prevent its inclusion when CSS aggregation is enabled. 1913 unset($types[$type][$file]); 1914 continue; 1915 } 1916 // Only include the stylesheet if it exists. 1917 if (file_exists($file)) { 1918 if (!$preprocess || !($is_writable && $preprocess_css)) { 1919 // If a CSS file is not to be preprocessed and it's a module CSS file, it needs to *always* appear at the *top*, 1920 // regardless of whether preprocessing is on or off. 1921 if (!$preprocess && $type == 'module') { 1922 $no_module_preprocess .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file . $query_string .'" />'."\n"; 1923 } 1924 // If a CSS file is not to be preprocessed and it's a theme CSS file, it needs to *always* appear at the *bottom*, 1925 // regardless of whether preprocessing is on or off. 1926 else if (!$preprocess && $type == 'theme') { 1927 $no_theme_preprocess .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file . $query_string .'" />'."\n"; 1928 } 1929 else { 1930 $output .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file . $query_string .'" />'."\n"; 1931 } 1932 } 1933 } 1934 } 1935 } 1936 1937 if ($is_writable && $preprocess_css) { 1938 // Prefix filename to prevent blocking by firewalls which reject files 1939 // starting with "ad*". 1940 $filename = 'css_'. md5(serialize($types) . $query_string) .'.css'; 1941 $preprocess_file = drupal_build_css_cache($types, $filename); 1942 $output .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $preprocess_file .'" />'."\n"; 1943 } 1944 } 1945 1946 return $no_module_preprocess . $output . $no_theme_preprocess; 1947 } 1948 1949 /** 1950 * Aggregate and optimize CSS files, putting them in the files directory. 1951 * 1952 * @param $types 1953 * An array of types of CSS files (e.g., screen, print) to aggregate and 1954 * compress into one file. 1955 * @param $filename 1956 * The name of the aggregate CSS file. 1957 * @return 1958 * The name of the CSS file. 1959 */ 1960 function drupal_build_css_cache($types, $filename) { 1961 $data = ''; 1962 1963 // Create the css/ within the files folder. 1964 $csspath = file_create_path('css'); 1965 file_check_directory($csspath, FILE_CREATE_DIRECTORY); 1966 1967 if (!file_exists($csspath .'/'. $filename)) { 1968 // Build aggregate CSS file. 1969 foreach ($types as $type) { 1970 foreach ($type as $file => $cache) { 1971 if ($cache) { 1972 $contents = drupal_load_stylesheet($file, TRUE); 1973 // Return the path to where this CSS file originated from. 1974 $base = base_path() . dirname($file) .'/'; 1975 _drupal_build_css_path(NULL, $base); 1976 // Prefix all paths within this CSS file, ignoring external and absolute paths. 1977 $data .= preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', '_drupal_build_css_path', $contents); 1978 } 1979 } 1980 } 1981 1982 // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, 1983 // @import rules must proceed any other style, so we move those to the top. 1984 $regexp = '/@import[^;]+;/i'; 1985 preg_match_all($regexp, $data, $matches); 1986 $data = preg_replace($regexp, '', $data); 1987 $data = implode('', $matches[0]) . $data; 1988 1989 // Create the CSS file. 1990 file_save_data($data, $csspath .'/'. $filename, FILE_EXISTS_REPLACE); 1991 } 1992 return $csspath .'/'. $filename; 1993 } 1994 1995 /** 1996 * Helper function for drupal_build_css_cache(). 1997 * 1998 * This function will prefix all paths within a CSS file. 1999 */ 2000 function _drupal_build_css_path($matches, $base = NULL) { 2001 static $_base; 2002 // Store base path for preg_replace_callback. 2003 if (isset($base)) { 2004 $_base = $base; 2005 } 2006 2007 // Prefix with base and remove '../' segments where possible. 2008 $path = $_base . $matches[1]; 2009 $last = ''; 2010 while ($path != $last) { 2011 $last = $path; 2012 $path = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $path); 2013 } 2014 return 'url('. $path .')'; 2015 } 2016 2017 /** 2018 * Loads the stylesheet and resolves all @import commands. 2019 * 2020 * Loads a stylesheet and replaces @import commands with the contents of the 2021 * imported file. Use this instead of file_get_contents when processing 2022 * stylesheets. 2023 * 2024 * The returned contents are compressed removing white space and comments only 2025 * when CSS aggregation is enabled. This optimization will not apply for 2026 * color.module enabled themes with CSS aggregation turned off. 2027 * 2028 * @param $file 2029 * Name of the stylesheet to be processed. 2030 * @param $optimize 2031 * Defines if CSS contents should be compressed or not. 2032 * @return 2033 * Contents of the stylesheet including the imported stylesheets. 2034 */ 2035 function drupal_load_stylesheet($file, $optimize = NULL) { 2036 static $_optimize; 2037 // Store optimization parameter for preg_replace_callback with nested @import loops. 2038 if (isset($optimize)) { 2039 $_optimize = $optimize; 2040 } 2041 2042 $contents = ''; 2043 if (file_exists($file)) { 2044 // Load the local CSS stylesheet. 2045 $contents = file_get_contents($file); 2046 2047 // Change to the current stylesheet's directory. 2048 $cwd = getcwd(); 2049 chdir(dirname($file)); 2050 2051 // Replaces @import commands with the actual stylesheet content. 2052 // This happens recursively but omits external files. 2053 $contents = preg_replace_callback('/@import\s*(?:url\()?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\)?;/', '_drupal_load_stylesheet', $contents); 2054 // Remove multiple charset declarations for standards compliance (and fixing Safari problems). 2055 $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents); 2056 2057 if ($_optimize) { 2058 // Perform some safe CSS optimizations. 2059 // Regexp to match comment blocks. 2060 $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'; 2061 // Regexp to match double quoted strings. 2062 $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; 2063 // Regexp to match single quoted strings. 2064 $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"; 2065 $contents = preg_replace_callback( 2066 "<$double_quot|$single_quot|$comment>Ss", // Match all comment blocks along 2067 "_process_comment", // with double/single quoted strings 2068 $contents); // and feed them to _process_comment(). 2069 $contents = preg_replace( 2070 '<\s*([@{}:;,]|\)\s|\s\()\s*>S', // Remove whitespace around separators, 2071 '\1', $contents); // but keep space around parentheses. 2072 // End the file with a new line. 2073 $contents .= "\n"; 2074 } 2075 2076 // Change back directory. 2077 chdir($cwd); 2078 } 2079 2080 return $contents; 2081 } 2082 2083 /** 2084 * Process comment blocks. 2085 * 2086 * This is the callback function for the preg_replace_callback() 2087 * used in drupal_load_stylesheet_content(). Support for comment 2088 * hacks is implemented here. 2089 */ 2090 function _process_comment($matches) { 2091 static $keep_nextone = FALSE; 2092 2093 // Quoted string, keep it. 2094 if ($matches[0][0] == "'" || $matches[0][0] == '"') { 2095 return $matches[0]; 2096 } 2097 // End of IE-Mac hack, keep it. 2098 if ($keep_nextone) { 2099 $keep_nextone = FALSE; 2100 return $matches[0]; 2101 } 2102 switch (strrpos($matches[0], '\\')) { 2103 case FALSE : 2104 // No backslash, strip it. 2105 return ''; 2106 2107 case drupal_strlen($matches[0])-3 : 2108 // Ends with \*/ so is a multi line IE-Mac hack, keep the next one also. 2109 $keep_nextone = TRUE; 2110 return '/*_\*/'; 2111 2112 default : 2113 // Single line IE-Mac hack. 2114 return '/*\_*/'; 2115 } 2116 } 2117 2118 /** 2119 * Loads stylesheets recursively and returns contents with corrected paths. 2120 * 2121 * This function is used for recursive loading of stylesheets and 2122 * returns the stylesheet content with all url() paths corrected. 2123 */ 2124 function _drupal_load_stylesheet($matches) { 2125 $filename = $matches[1]; 2126 // Load the imported stylesheet and replace @import commands in there as well. 2127 $file = drupal_load_stylesheet($filename); 2128 // Determine the file's directory. 2129 $directory = dirname($filename); 2130 // If the file is in the current directory, make sure '.' doesn't appear in 2131 // the url() path. 2132 $directory = $directory == '.' ? '' : $directory .'/'; 2133 2134 // Alter all internal url() paths. Leave external paths alone. We don't need 2135 // to normalize absolute paths here (i.e. remove folder/... segments) because 2136 // that will be done later. 2137 return preg_replace('/url\s*\(([\'"]?)(?![a-z]+:|\/+)/i', 'url(\1'. $directory, $file); 2138 } 2139 2140 /** 2141 * Delete all cached CSS files. 2142 */ 2143 function drupal_clear_css_cache() { 2144 file_scan_directory(file_create_path('css'), '.*', array('.', '..', 'CVS'), 'file_delete', TRUE); 2145 } 2146 2147 /** 2148 * Add a JavaScript file, setting or inline code to the page. 2149 * 2150 * The behavior of this function depends on the parameters it is called with. 2151 * Generally, it handles the addition of JavaScript to the page, either as 2152 * reference to an existing file or as inline code. The following actions can be 2153 * performed using this function: 2154 * 2155 * - Add a file ('core', 'module' and 'theme'): 2156 * Adds a reference to a JavaScript file to the page. JavaScript files 2157 * are placed in a certain order, from 'core' first, to 'module' and finally 2158 * 'theme' so that files, that are added later, can override previously added 2159 * files with ease. 2160 * 2161 * - Add inline JavaScript code ('inline'): 2162 * Executes a piece of JavaScript code on the current page by placing the code 2163 * directly in the page. This can, for example, be useful to tell the user that 2164 * a new message arrived, by opening a pop up, alert box etc. 2165 * 2166 * - Add settings ('setting'): 2167 * Adds a setting to Drupal's global storage of JavaScript settings. Per-page 2168 * settings are required by some modules to function properly. The settings 2169 * will be accessible at Drupal.settings. 2170 * 2171 * @param $data 2172 * (optional) If given, the value depends on the $type parameter: 2173 * - 'core', 'module' or 'theme': Path to the file relative to base_path(). 2174 * - 'inline': The JavaScript code that should be placed in the given scope. 2175 * - 'setting': An array with configuration options as associative array. The 2176 * array is directly placed in Drupal.settings. You might want to wrap your 2177 * actual configuration settings in another variable to prevent the pollution 2178 * of the Drupal.settings namespace. 2179 * @param $type 2180 * (optional) The type of JavaScript that should be added to the page. Allowed 2181 * values are 'core', 'module', 'theme', 'inline' and 'setting'. You 2182 * can, however, specify any value. It is treated as a reference to a JavaScript 2183 * file. Defaults to 'module'. 2184 * @param $scope 2185 * (optional) The location in which you want to place the script. Possible 2186 * values are 'header' and 'footer' by default. If your theme implements 2187 * different locations, however, you can also use these. 2188 * @param $defer 2189 * (optional) If set to TRUE, the defer attribute is set on the <script> tag. 2190 * Defaults to FALSE. This parameter is not used with $type == 'setting'. 2191 * @param $cache 2192 * (optional) If set to FALSE, the JavaScript file is loaded anew on every page 2193 * call, that means, it is not cached. Defaults to TRUE. Used only when $type 2194 * references a JavaScript file. 2195 * @param $preprocess 2196 * (optional) Should this JS file be aggregated if this 2197 * feature has been turned on under the performance section? 2198 * @return 2199 * If the first parameter is NULL, the JavaScript array that has been built so 2200 * far for $scope is returned. If the first three parameters are NULL, 2201 * an array with all scopes is returned. 2202 */ 2203 function drupal_add_js($data = NULL, $type = 'module', $scope = 'header', $defer = FALSE, $cache = TRUE, $preprocess = TRUE) { 2204 static $javascript = array(); 2205 2206 if (isset($data)) { 2207 2208 // Add jquery.js and drupal.js, as well as the basePath setting, the 2209 // first time a Javascript file is added. 2210 if (empty($javascript)) { 2211 $javascript['header'] = array( 2212 'core' => array( 2213 'misc/jquery.js' => array('cache' => TRUE, 'defer' => FALSE, 'preprocess' => TRUE), 2214 'misc/drupal.js' => array('cache' => TRUE, 'defer' => FALSE, 'preprocess' => TRUE), 2215 ), 2216 'module' => array(), 2217 'theme' => array(), 2218 'setting' => array( 2219 array('basePath' => base_path()), 2220 ), 2221 'inline' => array(), 2222 ); 2223 } 2224 2225 if (isset($scope) && !isset($javascript[$scope])) { 2226 $javascript[$scope] = array('core' => array(), 'module' => array(), 'theme' => array(), 'setting' => array(), 'inline' => array()); 2227 } 2228 2229 if (isset($type) && isset($scope) && !isset($javascript[$scope][$type])) { 2230 $javascript[$scope][$type] = array(); 2231 } 2232 2233 switch ($type) { 2234 case 'setting': 2235 $javascript[$scope][$type][] = $data; 2236 break; 2237 case 'inline': 2238 $javascript[$scope][$type][] = array('code' => $data, 'defer' => $defer); 2239 break; 2240 default: 2241 // If cache is FALSE, don't preprocess the JS file. 2242 $javascript[$scope][$type][$data] = array('cache' => $cache, 'defer' => $defer, 'preprocess' => (!$cache ? FALSE : $preprocess)); 2243 } 2244 } 2245 2246 if (isset($scope)) { 2247 2248 if (isset($javascript[$scope])) { 2249 return $javascript[$scope]; 2250 } 2251 else { 2252 return array(); 2253 } 2254 } 2255 else { 2256 return $javascript; 2257 } 2258 } 2259 2260 /** 2261 * Returns a themed presentation of all JavaScript code for the current page. 2262 * 2263 * References to JavaScript files are placed in a certain order: first, all 2264 * 'core' files, then all 'module' and finally all 'theme' JavaScript files 2265 * are added to the page. Then, all settings are output, followed by 'inline' 2266 * JavaScript code. If running update.php, all preprocessing is disabled. 2267 * 2268 * @param $scope 2269 * (optional) The scope for which the JavaScript rules should be returned. 2270 * Defaults to 'header'. 2271 * @param $javascript 2272 * (optional) An array with all JavaScript code. Defaults to the default 2273 * JavaScript array for the given scope. 2274 * @return 2275 * All JavaScript code segments and includes for the scope as HTML tags. 2276 */ 2277 function drupal_get_js($scope = 'header', $javascript = NULL) { 2278 if ((!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') && function_exists('locale_update_js_files')) { 2279 locale_update_js_files(); 2280 } 2281 2282 if (!isset($javascript)) { 2283 $javascript = drupal_add_js(NULL, NULL, $scope); 2284 } 2285 2286 if (empty($javascript)) { 2287 return ''; 2288 } 2289 2290 $output = ''; 2291 $preprocessed = ''; 2292 $no_preprocess = array('core' => '', 'module' => '', 'theme' => ''); 2293 $files = array(); 2294 $preprocess_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')); 2295 $directory = file_directory_path(); 2296 $is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC); 2297 2298 // A dummy query-string is added to filenames, to gain control over 2299 // browser-caching. The string changes on every update or full cache 2300 // flush, forcing browsers to load a new copy of the files, as the 2301 // URL changed. Files that should not be cached (see drupal_add_js()) 2302 // get time() as query-string instead, to enforce reload on every 2303 // page request. 2304 $query_string = '?'. substr(variable_get('css_js_query_string', '0'), 0, 1); 2305 2306 // For inline Javascript to validate as XHTML, all Javascript containing 2307 // XHTML needs to be wrapped in CDATA. To make that backwards compatible 2308 // with HTML 4, we need to comment out the CDATA-tag. 2309 $embed_prefix = "\n<!--//--><![CDATA[//><!--\n"; 2310 $embed_suffix = "\n//--><!]]>\n"; 2311 2312 foreach ($javascript as $type => $data) { 2313 2314 if (!$data) continue; 2315 2316 switch ($type) { 2317 case 'setting': 2318 $output .= '<script type="text/javascript">' . $embed_prefix . 'jQuery.extend(Drupal.settings, ' . drupal_to_js(call_user_func_array('array_merge_recursive', $data)) . ");" . $embed_suffix . "</script>\n"; 2319 break; 2320 case 'inline': 2321 foreach ($data as $info) { 2322 $output .= '<script type="text/javascript"' . ($info['defer'] ? ' defer="defer"' : '') . '>' . $embed_prefix . $info['code'] . $embed_suffix . "</script>\n"; 2323 } 2324 break; 2325 default: 2326 // If JS preprocessing is off, we still need to output the scripts. 2327 // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones. 2328 foreach ($data as $path => $info) { 2329 if (!$info['preprocess'] || !$is_writable || !$preprocess_js) { 2330 $no_preprocess[$type] .= '<script type="text/javascript"'. ($info['defer'] ? ' defer="defer"' : '') .' src="'. base_path() . $path . ($info['cache'] ? $query_string : '?'. time()) ."\"></script>\n"; 2331 } 2332 else { 2333 $files[$path] = $info; 2334 } 2335 } 2336 } 2337 } 2338 2339 // Aggregate any remaining JS files that haven't already been output. 2340 if ($is_writable && $preprocess_js && count($files) > 0) { 2341 // Prefix filename to prevent blocking by firewalls which reject files 2342 // starting with "ad*". 2343 $filename = 'js_'. md5(serialize($files) . $query_string) .'.js'; 2344 $preprocess_file = drupal_build_js_cache($files, $filename); 2345 $preprocessed .= '<script type="text/javascript" src="'. base_path() . $preprocess_file .'"></script>'."\n"; 2346 } 2347 2348 // Keep the order of JS files consistent as some are preprocessed and others are not. 2349 // Make sure any inline or JS setting variables appear last after libraries have loaded. 2350 $output = $preprocessed . implode('', $no_preprocess) . $output; 2351 2352 return $output; 2353 } 2354 2355 /** 2356 * Assist in adding the tableDrag JavaScript behavior to a themed table. 2357 * 2358 * Draggable tables should be used wherever an outline or list of sortable items 2359 * needs to be arranged by an end-user. Draggable tables are very flexible and 2360 * can manipulate the value of form elements placed within individual columns. 2361 * 2362 * To set up a table to use drag and drop in place of weight select-lists or 2363 * in place of a form that contains parent relationships, the form must be 2364 * themed into a table. The table must have an id attribute set. If using 2365 * theme_table(), the id may be set as such: 2366 * @code 2367 * $output = theme('table', $header, $rows, array('id' => 'my-module-table')); 2368 * return $output; 2369 * @endcode 2370 * 2371 * In the theme function for the form, a special class must be added to each 2372 * form element within the same column, "grouping" them together. 2373 * 2374 * In a situation where a single weight column is being sorted in the table, the 2375 * classes could be added like this (in the theme function): 2376 * @code 2377 * $form['my_elements'][$delta]['weight']['#attributes']['class'] = "my-elements-weight"; 2378 * @endcode 2379 * 2380 * Each row of the table must also have a class of "draggable" in order to enable the 2381 * drag handles: 2382 * @code 2383 * $row = array(...); 2384 * $rows[] = array( 2385 * 'data' => $row, 2386 * 'class' => 'draggable', 2387 * ); 2388 * @endcode 2389 * 2390 * When tree relationships are present, the two additional classes 2391 * 'tabledrag-leaf' and 'tabledrag-root' can be used to refine the behavior: 2392 * - Rows with the 'tabledrag-leaf' class cannot have child rows. 2393 * - Rows with the 'tabledrag-root' class cannot be nested under a parent row. 2394 * 2395 * Calling drupal_add_tabledrag() would then be written as such: 2396 * @code 2397 * drupal_add_tabledrag('my-module-table', 'order', 'sibling', 'my-elements-weight'); 2398 * @endcode 2399 * 2400 * In a more complex case where there are several groups in one column (such as 2401 * the block regions on the admin/build/block page), a separate subgroup class 2402 * must also be added to differentiate the groups. 2403 * @code 2404 * $form['my_elements'][$region][$delta]['weight']['#attributes']['class'] = "my-elements-weight my-elements-weight-". $region; 2405 * @endcode 2406 * 2407 * $group is still 'my-element-weight', and the additional $subgroup variable 2408 * will be passed in as 'my-elements-weight-'. $region. This also means that 2409 * you'll need to call drupal_add_tabledrag() once for every region added. 2410 * 2411 * @code 2412 * foreach ($regions as $region) { 2413 * drupal_add_tabledrag('my-module-table', 'order', 'sibling', 'my-elements-weight', 'my-elements-weight-'. $region); 2414 * } 2415 * @endcode 2416 * 2417 * In a situation where tree relationships are present, adding multiple 2418 * subgroups is not necessary, because the table will contain indentations that 2419 * provide enough information about the sibling and parent relationships. 2420 * See theme_menu_overview_form() for an example creating a table containing 2421 * parent relationships. 2422 * 2423 * Please note that this function should be called from the theme layer, such as 2424 * in a .tpl.php file, theme_ function, or in a template_preprocess function, 2425 * not in a form declartion. Though the same JavaScript could be added to the 2426 * page using drupal_add_js() directly, this function helps keep template files 2427 * clean and readable. It also prevents tabledrag.js from being added twice 2428 * accidentally. 2429 * 2430 * @param $table_id 2431 * String containing the target table's id attribute. If the table does not 2432 * have an id, one will need to be set, such as <table id="my-module-table">. 2433 * @param $action 2434 * String describing the action to be done on the form item. Either 'match' 2435 * 'depth', or 'order'. Match is typically used for parent relationships. 2436 * Order is typically used to set weights on other form elements with the same 2437 * group. Depth updates the target element with the current indentation. 2438 * @param $relationship 2439 * String describing where the $action variable should be performed. Either 2440 * 'parent', 'sibling', 'group', or 'self'. Parent will only look for fields 2441 * up the tree. Sibling will look for fields in the same group in rows above 2442 * and below it. Self affects the dragged row itself. Group affects the 2443 * dragged row, plus any children below it (the entire dragged group). 2444 * @param $group 2445 * A class name applied on all related form elements for this action. 2446 * @param $subgroup 2447 * (optional) If the group has several subgroups within it, this string should 2448 * contain the class name identifying fields in the same subgroup. 2449 * @param $source 2450 * (optional) If the $action is 'match', this string should contain the class 2451 * name identifying what field will be used as the source value when matching 2452 * the value in $subgroup. 2453 * @param $hidden 2454 * (optional) The column containing the field elements may be entirely hidden 2455 * from view dynamically when the JavaScript is loaded. Set to FALSE if the 2456 * column should not be hidden. 2457 * @param $limit 2458 * (optional) Limit the maximum amount of parenting in this table. 2459 * @see block-admin-display-form.tpl.php 2460 * @see theme_menu_overview_form() 2461 */ 2462 function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgroup = NULL, $source = NULL, $hidden = TRUE, $limit = 0) { 2463 static $js_added = FALSE; 2464 if (!$js_added) { 2465 drupal_add_js('misc/tabledrag.js', 'core'); 2466 $js_added = TRUE; 2467 } 2468 2469 // If a subgroup or source isn't set, assume it is the same as the group. 2470 $target = isset($subgroup) ? $subgroup : $group; 2471 $source = isset($source) ? $source : $target; 2472 $settings['tableDrag'][$table_id][$group][] = array( 2473 'target' => $target, 2474 'source' => $source, 2475 'relationship' => $relationship, 2476 'action' => $action, 2477 'hidden' => $hidden, 2478 'limit' => $limit, 2479 ); 2480 drupal_add_js($settings, 'setting'); 2481 } 2482 2483 /** 2484 * Aggregate JS files, putting them in the files directory. 2485 * 2486 * @param $files 2487 * An array of JS files to aggregate and compress into one file. 2488 * @param $filename 2489 * The name of the aggregate JS file. 2490 * @return 2491 * The name of the JS file. 2492 */ 2493 function drupal_build_js_cache($files, $filename) { 2494 $contents = ''; 2495 2496 // Create the js/ within the files folder. 2497 $jspath = file_create_path('js'); 2498 file_check_directory($jspath, FILE_CREATE_DIRECTORY); 2499 2500 if (!file_exists($jspath .'/'. $filename)) { 2501 // Build aggregate JS file. 2502 foreach ($files as $path => $info) { 2503 if ($info['preprocess']) { 2504 // Append a ';' and a newline after each JS file to prevent them from running together. 2505 $contents .= file_get_contents($path) .";\n"; 2506 } 2507 } 2508 2509 // Create the JS file. 2510 file_save_data($contents, $jspath .'/'. $filename, FILE_EXISTS_REPLACE); 2511 } 2512 2513 return $jspath .'/'. $filename; 2514 } 2515 2516 /** 2517 * Delete all cached JS files. 2518 */ 2519 function drupal_clear_js_cache() { 2520 file_scan_directory(file_create_path('js'), '.*', array('.', '..', 'CVS'), 'file_delete', TRUE); 2521 variable_set('javascript_parsed', array()); 2522 } 2523 2524 /** 2525 * Converts a PHP variable into its Javascript equivalent. 2526 * 2527 * We use HTML-safe strings, i.e. with <, > and & escaped. 2528 */ 2529 function drupal_to_js($var) { 2530 switch (gettype($var)) { 2531 case 'boolean': 2532 return $var ? 'true' : 'false'; // Lowercase necessary! 2533 case 'integer': 2534 case 'double': 2535 return $var; 2536 case 'resource': 2537 case 'string': 2538 return '"'. str_replace(array("\r", "\n", "<", ">", "&"), 2539 array('\r', '\n', '\x3c', '\x3e', '\x26'), 2540 addslashes($var)) .'"'; 2541 case 'array': 2542 // Arrays in JSON can't be associative. If the array is empty or if it 2543 // has sequential whole number keys starting with 0, it's not associative 2544 // so we can go ahead and convert it as an array. 2545 if (empty ($var) || array_keys($var) === range(0, sizeof($var) - 1)) { 2546 $output = array(); 2547 foreach ($var as $v) { 2548 $output[] = drupal_to_js($v); 2549 } 2550 return '[ '. implode(', ', $output) .' ]'; 2551 } 2552 // Otherwise, fall through to convert the array as an object. 2553 case 'object': 2554 $output = array(); 2555 foreach ($var as $k => $v) { 2556 $output[] = drupal_to_js(strval($k)) .': '. drupal_to_js($v); 2557 } 2558 return '{ '. implode(', ', $output) .' }'; 2559 default: 2560 return 'null'; 2561 } 2562 } 2563 2564 /** 2565 * Return data in JSON format. 2566 * 2567 * This function should be used for JavaScript callback functions returning 2568 * data in JSON format. It sets the header for JavaScript output. 2569 * 2570 * @param $var 2571 * (optional) If set, the variable will be converted to JSON and output. 2572 */ 2573 function drupal_json($var = NULL) { 2574 // We are returning JavaScript, so tell the browser. 2575 drupal_set_header('Content-Type: text/javascript; charset=utf-8'); 2576 2577 if (isset($var)) { 2578 echo drupal_to_js($var); 2579 } 2580 } 2581 2582 /** 2583 * Wrapper around urlencode() which avoids Apache quirks. 2584 * 2585 * Should be used when placing arbitrary data in an URL. Note that Drupal paths 2586 * are urlencoded() when passed through url() and do not require urlencoding() 2587 * of individual components. 2588 * 2589 * Notes: 2590 * - For esthetic reasons, we do not escape slashes. This also avoids a 'feature' 2591 * in Apache where it 404s on any path containing '%2F'. 2592 * - mod_rewrite unescapes %-encoded ampersands, hashes, and slashes when clean 2593 * URLs are used, which are interpreted as delimiters by PHP. These 2594 * characters are double escaped so PHP will still see the encoded version. 2595 * - With clean URLs, Apache changes '//' to '/', so every second slash is 2596 * double escaped. 2597 * - This function should only be used on paths, not on query string arguments, 2598 * otherwise unwanted double encoding will occur. 2599 * 2600 * @param $text 2601 * String to encode 2602 */ 2603 function drupal_urlencode($text) { 2604 if (variable_get('clean_url', '0')) { 2605 return str_replace(array('%2F', '%26', '%23', '//'), 2606 array('/', '%2526', '%2523', '/%252F'), 2607 rawurlencode($text)); 2608 } 2609 else { 2610 return str_replace('%2F', '/', rawurlencode($text)); 2611 } 2612 } 2613 2614 /** 2615 * Ensure the private key variable used to generate tokens is set. 2616 * 2617 * @return 2618 * The private key. 2619 */ 2620 function drupal_get_private_key() { 2621 if (!($key = variable_get('drupal_private_key', 0))) { 2622 $key = md5(uniqid(mt_rand(), true)) . md5(uniqid(mt_rand(), true)); 2623 variable_set('drupal_private_key', $key); 2624 } 2625 return $key; 2626 } 2627 2628 /** 2629 * Generate a token based on $value, the current user session and private key. 2630 * 2631 * @param $value 2632 * An additional value to base the token on. 2633 */ 2634 function drupal_get_token($value = '') { 2635 $private_key = drupal_get_private_key(); 2636 return md5(session_id() . $value . $private_key); 2637 } 2638 2639 /** 2640 * Validate a token based on $value, the current user session and private key. 2641 * 2642 * @param $token 2643 * The token to be validated. 2644 * @param $value 2645 * An additional value to base the token on. 2646 * @param $skip_anonymous 2647 * Set to true to skip token validation for anonymous users. 2648 * @return 2649 * True for a valid token, false for an invalid token. When $skip_anonymous 2650 * is true, the return value will always be true for anonymous users. 2651 */ 2652 function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) { 2653 global $user; 2654 return (($skip_anonymous && $user->uid == 0) || ($token == md5(session_id() . $value . variable_get('drupal_private_key', '')))); 2655 } 2656 2657 /** 2658 * Performs one or more XML-RPC request(s). 2659 * 2660 * @param $url 2661 * An absolute URL of the XML-RPC endpoint. 2662 * Example: 2663 * http://www.example.com/xmlrpc.php 2664 * @param ... 2665 * For one request: 2666 * The method name followed by a variable number of arguments to the method. 2667 * For multiple requests (system.multicall): 2668 * An array of call arrays. Each call array follows the pattern of the single 2669 * request: method name followed by the arguments to the method. 2670 * @return 2671 * For one request: 2672 * Either the return value of the method on success, or FALSE. 2673 * If FALSE is returned, see xmlrpc_errno() and xmlrpc_error_msg(). 2674 * For multiple requests: 2675 * An array of results. Each result will either be the result 2676 * returned by the method called, or an xmlrpc_error object if the call 2677 * failed. See xmlrpc_error(). 2678 */ 2679 function xmlrpc($url) { 2680 require_once './includes/xmlrpc.inc'; 2681 $args = func_get_args(); 2682 return call_user_func_array('_xmlrpc', $args); 2683 } 2684 2685 function _drupal_bootstrap_full() { 2686 static $called; 2687 2688 if ($called) { 2689 return; 2690 } 2691 $called = 1; 2692 require_once './includes/theme.inc'; 2693 require_once './includes/pager.inc'; 2694 require_once './includes/menu.inc'; 2695 require_once './includes/tablesort.inc'; 2696 require_once './includes/file.inc'; 2697 require_once './includes/unicode.inc'; 2698 require_once './includes/image.inc'; 2699 require_once './includes/form.inc'; 2700 require_once './includes/mail.inc'; 2701 require_once './includes/actions.inc'; 2702 // Set the Drupal custom error handler. 2703 set_error_handler('drupal_error_handler'); 2704 // Emit the correct charset HTTP header. 2705 drupal_set_header('Content-Type: text/html; charset=utf-8'); 2706 // Detect string handling method 2707 unicode_check(); 2708 // Undo magic quotes 2709 fix_gpc_magic(); 2710 // Load all enabled modules 2711 module_load_all(); 2712 // Let all modules take action before menu system handles the request 2713 // We do not want this while running update.php. 2714 if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { 2715 module_invoke_all('init'); 2716 } 2717 } 2718 2719 /** 2720 * Store the current page in the cache. 2721 * 2722 * If page_compression is enabled, a gzipped version of the page is stored in 2723 * the cache to avoid compressing the output on each request. The cache entry 2724 * is unzipped in the relatively rare event that the page is requested by a 2725 * client without gzip support. 2726 * 2727 * Page compression requires the PHP zlib extension 2728 * (http://php.net/manual/en/ref.zlib.php). 2729 * 2730 * @see drupal_page_header 2731 */ 2732 function page_set_cache() { 2733 global $user, $base_root; 2734 2735 if (!$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET' && page_get_cache(TRUE)) { 2736 // This will fail in some cases, see page_get_cache() for the explanation. 2737 if ($data = ob_get_contents()) { 2738 if (variable_get('page_compression', TRUE) && extension_loaded('zlib')) { 2739 $data = gzencode($data, 9, FORCE_GZIP); 2740 } 2741 ob_end_flush(); 2742 cache_set($base_root . request_uri(), $data, 'cache_page', CACHE_TEMPORARY, drupal_get_headers()); 2743 } 2744 } 2745 } 2746 2747 /** 2748 * Executes a cron run when called 2749 * @return 2750 * Returns TRUE if ran successfully 2751 */ 2752 function drupal_cron_run() { 2753 // Try to allocate enough time to run all the hook_cron implementations. 2754 if (function_exists('set_time_limit')) { 2755 @set_time_limit(240); 2756 } 2757 2758 // Fetch the cron semaphore 2759 $semaphore = variable_get('cron_semaphore', FALSE); 2760 2761 if ($semaphore) { 2762 if (time() - $semaphore > 3600) { 2763 // Either cron has been running for more than an hour or the semaphore 2764 // was not reset due to a database error. 2765 watchdog('cron', 'Cron has been running for more than an hour and is most likely stuck.', array(), WATCHDOG_ERROR); 2766 2767 // Release cron semaphore 2768 variable_del('cron_semaphore'); 2769 } 2770 else { 2771 // Cron is still running normally. 2772 watchdog('cron', 'Attempting to re-run cron while it is already running.', array(), WATCHDOG_WARNING); 2773 } 2774 } 2775 else { 2776 // Register shutdown callback 2777 register_shutdown_function('drupal_cron_cleanup'); 2778 2779 // Lock cron semaphore 2780 variable_set('cron_semaphore', time()); 2781 2782 // Iterate through the modules calling their cron handlers (if any): 2783 module_invoke_all('cron'); 2784 2785 // Record cron time 2786 variable_set('cron_last', time()); 2787 watchdog('cron', 'Cron run completed.', array(), WATCHDOG_NOTICE); 2788 2789 // Release cron semaphore 2790 variable_del('cron_semaphore'); 2791 2792 // Return TRUE so other functions can check if it did run successfully 2793 return TRUE; 2794 } 2795 } 2796 2797 /** 2798 * Shutdown function for cron cleanup. 2799 */ 2800 function drupal_cron_cleanup() { 2801 // See if the semaphore is still locked. 2802 if (variable_get('cron_semaphore', FALSE)) { 2803 watchdog('cron', 'Cron run exceeded the time limit and was aborted.', array(), WATCHDOG_WARNING); 2804 2805 // Release cron semaphore 2806 variable_del('cron_semaphore'); 2807 } 2808 } 2809 2810 /** 2811 * Return an array of system file objects. 2812 * 2813 * Returns an array of file objects of the given type from the site-wide 2814 * directory (i.e. modules/), the all-sites directory (i.e. 2815 * sites/all/modules/), the profiles directory, and site-specific directory 2816 * (i.e. sites/somesite/modules/). The returned array will be keyed using the 2817 * key specified (name, basename, filename). Using name or basename will cause 2818 * site-specific files to be prioritized over similar files in the default 2819 * directories. That is, if a file with the same name appears in both the 2820 * site-wide directory and site-specific directory, only the site-specific 2821 * version will be included. 2822 * 2823 * @param $mask 2824 * The regular expression of the files to find. 2825 * @param $directory 2826 * The subdirectory name in which the files are found. For example, 2827 * 'modules' will search in both modules/ and 2828 * sites/somesite/modules/. 2829 * @param $key 2830 * The key to be passed to file_scan_directory(). 2831 * @param $min_depth 2832 * Minimum depth of directories to return files from. 2833 * 2834 * @return 2835 * An array of file objects of the specified type. 2836 */ 2837 function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) { 2838 global $profile; 2839 $config = conf_path(); 2840 2841 // When this function is called during Drupal's initial installation process, 2842 // the name of the profile that's about to be installed is stored in the global 2843 // $profile variable. At all other times, the standard Drupal systems variable 2844 // table contains the name of the current profile, and we can call variable_get() 2845 // to determine what one is active. 2846 if (!isset($profile)) { 2847 $profile = variable_get('install_profile', 'default'); 2848 } 2849 $searchdir = array($directory); 2850 $files = array(); 2851 2852 // The 'profiles' directory contains pristine collections of modules and 2853 // themes as organized by a distribution. It is pristine in the same way 2854 // that /modules is pristine for core; users should avoid changing anything 2855 // there in favor of sites/all or sites/<domain> directories. 2856 if (file_exists("profiles/$profile/$directory")) { 2857 $searchdir[] = "profiles/$profile/$directory"; 2858 } 2859 2860 // Always search sites/all/* as well as the global directories 2861 $searchdir[] = 'sites/all/'. $directory; 2862 2863 if (file_exists("$config/$directory")) { 2864 $searchdir[] = "$config/$directory"; 2865 } 2866 2867 // Get current list of items 2868 foreach ($searchdir as $dir) { 2869 $files = array_merge($files, file_scan_directory($dir, $mask, array('.', '..', 'CVS'), 0, TRUE, $key, $min_depth)); 2870 } 2871 2872 return $files; 2873 } 2874 2875 2876 /** 2877 * Hands off alterable variables to type-specific *_alter implementations. 2878 * 2879 * This dispatch function hands off the passed in variables to type-specific 2880 * hook_TYPE_alter() implementations in modules. It ensures a consistent 2881 * interface for all altering operations. 2882 * 2883 * @param $type 2884 * A string describing the type of the alterable $data (e.g. 'form', 2885 * 'profile'). 2886 * @param $data 2887 * The variable that will be passed to hook_TYPE_alter() implementations to 2888 * be altered. The type of this variable depends on $type. For example, when 2889 * altering a 'form', $data will be a structured array. When altering a 2890 * 'profile', $data will be an object. If you need to pass additional 2891 * parameters by reference to the hook_TYPE_alter() functions, include them 2892 * as an array in $data['__drupal_alter_by_ref']. They will be unpacked and 2893 * passed to the hook_TYPE_alter() functions, before the additional 2894 * ... parameters (see below). 2895 * @param ... 2896 * Any additional parameters will be passed on to the hook_TYPE_alter() 2897 * functions (not by reference), after any by-reference parameters included 2898 * in $data (see above) 2899 */ 2900 function drupal_alter($type, &$data) { 2901 // PHP's func_get_args() always returns copies of params, not references, so 2902 // drupal_alter() can only manipulate data that comes in via the required first 2903 // param. For the edge case functions that must pass in an arbitrary number of 2904 // alterable parameters (hook_form_alter() being the best example), an array of 2905 // those params can be placed in the __drupal_alter_by_ref key of the $data 2906 // array. This is somewhat ugly, but is an unavoidable consequence of a flexible 2907 // drupal_alter() function, and the limitations of func_get_args(). 2908 // @todo: Remove this in Drupal 7. 2909 if (is_array($data) && isset($data['__drupal_alter_by_ref'])) { 2910 $by_ref_parameters = $data['__drupal_alter_by_ref']; 2911 unset($data['__drupal_alter_by_ref']); 2912 } 2913 2914 // Hang onto a reference to the data array so that it isn't blown away later. 2915 // Also, merge in any parameters that need to be passed by reference. 2916 $args = array(&$data); 2917 if (isset($by_ref_parameters)) { 2918 $args = array_merge($args, $by_ref_parameters); 2919 } 2920 2921 // Now, use func_get_args() to pull in any additional parameters passed into 2922 // the drupal_alter() call. 2923 $additional_args = func_get_args(); 2924 array_shift($additional_args); 2925 array_shift($additional_args); 2926 $args = array_merge($args, $additional_args); 2927 2928 foreach (module_implements($type .'_alter') as $module) { 2929 $function = $module .'_'. $type .'_alter'; 2930 call_user_func_array($function, $args); 2931 } 2932 } 2933 2934 2935 /** 2936 * Renders HTML given a structured array tree. 2937 * 2938 * Recursively iterates over each of the array elements, generating HTML code. 2939 * This function is usually called from within another function, like 2940 * drupal_get_form() or node_view(). 2941 * 2942 * drupal_render() flags each element with a '#printed' status to indicate that 2943 * the element has been rendered, which allows individual elements of a given 2944 * array to be rendered independently. This prevents elements from being 2945 * rendered more than once on subsequent calls to drupal_render() if, for example, 2946 * they are part of a larger array. If the same array or array element is passed 2947 * more than once to drupal_render(), it simply returns a NULL value. 2948 * 2949 * @param $elements 2950 * The structured array describing the data to be rendered. 2951 * @return 2952 * The rendered HTML. 2953 */ 2954 function drupal_render(&$elements) { 2955 if (!isset($elements) || (isset($elements['#access']) && !$elements['#access'])) { 2956 return NULL; 2957 } 2958 2959 // If the default values for this element haven't been loaded yet, populate 2960 // them. 2961 if (!isset($elements['#defaults_loaded']) || !$elements['#defaults_loaded']) { 2962 if ((!empty($elements['#type'])) && ($info = _element_info($elements['#type']))) { 2963 $elements += $info; 2964 } 2965 } 2966 2967 // Make any final changes to the element before it is rendered. This means 2968 // that the $element or the children can be altered or corrected before the 2969 // element is rendered into the final text. 2970 if (isset($elements['#pre_render'])) { 2971 foreach ($elements['#pre_render'] as $function) { 2972 if (function_exists($function)) { 2973 $elements = $function($elements); 2974 } 2975 } 2976 } 2977 2978 $content = ''; 2979 // Either the elements did not go through form_builder or one of the children 2980 // has a #weight. 2981 if (!isset($elements['#sorted'])) { 2982 uasort($elements, "element_sort"); 2983 } 2984 $elements += array('#title' => NULL, '#description' => NULL); 2985 if (!isset($elements['#children'])) { 2986 $children = element_children($elements); 2987 // Render all the children that use a theme function. 2988 if (isset($elements['#theme']) && empty($elements['#theme_used'])) { 2989 $elements['#theme_used'] = TRUE; 2990 2991 $previous = array(); 2992 foreach (array('#value', '#type', '#prefix', '#suffix') as $key) { 2993 $previous[$key] = isset($elements[$key]) ? $elements[$key] : NULL; 2994 } 2995 // If we rendered a single element, then we will skip the renderer. 2996 if (empty($children)) { 2997 $elements['#printed'] = TRUE; 2998 } 2999 else { 3000 $elements['#value'] = ''; 3001 } 3002 $elements['#type'] = 'markup'; 3003 3004 unset($elements['#prefix'], $elements['#suffix']); 3005 $content = theme($elements['#theme'], $elements); 3006 3007 foreach (array('#value', '#type', '#prefix', '#suffix') as $key) { 3008 $elements[$key] = isset($previous[$key]) ? $previous[$key] : NULL; 3009 } 3010 } 3011 // Render each of the children using drupal_render and concatenate them. 3012 if (!isset($content) || $content === '') { 3013 foreach ($children as $key) { 3014 $content .= drupal_render($elements[$key]); 3015 } 3016 } 3017 } 3018 if (isset($content) && $content !== '') { 3019 $elements['#children'] = $content; 3020 } 3021 3022 // Until now, we rendered the children, here we render the element itself 3023 if (!isset($elements['#printed'])) { 3024 $content = theme(!empty($elements['#type']) ? $elements['#type'] : 'markup', $elements); 3025 $elements['#printed'] = TRUE; 3026 } 3027 3028 if (isset($content) && $content !== '') { 3029 // Filter the outputted content and make any last changes before the 3030 // content is sent to the browser. The changes are made on $content 3031 // which allows the output'ed text to be filtered. 3032 if (isset($elements['#post_render'])) { 3033 foreach ($elements['#post_render'] as $function) { 3034 if (function_exists($function)) { 3035 $content = $function($content, $elements); 3036 } 3037 } 3038 } 3039 $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : ''; 3040 $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : ''; 3041 return $prefix . $content . $suffix; 3042 } 3043 } 3044 3045 /** 3046 * Function used by uasort to sort structured arrays by weight. 3047 */ 3048 function element_sort($a, $b) { 3049 $a_weight = (is_array($a) && isset($a['#weight'])) ? $a['#weight'] : 0; 3050 $b_weight = (is_array($b) && isset($b['#weight'])) ? $b['#weight'] : 0; 3051 if ($a_weight == $b_weight) { 3052 return 0; 3053 } 3054 return ($a_weight < $b_weight) ? -1 : 1; 3055 } 3056 3057 /** 3058 * Check if the key is a property. 3059 */ 3060 function element_property($key) { 3061 return $key[0] == '#'; 3062 } 3063 3064 /** 3065 * Get properties of a structured array element. Properties begin with '#'. 3066 */ 3067 function element_properties($element) { 3068 return array_filter(array_keys((array) $element), 'element_property'); 3069 } 3070 3071 /** 3072 * Check if the key is a child. 3073 */ 3074 function element_child($key) { 3075 return !isset($key[0]) || $key[0] != '#'; 3076 } 3077 3078 /** 3079 * Get keys of a structured array tree element that are not properties (i.e., do not begin with '#'). 3080 */ 3081 function element_children($element) { 3082 return array_filter(array_keys((array) $element), 'element_child'); 3083 } 3084 3085 /** 3086 * Provide theme registration for themes across .inc files. 3087 */ 3088 function drupal_common_theme() { 3089 return array( 3090 // theme.inc 3091 'placeholder' => array( 3092 'arguments' => array('text' => NULL) 3093 ), 3094 'page' => array( 3095 'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE), 3096 'template' => 'page', 3097 ), 3098 'maintenance_page' => array( 3099 'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE), 3100 'template' => 'maintenance-page', 3101 ), 3102 'update_page' => array( 3103 'arguments' => array('content' => NULL, 'show_messages' => TRUE), 3104 ), 3105 'install_page' => array( 3106 'arguments' => array('content' => NULL), 3107 ), 3108 'task_list' => array( 3109 'arguments' => array('items' => NULL, 'active' => NULL), 3110 ), 3111 'status_messages' => array( 3112 'arguments' => array('display' => NULL), 3113 ), 3114 'links' => array( 3115 'arguments' => array('links' => NULL, 'attributes' => array('class' => 'links')), 3116 ), 3117 'image' => array( 3118 'arguments' => array('path' => NULL, 'alt' => '', 'title' => '', 'attributes' => NULL, 'getsize' => TRUE), 3119 ), 3120 'breadcrumb' => array( 3121 'arguments' => array('breadcrumb' => NULL), 3122 ), 3123 'help' => array( 3124 'arguments' => array(), 3125 ), 3126 'submenu' => array( 3127 'arguments' => array('links' => NULL), 3128 ), 3129 'table' => array( 3130 'arguments' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL), 3131 ), 3132 'table_select_header_cell' => array( 3133 'arguments' => array(), 3134 ), 3135 'tablesort_indicator' => array( 3136 'arguments' => array('style' => NULL), 3137 ), 3138 'box' => array( 3139 'arguments' => array('title' => NULL, 'content' => NULL, 'region' => 'main'), 3140 'template' => 'box', 3141 ), 3142 'block' => array( 3143 'arguments' => array('block' => NULL), 3144 'template' => 'block', 3145 ), 3146 'mark' => array( 3147 'arguments' => array('type' => MARK_NEW), 3148 ), 3149 'item_list' => array( 3150 'arguments' => array('items' => array(), 'title' => NULL, 'type' => 'ul', 'attributes' => NULL), 3151 ), 3152 'more_help_link' => array( 3153 'arguments' => array('url' => NULL), 3154 ), 3155 'xml_icon' => array( 3156 'arguments' => array('url' => NULL), 3157 ), 3158 'feed_icon' => array( 3159 'arguments' => array('url' => NULL, 'title' => NULL), 3160 ), 3161 'more_link' => array( 3162 'arguments' => array('url' => NULL, 'title' => NULL) 3163 ), 3164 'closure' => array( 3165 'arguments' => array('main' => 0), 3166 ), 3167 'blocks' => array( 3168 'arguments' => array('region' => NULL), 3169 ), 3170 'username' => array( 3171 'arguments' => array('object' => NULL), 3172 ), 3173 'progress_bar' => array( 3174 'arguments' => array('percent' => NULL, 'message' => NULL), 3175 ), 3176 'indentation' => array( 3177 'arguments' => array('size' => 1), 3178 ), 3179 // from pager.inc 3180 'pager' => array( 3181 'arguments' => array('tags' => array(), 'limit' => 10, 'element' => 0, 'parameters' => array()), 3182 ), 3183 'pager_first' => array( 3184 'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'parameters' => array()), 3185 ), 3186 'pager_previous' => array( 3187 'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'interval' => 1, 'parameters' => array()), 3188 ), 3189 'pager_next' => array( 3190 'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'interval' => 1, 'parameters' => array()), 3191 ), 3192 'pager_last' => array( 3193 'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'parameters' => array()), 3194 ), 3195 'pager_link' => array( 3196 'arguments' => array('text' => NULL, 'page_new' => NULL, 'element' => NULL, 'parameters' => array(), 'attributes' => array()), 3197 ), 3198 // from menu.inc 3199 'menu_item_link' => array( 3200 'arguments' => array('item' => NULL), 3201 ), 3202 'menu_tree' => array( 3203 'arguments' => array('tree' => NULL), 3204 ), 3205 'menu_item' => array( 3206 'arguments' => array('link' => NULL, 'has_children' => NULL, 'menu' => ''), 3207 ), 3208 'menu_local_task' => array( 3209 'arguments' => array('link' => NULL, 'active' => FALSE), 3210 ), 3211 'menu_local_tasks' => array( 3212 'arguments' => array(), 3213 ), 3214 // from form.inc 3215 'select' => array( 3216 'arguments' => array('element' => NULL), 3217 ), 3218 'fieldset' => array( 3219 'arguments' => array('element' => NULL), 3220 ), 3221 'radio' => array( 3222 'arguments' => array('element' => NULL), 3223 ), 3224 'radios' => array( 3225 'arguments' => array('element' => NULL), 3226 ), 3227 'password_confirm' => array( 3228 'arguments' => array('element' => NULL), 3229 ), 3230 'date' => array( 3231 'arguments' => array('element' => NULL), 3232 ), 3233 'item' => array( 3234 'arguments' => array('element' => NULL), 3235 ), 3236 'checkbox' => array( 3237 'arguments' => array('element' => NULL), 3238 ), 3239 'checkboxes' => array( 3240 'arguments' => array('element' => NULL), 3241 ), 3242 'submit' => array( 3243 'arguments' => array('element' => NULL), 3244 ), 3245 'button' => array( 3246 'arguments' => array('element' => NULL), 3247 ), 3248 'image_button' => array( 3249 'arguments' => array('element' => NULL), 3250 ), 3251 'hidden' => array( 3252 'arguments' => array('element' => NULL), 3253 ), 3254 'token' => array( 3255 'arguments' => array('element' => NULL), 3256 ), 3257 'textfield' => array( 3258 'arguments' => array('element' => NULL), 3259 ), 3260 'form' => array( 3261 'arguments' => array('element' => NULL), 3262 ), 3263 'textarea' => array( 3264 'arguments' => array('element' => NULL), 3265 ), 3266 'markup' => array( 3267 'arguments' => array('element' => NULL), 3268 ), 3269 'password' => array( 3270 'arguments' => array('element' => NULL), 3271 ), 3272 'file' => array( 3273 'arguments' => array('element' => NULL), 3274 ), 3275 'form_element' => array( 3276 'arguments' => array('element' => NULL, 'value' => NULL), 3277 ), 3278 ); 3279 } 3280 3281 /** 3282 * @ingroup schemaapi 3283 * @{ 3284 */ 3285 3286 /** 3287 * Get the schema definition of a table, or the whole database schema. 3288 * 3289 * The returned schema will include any modifications made by any 3290 * module that implements hook_schema_alter(). 3291 * 3292 * @param $table 3293 * The name of the table. If not given, the schema of all tables is returned. 3294 * @param $rebuild 3295 * If true, the schema will be rebuilt instead of retrieved from the cache. 3296 */ 3297 function drupal_get_schema($table = NULL, $rebuild = FALSE) { 3298 static $schema = array(); 3299 3300 if (empty($schema) || $rebuild) { 3301 // Try to load the schema from cache. 3302 if (!$rebuild && $cached = cache_get('schema')) { 3303 $schema = $cached->data; 3304 } 3305 // Otherwise, rebuild the schema cache. 3306 else { 3307 $schema = array(); 3308 // Load the .install files to get hook_schema. 3309 module_load_all_includes('install'); 3310 3311 // Invoke hook_schema for all modules. 3312 foreach (module_implements('schema') as $module) { 3313 // Cast the result of hook_schema() to an array, as a NULL return value 3314 // would cause array_merge() to set the $schema variable to NULL as well. 3315 // That would break modules which use $schema further down the line. 3316 $current = (array) module_invoke($module, 'schema'); 3317 _drupal_initialize_schema($module, $current); 3318 $schema = array_merge($schema, $current); 3319 } 3320 3321 drupal_alter('schema', $schema); 3322 cache_set('schema', $schema); 3323 } 3324 } 3325 3326 if (!isset($table)) { 3327 return $schema; 3328 } 3329 elseif (isset($schema[$table])) { 3330 return $schema[$table]; 3331 } 3332 else { 3333 return FALSE; 3334 } 3335 } 3336 3337 /** 3338 * Create all tables that a module defines in its hook_schema(). 3339 * 3340 * Note: This function does not pass the module's schema through 3341 * hook_schema_alter(). The module's tables will be created exactly as the 3342 * module defines them. 3343 * 3344 * @param $module 3345 * The module for which the tables will be created. 3346 * @return 3347 * An array of arrays with the following key/value pairs: 3348 * - success: a boolean indicating whether the query succeeded. 3349 * - query: the SQL query(s) executed, passed through check_plain(). 3350 */ 3351 function drupal_install_schema($module) { 3352 $schema = drupal_get_schema_unprocessed($module); 3353 _drupal_initialize_schema($module, $schema); 3354 3355 $ret = array(); 3356 foreach ($schema as $name => $table) { 3357 db_create_table($ret, $name, $table); 3358 } 3359 return $ret; 3360 } 3361 3362 /** 3363 * Remove all tables that a module defines in its hook_schema(). 3364 * 3365 * Note: This function does not pass the module's schema through 3366 * hook_schema_alter(). The module's tables will be created exactly as the 3367 * module defines them. 3368 * 3369 * @param $module 3370 * The module for which the tables will be removed. 3371 * @return 3372 * An array of arrays with the following key/value pairs: 3373 * - success: a boolean indicating whether the query succeeded. 3374 * - query: the SQL query(s) executed, passed through check_plain(). 3375 */ 3376 function drupal_uninstall_schema($module) { 3377 $schema = drupal_get_schema_unprocessed($module); 3378 _drupal_initialize_schema($module, $schema); 3379 3380 $ret = array(); 3381 foreach ($schema as $table) { 3382 db_drop_table($ret, $table['name']); 3383 } 3384 return $ret; 3385 } 3386 3387 /** 3388 * Returns the unprocessed and unaltered version of a module's schema. 3389 * 3390 * Use this function only if you explicitly need the original 3391 * specification of a schema, as it was defined in a module's 3392 * hook_schema(). No additional default values will be set, 3393 * hook_schema_alter() is not invoked and these unprocessed 3394 * definitions won't be cached. 3395 * 3396 * This function can be used to retrieve a schema specification in 3397 * hook_schema(), so it allows you to derive your tables from existing 3398 * specifications. 3399 * 3400 * It is also used by drupal_install_schema() and 3401 * drupal_uninstall_schema() to ensure that a module's tables are 3402 * created exactly as specified without any changes introduced by a 3403 * module that implements hook_schema_alter(). 3404 * 3405 * @param $module 3406 * The module to which the table belongs. 3407 * @param $table 3408 * The name of the table. If not given, the module's complete schema 3409 * is returned. 3410 */ 3411 function drupal_get_schema_unprocessed($module, $table = NULL) { 3412 // Load the .install file to get hook_schema. 3413 module_load_install($module); 3414 $schema = module_invoke($module, 'schema'); 3415 3416 if (!is_null($table) && isset($schema[$table])) { 3417 return $schema[$table]; 3418 } 3419 elseif (!empty($schema)) { 3420 return $schema; 3421 } 3422 3423 return array(); 3424 } 3425 3426 /** 3427 * Fill in required default values for table definitions returned by hook_schema(). 3428 * 3429 * @param $module 3430 * The module for which hook_schema() was invoked. 3431 * @param $schema 3432 * The schema definition array as it was returned by the module's 3433 * hook_schema(). 3434 */ 3435 function _drupal_initialize_schema($module, &$schema) { 3436 // Set the name and module key for all tables. 3437 foreach ($schema as $name => $table) { 3438 if (empty($table['module'])) { 3439 $schema[$name]['module'] = $module; 3440 } 3441 if (!isset($table['name'])) { 3442 $schema[$name]['name'] = $name; 3443 } 3444 } 3445 } 3446 3447 /** 3448 * Retrieve a list of fields from a table schema. The list is suitable for use in a SQL query. 3449 * 3450 * @param $table 3451 * The name of the table from which to retrieve fields. 3452 * @param 3453 * An optional prefix to to all fields. 3454 * 3455 * @return An array of fields. 3456 **/ 3457 function drupal_schema_fields_sql($table, $prefix = NULL) { 3458 $schema = drupal_get_schema($table); 3459 $fields = array_keys($schema['fields']); 3460 if ($prefix) { 3461 $columns = array(); 3462 foreach ($fields as $field) { 3463 $columns[] = "$prefix.$field"; 3464 } 3465 return $columns; 3466 } 3467 else { 3468 return $fields; 3469 } 3470 } 3471 3472 /** 3473 * Save a record to the database based upon the schema. 3474 * 3475 * Default values are filled in for missing items, and 'serial' (auto increment) 3476 * types are filled in with IDs. 3477 * 3478 * @param $table 3479 * The name of the table; this must exist in schema API. 3480 * @param $object 3481 * The object to write. This is a reference, as defaults according to 3482 * the schema may be filled in on the object, as well as ID on the serial 3483 * type(s). Both array an object types may be passed. 3484 * @param $update 3485 * If this is an update, specify the primary keys' field names. It is the 3486 * caller's responsibility to know if a record for this object already 3487 * exists in the database. If there is only 1 key, you may pass a simple string. 3488 * @return 3489 * Failure to write a record will return FALSE. Otherwise SAVED_NEW or 3490 * SAVED_UPDATED is returned depending on the operation performed. The 3491 * $object parameter contains values for any serial fields defined by 3492 * the $table. For example, $object->nid will be populated after inserting 3493 * a new node. 3494 */ 3495 function drupal_write_record($table, &$object, $update = array()) { 3496 // Standardize $update to an array. 3497 if (is_string($update)) { 3498 $update = array($update); 3499 } 3500 3501 $schema = drupal_get_schema($table); 3502 if (empty($schema)) { 3503 return FALSE; 3504 } 3505 3506 // Convert to an object if needed. 3507 if (is_array($object)) { 3508 $object = (object) $object; 3509 $array = TRUE; 3510 } 3511 else { 3512 $array = FALSE; 3513 } 3514 3515 $fields = $defs = $values = $serials = $placeholders = array(); 3516 3517 // Go through our schema, build SQL, and when inserting, fill in defaults for 3518 // fields that are not set. 3519 foreach ($schema['fields'] as $field => $info) { 3520 // Special case -- skip serial types if we are updating. 3521 if ($info['type'] == 'serial' && count($update)) { 3522 continue; 3523 } 3524 3525 // For inserts, populate defaults from Schema if not already provided 3526 if (!isset($object->$field) && !count($update) && isset($info['default'])) { 3527 $object->$field = $info['default']; 3528 } 3529 3530 // Track serial fields so we can helpfully populate them after the query. 3531 if ($info['type'] == 'serial') { 3532 $serials[] = $field; 3533 // Ignore values for serials when inserting data. Unsupported. 3534 unset($object->$field); 3535 } 3536 3537 // Build arrays for the fields, placeholders, and values in our query. 3538 if (isset($object->$field)) { 3539 $fields[] = $field; 3540 $placeholders[] = db_type_placeholder($info['type']); 3541 3542 if (empty($info['serialize'])) { 3543 $values[] = $object->$field; 3544 } 3545 else { 3546 $values[] = serialize($object->$field); 3547 } 3548 } 3549 } 3550 3551 // Build the SQL. 3552 $query = ''; 3553 if (!count($update)) { 3554 $query = "INSERT INTO {". $table ."} (". implode(', ', $fields) .') VALUES ('. implode(', ', $placeholders) .')'; 3555 $return = SAVED_NEW; 3556 } 3557 else { 3558 $query = ''; 3559 foreach ($fields as $id => $field) { 3560 if ($query) { 3561 $query .= ', '; 3562 } 3563 $query .= $field .' = '. $placeholders[$id]; 3564 } 3565 3566 foreach ($update as $key){ 3567 $conditions[] = "$key = ". db_type_placeholder($schema['fields'][$key]['type']); 3568 $values[] = $object->$key; 3569 } 3570 3571 $query = "UPDATE {". $table ."} SET $query WHERE ". implode(' AND ', $conditions); 3572 $return = SAVED_UPDATED; 3573 } 3574 3575 // Execute the SQL. 3576 if (db_query($query, $values)) { 3577 if ($serials) { 3578 // Get last insert ids and fill them in. 3579 foreach ($serials as $field) { 3580 $object->$field = db_last_insert_id($table, $field); 3581 } 3582 } 3583 } 3584 else { 3585 $return = FALSE; 3586 } 3587 3588 // If we began with an array, convert back so we don't surprise the caller. 3589 if ($array) { 3590 $object = (array) $object; 3591 } 3592 3593 return $return; 3594 } 3595 3596 /** 3597 * @} End of "ingroup schemaapi". 3598 */ 3599 3600 /** 3601 * Parse Drupal info file format. 3602 * 3603 * Files should use an ini-like format to specify values. 3604 * White-space generally doesn't matter, except inside values. 3605 * e.g. 3606 * 3607 * @code 3608 * key = value 3609 * key = "value" 3610 * key = 'value' 3611 * key = "multi-line 3612 * 3613 * value" 3614 * key = 'multi-line 3615 * 3616 * value' 3617 * key 3618 * = 3619 * 'value' 3620 * @endcode 3621 * 3622 * Arrays are created using a GET-like syntax: 3623 * 3624 * @code 3625 * key[] = "numeric array" 3626 * key[index] = "associative array" 3627 * key[index][] = "nested numeric array" 3628 * key[index][index] = "nested associative array" 3629 * @endcode 3630 * 3631 * PHP constants are substituted in, but only when used as the entire value: 3632 * 3633 * Comments should start with a semi-colon at the beginning of a line. 3634 * 3635 * This function is NOT for placing arbitrary module-specific settings. Use 3636 * variable_get() and variable_set() for that. 3637 * 3638 * Information stored in the module.info file: 3639 * - name: The real name of the module for display purposes. 3640 * - description: A brief description of the module. 3641 * - dependencies: An array of shortnames of other modules this module depends on. 3642 * - package: The name of the package of modules this module belongs to. 3643 * 3644 * Example of .info file: 3645 * @code 3646 * name = Forum 3647 * description = Enables threaded discussions about general topics. 3648 * dependencies[] = taxonomy 3649 * dependencies[] = comment 3650 * package = Core - optional 3651 * version = VERSION 3652 * @endcode 3653 * 3654 * @param $filename 3655 * The file we are parsing. Accepts file with relative or absolute path. 3656 * @return 3657 * The info array. 3658 */ 3659 function drupal_parse_info_file($filename) { 3660 $info = array(); 3661 $constants = get_defined_constants(); 3662 3663 if (!file_exists($filename)) { 3664 return $info; 3665 } 3666 3667 $data = file_get_contents($filename); 3668 if (preg_match_all(' 3669 @^\s* # Start at the beginning of a line, ignoring leading whitespace 3670 ((?: 3671 [^=;\[\]]| # Key names cannot contain equal signs, semi-colons or square brackets, 3672 \[[^\[\]]*\] # unless they are balanced and not nested 3673 )+?) 3674 \s*=\s* # Key/value pairs are separated by equal signs (ignoring white-space) 3675 (?: 3676 ("(?:[^"]|(?<=\\\\)")*")| # Double-quoted string, which may contain slash-escaped quotes/slashes 3677 (\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes 3678 ([^\r\n]*?) # Non-quoted string 3679 )\s*$ # Stop at the next end of a line, ignoring trailing whitespace 3680 @msx', $data, $matches, PREG_SET_ORDER)) { 3681 foreach ($matches as $match) { 3682 // Fetch the key and value string 3683 $i = 0; 3684 foreach (array('key', 'value1', 'value2', 'value3') as $var) { 3685 $$var = isset($match[++$i]) ? $match[$i] : ''; 3686 } 3687 $value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3; 3688 3689 // Parse array syntax 3690 $keys = preg_split('/\]?\[/', rtrim($key, ']')); 3691 $last = array_pop($keys); 3692 $parent = &$info; 3693 3694 // Create nested arrays 3695 foreach ($keys as $key) { 3696 if ($key == '') { 3697 $key = count($parent); 3698 } 3699 if (!isset($parent[$key]) || !is_array($parent[$key])) { 3700 $parent[$key] = array(); 3701 } 3702 $parent = &$parent[$key]; 3703 } 3704 3705 // Handle PHP constants. 3706 if (isset($constants[$value])) { 3707 $value = $constants[$value]; 3708 } 3709 3710 // Insert actual value 3711 if ($last == '') { 3712 $last = count($parent); 3713 } 3714 $parent[$last] = $value; 3715 } 3716 } 3717 3718 return $info; 3719 } 3720 3721 /** 3722 * @return 3723 * Array of the possible severity levels for log messages. 3724 * 3725 * @see watchdog 3726 */ 3727 function watchdog_severity_levels() { 3728 return array( 3729 WATCHDOG_EMERG => t('emergency'), 3730 WATCHDOG_ALERT => t('alert'), 3731 WATCHDOG_CRITICAL => t('critical'), 3732 WATCHDOG_ERROR => t('error'), 3733 WATCHDOG_WARNING => t('warning'), 3734 WATCHDOG_NOTICE => t('notice'), 3735 WATCHDOG_INFO => t('info'), 3736 WATCHDOG_DEBUG => t('debug'), 3737 ); 3738 } 3739 3740 3741 /** 3742 * Explode a string of given tags into an array. 3743 * 3744 * @see drupal_implode_tags() 3745 */ 3746 function drupal_explode_tags($tags) { 3747 // This regexp allows the following types of user input: 3748 // this, "somecompany, llc", "and ""this"" w,o.rks", foo bar 3749 $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x'; 3750 preg_match_all($regexp, $tags, $matches); 3751 $typed_tags = array_unique($matches[1]); 3752 3753 $tags = array(); 3754 foreach ($typed_tags as $tag) { 3755 // If a user has escaped a term (to demonstrate that it is a group, 3756 // or includes a comma or quote character), we remove the escape 3757 // formatting so to save the term into the database as the user intends. 3758 $tag = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $tag))); 3759 if ($tag != "") { 3760 $tags[] = $tag; 3761 } 3762 } 3763 3764 return $tags; 3765 } 3766 3767 /** 3768 * Implode an array of tags into a string. 3769 * 3770 * @see drupal_explode_tags() 3771 */ 3772 function drupal_implode_tags($tags) { 3773 $encoded_tags = array(); 3774 foreach ($tags as $tag) { 3775 // Commas and quotes in tag names are special cases, so encode them. 3776 if (strpos($tag, ',') !== FALSE || strpos($tag, '"') !== FALSE) { 3777 $tag = '"'. str_replace('"', '""', $tag) .'"'; 3778 } 3779 3780 $encoded_tags[] = $tag; 3781 } 3782 return implode(', ', $encoded_tags); 3783 } 3784 3785 /** 3786 * Flush all cached data on the site. 3787 * 3788 * Empties cache tables, rebuilds the menu cache and theme registries, and 3789 * invokes a hook so that other modules' cache data can be cleared as well. 3790 */ 3791 function drupal_flush_all_caches() { 3792 // Change query-strings on css/js files to enforce reload for all users. 3793 _drupal_flush_css_js(); 3794 3795 drupal_clear_css_cache(); 3796 drupal_clear_js_cache(); 3797 3798 // If invoked from update.php, we must not update the theme information in the 3799 // database, or this will result in all themes being disabled. 3800 if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update') { 3801 _system_theme_data(); 3802 } 3803 else { 3804 system_theme_data(); 3805 } 3806 3807 drupal_rebuild_theme_registry(); 3808 menu_rebuild(); 3809 node_types_rebuild(); 3810 // Don't clear cache_form - in-progress form submissions may break. 3811 // Ordered so clearing the page cache will always be the last action. 3812 $core = array('cache', 'cache_block', 'cache_filter', 'cache_page'); 3813 $cache_tables = array_merge(module_invoke_all('flush_caches'), $core); 3814 foreach ($cache_tables as $table) { 3815 cache_clear_all('*', $table, TRUE); 3816 } 3817 } 3818 3819 /** 3820 * Helper function to change query-strings on css/js files. 3821 * 3822 * Changes the character added to all css/js files as dummy query-string, 3823 * so that all browsers are forced to reload fresh files. We keep 3824 * 20 characters history (FIFO) to avoid repeats, but only the first 3825 * (newest) character is actually used on urls, to keep them short. 3826 * This is also called from update.php. 3827 */ 3828 function _drupal_flush_css_js() { 3829 $string_history = variable_get('css_js_query_string', '00000000000000000000'); 3830 $new_character = $string_history[0]; 3831 // Not including 'q' to allow certain JavaScripts to re-use query string. 3832 $characters = 'abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; 3833 while (strpos($string_history, $new_character) !== FALSE) { 3834 $new_character = $characters[mt_rand(0, strlen($characters) - 1)]; 3835 } 3836 variable_set('css_js_query_string', $new_character . substr($string_history, 0, 19)); 3837 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Mon Jul 9 18:01:44 2012 | Cross-referenced by PHPXref 0.7 |