Newer
Older
Dries Buytaert
committed
// $Id$
/**
* @file
* Common functions that many Drupal modules will need to reference.
*
* The functions that are critical and need to be available even when serving
* a cached page are instead located in bootstrap.inc.
*/
/**
* Error reporting level: display no errors.
*/
define('ERROR_REPORTING_HIDE', 0);
/**
* Error reporting level: display errors and warnings.
*/
define('ERROR_REPORTING_DISPLAY_SOME', 1);
/**
* Error reporting level: display all messages.
*/
define('ERROR_REPORTING_DISPLAY_ALL', 2);
Steven Wittens
committed
/**
* Return status for saving which involved creating a new item.
*/
define('SAVED_NEW', 1);
/**
* Return status for saving which involved an update to an existing item.
*/
define('SAVED_UPDATED', 2);
/**
* Return status for saving which deleted an existing item.
*/
define('SAVED_DELETED', 3);
Angie Byron
committed
/**
* The weight of JavaScript libraries, settings or jQuery plugins being
* added to the page.
*/
define('JS_LIBRARY', -100);
/**
* The default weight of JavaScript being added to the page.
*/
define('JS_DEFAULT', 0);
/**
* The weight of theme JavaScript code being added to the page.
*/
define('JS_THEME', 100);
Dries Buytaert
committed
/**
* Error code indicating that the request made by drupal_http_request() exceeded
* the specified timeout.
*/
define('HTTP_REQUEST_TIMEOUT', 1);
Dries Buytaert
committed
* Add content to a specified region.
Dries Buytaert
committed
* Page region the content is added to.
Dries Buytaert
committed
* Content to be added.
Dries Buytaert
committed
function drupal_add_region_content($region = NULL, $data = NULL) {
static $content = array();
if (!is_null($region) && !is_null($data)) {
$content[$region][] = $data;
}
return $content;
}
/**
Dries Buytaert
committed
* Get assigned content for a given region.
* A specified region to fetch content for. If NULL, all regions will be
* returned.
* @param $delimiter
* Content to be inserted between exploded array elements.
*/
Dries Buytaert
committed
function drupal_get_region_content($region = NULL, $delimiter = ' ') {
$content = drupal_add_region_content();
if (isset($region)) {
if (isset($content[$region]) && is_array($content[$region])) {
}
else {
foreach (array_keys($content) as $region) {
if (is_array($content[$region])) {
$content[$region] = implode($delimiter, $content[$region]);
}
}
return $content;
}
}
* @param $breadcrumb
* Array of links, starting with "home" and proceeding up to but not including
* the current page.
Dries Buytaert
committed
$stored_breadcrumb = &drupal_static(__FUNCTION__);
if (!is_null($breadcrumb)) {
$stored_breadcrumb = $breadcrumb;
}
return $stored_breadcrumb;
}
function drupal_get_breadcrumb() {
$breadcrumb = drupal_set_breadcrumb();
if (is_null($breadcrumb)) {
$breadcrumb = menu_get_active_breadcrumb();
}
return $breadcrumb;
}
/**
Dries Buytaert
committed
* Return a string containing RDF namespaces for the <html> tag of an XHTML
* page.
*/
function drupal_get_rdf_namespaces() {
// Serialize the RDF namespaces used in RDFa annotation.
$xml_rdf_namespaces = array();
foreach (module_invoke_all('rdf_namespaces') as $prefix => $uri) {
$xml_rdf_namespaces[] = 'xmlns:' . $prefix . '="' . $uri . '"';
}
return implode("\n ", $xml_rdf_namespaces);
}
function drupal_add_html_head($data = NULL) {
Dries Buytaert
committed
$stored_head = &drupal_static(__FUNCTION__, '');
$stored_head .= $data . "\n";
/**
* Retrieve output to be displayed in the head tag of the HTML page.
*/
$output = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
return $output . drupal_add_html_head();
* Reset the static variable which holds the aliases mapped for this request.
function drupal_clear_path_cache() {
drupal_lookup_path('wipe');
* Add a feed URL for the current page.
*
* This function can be called as long the HTML header hasn't been sent.
*
* A url for the feed.
* @param $title
* The title of the feed.
function drupal_add_feed($url = NULL, $title = '') {
Dries Buytaert
committed
$stored_feed_links = &drupal_static(__FUNCTION__, array());
Gábor Hojtsy
committed
if (!is_null($url) && !isset($stored_feed_links[$url])) {
$stored_feed_links[$url] = theme('feed_icon', $url, $title);
drupal_add_link(array('rel' => 'alternate',
'type' => 'application/rss+xml',
'title' => $title,
'href' => $url));
}
return $stored_feed_links;
}
/**
* Get the feed URLs for the current page.
*
* @param $delimiter
* A delimiter to split feeds by.
*/
function drupal_get_feeds($delimiter = "\n") {
$feeds = drupal_add_feed();
return implode($feeds, $delimiter);
}
Gerhard Killesreiter
committed
/**
* Parse an array into a valid urlencoded query string.
*
* @param $query
* The array to be processed e.g. $_GET.
Gerhard Killesreiter
committed
* @param $exclude
* The array filled with keys to be excluded. Use parent[child] to exclude
* nested items.
Gerhard Killesreiter
committed
* @param $parent
* Should not be passed, only used in recursive calls.
Gerhard Killesreiter
committed
* @return
* An urlencoded string which can be appended to/as the URL query string.
Gerhard Killesreiter
committed
*/
function drupal_query_string_encode($query, $exclude = array(), $parent = '') {
$params = array();
foreach ($query as $key => $value) {
Dries Buytaert
committed
$key = rawurlencode($key);
Gerhard Killesreiter
committed
if ($parent) {
$key = $parent . '[' . $key . ']';
Gerhard Killesreiter
committed
}
if (in_array($key, $exclude)) {
Gerhard Killesreiter
committed
continue;
}
if (is_array($value)) {
$params[] = drupal_query_string_encode($value, $exclude, $key);
}
else {
Dries Buytaert
committed
$params[] = $key . '=' . rawurlencode($value);
Gerhard Killesreiter
committed
}
}
return implode('&', $params);
}
/**
* Prepare a destination query string for use in combination with drupal_goto().
* Used to direct the user back to the referring page after completing a form.
* By default the current URL is returned. If a destination exists in the
* previous request, that destination is returned. As such, a destination can
* persist across multiple pages.
*
* @see drupal_goto()
*/
function drupal_get_destination() {
if (isset($_REQUEST['destination'])) {
return 'destination=' . urlencode($_REQUEST['destination']);
Dries Buytaert
committed
}
else {
// Use $_GET here to retrieve the original path in source form.
$path = isset($_GET['q']) ? $_GET['q'] : '';
Gerhard Killesreiter
committed
$query = drupal_query_string_encode($_GET, array('q'));
if ($query != '') {
$path .= '?' . $query;
Gerhard Killesreiter
committed
}
return 'destination=' . urlencode($path);
}
}
* This issues an on-site HTTP redirect. The function makes sure the redirected
* URL is formatted correctly.
* Usually the redirected URL is constructed from this function's input
* parameters. However you may override that behavior by setting a
* destination in either the $_REQUEST-array (i.e. by using
* the query string of an URI) This is used to direct the user back to
* the proper page after completing a form. For example, after editing
* a post on the 'admin/content/node'-page or after having logged on using the
* 'user login'-block in a sidebar. The function drupal_get_destination()
* can be used to help set the destination URL.
*
* Drupal will ensure that messages set by drupal_set_message() and other
* session data are written to the database before the user is redirected.
* This function ends the request; use it instead of a return in your menu callback.
* A Drupal path or a full URL.
* A query string component, if any.
* A destination fragment identifier (named anchor).
* @param $http_response_code
* Valid values for an actual "goto" as per RFC 2616 section 10.3 are:
* - 301 Moved Permanently (the recommended value for most redirects)
* - 302 Found (default in Drupal and PHP, sometimes used for spamming search
* engines)
* - 303 See Other
* - 304 Not Modified
* - 305 Use Proxy
* - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance")
* Note: Other values are defined by RFC 2616, but are rarely used and poorly
* supported.
* @see drupal_get_destination()
function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) {
Dries Buytaert
committed
if (isset($_REQUEST['destination'])) {
extract(parse_url(urldecode($_REQUEST['destination'])));
}
$url = url($path, array('query' => $query, 'fragment' => $fragment, 'absolute' => TRUE));
// Allow modules to react to the end of the page request before redirecting.
Dries Buytaert
committed
// We do not want this while running update.php.
if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
Dries Buytaert
committed
module_invoke_all('exit', $url);
}
Dries Buytaert
committed
// Commit the session, if necessary. We need all session data written to the
// database before redirecting.
drupal_session_commit();
Dries Buytaert
committed
header('Location: ' . $url, TRUE, $http_response_code);
Dries Buytaert
committed
// The "Location" header sends a redirect status code to the HTTP daemon. In
// some cases this can be wrong, so we make sure none of the code below the
// drupal_goto() call gets executed upon redirection.
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Generates a site offline message.
Dries Buytaert
committed
*/
function drupal_site_offline() {
drupal_maintenance_theme();
Dries Buytaert
committed
drupal_set_header('503 Service unavailable');
Dries Buytaert
committed
drupal_set_title(t('Site offline'));
Steven Wittens
committed
print theme('maintenance_page', filter_xss_admin(variable_get('site_offline_message',
t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))));
Dries Buytaert
committed
}
/**
* Generates a 404 error if the request can not be handled.
*/
Dries Buytaert
committed
drupal_set_header('404 Not Found');
watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
Angie Byron
committed
// Keep old path for reference, and to allow forms to redirect to it.
if (!isset($_REQUEST['destination'])) {
$_REQUEST['destination'] = $_GET['q'];
}
// Custom 404 handler. Set the active item in case there are tabs to
// display, or other dependencies on the path.
Gábor Hojtsy
committed
menu_set_active_item($path);
Dries Buytaert
committed
$return = menu_execute_active_handler($path);
Gerhard Killesreiter
committed
}
Gábor Hojtsy
committed
if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
// Standard 404 handler.
Gábor Hojtsy
committed
$return = t('The requested page could not be found.');
Dries Buytaert
committed
drupal_set_page_content($return);
$page = element_info('page');
Dries Buytaert
committed
// Optionally omit the blocks to conserve CPU and bandwidth.
$page['#show_blocks'] = variable_get('site_404_blocks', FALSE);
print drupal_render_page($page);
/**
* Generates a 403 error if the request is not allowed.
*/
function drupal_access_denied() {
Dries Buytaert
committed
drupal_set_header('403 Forbidden');
watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
Angie Byron
committed
// Keep old path for reference, and to allow forms to redirect to it.
if (!isset($_REQUEST['destination'])) {
$_REQUEST['destination'] = $_GET['q'];
}
// Custom 403 handler. Set the active item in case there are tabs to
// display or other dependencies on the path.
Gábor Hojtsy
committed
menu_set_active_item($path);
Dries Buytaert
committed
$return = menu_execute_active_handler($path);
Gerhard Killesreiter
committed
}
Gábor Hojtsy
committed
if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
// Standard 403 handler.
drupal_set_title(t('Access denied'));
$return = t('You are not authorized to access this page.');
print drupal_render_page($return);
Dries Buytaert
committed
* This is a flexible and powerful HTTP client implementation. Correctly
* handles GET, POST, PUT or any other HTTP requests. Handles redirects.
Dries Buytaert
committed
* @param $options
* (optional) An array which can have one or more of following keys:
* - headers
* An array containing request headers to send as name/value pairs.
* - method
* A string containing the request method. Defaults to 'GET'.
* - data
* A string containing the request body. Defaults to NULL.
* - max_redirects
* An integer representing how many times a redirect may be followed.
* Defaults to 3.
Dries Buytaert
committed
* - timeout
* A float representing the maximum number of seconds the function call
* may take. The default is 30 seconds. If a timeout occurs, the error
* code is set to the HTTP_REQUEST_TIMEOUT constant.
Dries Buytaert
committed
* An object which can have one or more of the following parameters:
* - request
* A string containing the request body that was sent.
* - code
* An integer containing the response status code, or the error code if
* an error occurred.
Dries Buytaert
committed
* - protocol
* The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
Dries Buytaert
committed
* - status_message
* The status message from the response, if a response was received.
Dries Buytaert
committed
* - redirect_code
* If redirected, an integer containing the initial response status code.
* - redirect_url
* If redirected, a string containing the redirection location.
* - error
Dries Buytaert
committed
* If an error occurred, the error message. Otherwise not set.
Dries Buytaert
committed
* - headers
* An array containing the response headers as name/value pairs.
* - data
* A string containing the response body that was received.
Dries Buytaert
committed
function drupal_http_request($url, array $options = array()) {
Dries Buytaert
committed
global $db_prefix;
Dries Buytaert
committed
$result = new stdClass();
// Parse the URL and make sure we can handle the schema.
Dries Buytaert
committed
$uri = @parse_url($url);
Dries Buytaert
committed
Dries Buytaert
committed
if ($uri == FALSE) {
$result->error = 'unable to parse URL';
Dries Buytaert
committed
return $result;
}
Dries Buytaert
committed
if (!isset($uri['scheme'])) {
$result->error = 'missing schema';
Dries Buytaert
committed
return $result;
}
Dries Buytaert
committed
timer_start(__FUNCTION__);
// Merge the default options.
$options += array(
'headers' => array(),
'method' => 'GET',
'data' => NULL,
'max_redirects' => 3,
'timeout' => 30,
);
$port = isset($uri['port']) ? $uri['port'] : 80;
$host = $uri['host'] . ($port != 80 ? ':' . $port : '');
Dries Buytaert
committed
$fp = @fsockopen($uri['host'], $port, $errno, $errstr, $options['timeout']);
Dries Buytaert
committed
// Note: Only works when PHP is compiled with OpenSSL support.
$port = isset($uri['port']) ? $uri['port'] : 443;
$host = $uri['host'] . ($port != 443 ? ':' . $port : '');
Dries Buytaert
committed
$fp = @fsockopen('ssl://' . $uri['host'], $port, $errno, $errstr, $options['timeout']);
$result->error = 'invalid schema ' . $uri['scheme'];
Gábor Hojtsy
committed
// When a network error occurs, we use a negative number so it does not
// clash with the HTTP status codes.
Dries Buytaert
committed
$result->code = -$errno;
$result->error = trim($errstr);
Dries Buytaert
committed
// Mark that this request failed. This will trigger a check of the web
// server's ability to make outgoing HTTP requests the next time that
// requirements checking is performed.
// @see system_requirements()
variable_set('drupal_http_request_fails', TRUE);
$path = isset($uri['path']) ? $uri['path'] : '/';
if (isset($uri['query'])) {
$path .= '?' . $uri['query'];
Dries Buytaert
committed
// Merge the default headers.
$options['headers'] += array(
'User-Agent' => 'Drupal (+http://drupal.org/)',
Dries Buytaert
committed
// RFC 2616: "non-standard ports MUST, default ports MAY be included".
// We don't add the standard port to prevent from breaking rewrite rules
// checking the host that do not take into account the port number.
$options['headers']['Host'] = $host;
Dries Buytaert
committed
// Only add Content-Length if we actually have any content or if it is a POST
// or PUT request. Some non-standard servers get confused by Content-Length in
// at least HEAD/GET requests, and Squid always requires Content-Length in
// POST/PUT requests.
$content_length = strlen($options['data']);
if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
$options['headers']['Content-Length'] = $content_length;
Dries Buytaert
committed
}
// If the server URL has a user then attempt to use basic authentication.
if (isset($uri['user'])) {
Dries Buytaert
committed
$options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : ''));
}
Dries Buytaert
committed
// If the database prefix is being used by SimpleTest to run the tests in a copied
// database then set the user-agent header to the database prefix so that any
// calls to other Drupal pages will run the SimpleTest prefixed database. The
// user-agent is used to ensure that multiple testing sessions running at the
// same time won't interfere with each other as they would if the database
// prefix were stored statically in a file or database variable.
Dries Buytaert
committed
if (is_string($db_prefix) && preg_match("/simpletest\d+/", $db_prefix, $matches)) {
$options['headers']['User-Agent'] = drupal_generate_test_ua($matches[0]);
Dries Buytaert
committed
}
Dries Buytaert
committed
$request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
Dries Buytaert
committed
foreach ($options['headers'] as $name => $value) {
Dries Buytaert
committed
$request .= $name . ': ' . trim($value) . "\r\n";
Dries Buytaert
committed
$request .= "\r\n" . $options['data'];
$result->request = $request;
fwrite($fp, $request);
// Fetch response.
Dries Buytaert
committed
while (!feof($fp)) {
// Calculate how much time is left of the original timeout value.
$timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
if ($timeout <= 0) {
$result->code = HTTP_REQUEST_TIMEOUT;
$result->error = 'request timed out';
return $result;
}
stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
$response .= fread($fp, 1024);
Dries Buytaert
committed
// Parse response headers from the response body.
list($response, $result->data) = explode("\r\n\r\n", $response, 2);
$response = preg_split("/\r\n|\n|\r/", $response);
Dries Buytaert
committed
// Parse the response status line.
Dries Buytaert
committed
list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
$result->protocol = $protocol;
$result->status_message = $status_message;
Dries Buytaert
committed
// Parse the response headers.
while ($line = trim(array_shift($response))) {
if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
// RFC 2109: the Set-Cookie response header comprises the token Set-
// Cookie:, followed by a comma-separated list of one or more cookies.
$result->headers[$header] .= ',' . trim($value);
}
else {
$result->headers[$header] = trim($value);
}
Dries Buytaert
committed
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
100 => 'Continue',
101 => 'Switching Protocols',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
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',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Time-out',
505 => 'HTTP Version not supported',
// RFC 2616 states that all unknown HTTP codes must be treated the same as the
// base code in their class.
if (!isset($responses[$code])) {
$code = floor($code / 100) * 100;
}
Dries Buytaert
committed
$result->code = $code;
switch ($code) {
case 200: // OK
case 304: // Not modified
break;
case 301: // Moved permanently
case 302: // Moved temporarily
case 307: // Moved temporarily
$location = $result->headers['Location'];
Dries Buytaert
committed
$options['timeout'] -= timer_read(__FUNCTION__) / 1000;
if ($options['timeout'] <= 0) {
$result->code = HTTP_REQUEST_TIMEOUT;
$result->error = 'request timed out';
}
elseif ($options['max_redirects']) {
Dries Buytaert
committed
// Redirect to the new location.
$options['max_redirects']--;
$result = drupal_http_request($location, $options);
$result->redirect_code = $code;
Dries Buytaert
committed
$result->error = $status_message;
Dries Buytaert
committed
* Custom PHP error handler.
Dries Buytaert
committed
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
* @param $error_level
* The level of the error raised.
* @param $message
* The error message.
* @param $filename
* The filename that the error was raised in.
* @param $line
* The line number the error was raised at.
* @param $context
* An array that points to the active symbol table at the point the error occurred.
*/
function _drupal_error_handler($error_level, $message, $filename, $line, $context) {
if ($error_level & error_reporting()) {
// All these constants are documented at http://php.net/manual/en/errorfunc.constants.php
$types = array(
E_ERROR => 'Error',
E_WARNING => 'Warning',
E_PARSE => 'Parse error',
E_NOTICE => 'Notice',
E_CORE_ERROR => 'Core error',
E_CORE_WARNING => 'Core warning',
E_COMPILE_ERROR => 'Compile error',
E_COMPILE_WARNING => 'Compile warning',
E_USER_ERROR => 'User error',
E_USER_WARNING => 'User warning',
E_USER_NOTICE => 'User notice',
E_STRICT => 'Strict warning',
E_RECOVERABLE_ERROR => 'Recoverable fatal error'
);
$backtrace = debug_backtrace();
Dries Buytaert
committed
Dries Buytaert
committed
$caller = _drupal_get_last_caller(debug_backtrace());
Dries Buytaert
committed
// We treat recoverable errors as fatal.
Dries Buytaert
committed
_drupal_log_error(array(
'%type' => isset($types[$error_level]) ? $types[$error_level] : 'Unknown error',
'%message' => $message,
'%function' => $caller['function'],
'%file' => $caller['file'],
'%line' => $caller['line'],
), $error_level == E_RECOVERABLE_ERROR);
Dries Buytaert
committed
}
}
/**
* Custom PHP exception handler.
*
* Uncaught exceptions are those not enclosed in a try/catch block. They are
* always fatal: the execution of the script will stop as soon as the exception
* handler exits.
*
* @param $exception
* The exception object that was thrown.
Dries Buytaert
committed
function _drupal_exception_handler($exception) {
Dries Buytaert
committed
// Log the message to the watchdog and return an error page to the user.
_drupal_log_error(_drupal_decode_exception($exception), TRUE);
}
/**
* Decode an exception, especially to retrive the correct caller.
*
* @param $exception
* The exception object that was thrown.
* @return An error in the format expected by _drupal_log_error().
*/
function _drupal_decode_exception($exception) {
$message = $exception->getMessage();
Dries Buytaert
committed
$backtrace = $exception->getTrace();
// Add the line throwing the exception to the backtrace.
array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile()));
// For PDOException errors, we try to return the initial caller,
// skipping internal functions of the database layer.
if ($exception instanceof PDOException) {
// The first element in the stack is the call, the second element gives us the caller.
// We skip calls that occurred in one of the classes of the database layer
// or in one of its global functions.
$db_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql');
Dries Buytaert
committed
while (!empty($backtrace[1]) && ($caller = $backtrace[1]) &&
((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || $caller['class'] == 'PDOStatement')) ||
in_array($caller['function'], $db_functions))) {
Dries Buytaert
committed
// We remove that call.
array_shift($backtrace);
}
if (isset($exception->query_string, $exception->args)) {
$message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE);
}
Neil Drumm
committed
}
Dries Buytaert
committed
$caller = _drupal_get_last_caller($backtrace);
Neil Drumm
committed
Dries Buytaert
committed
return array(
'%type' => get_class($exception),
'%message' => $message,
Dries Buytaert
committed
'%function' => $caller['function'],
'%file' => $caller['file'],
'%line' => $caller['line'],
);
Dries Buytaert
committed
}
Gábor Hojtsy
committed
Dries Buytaert
committed
/**
* Log a PHP error or exception, display an error page in fatal cases.
*
Dries Buytaert
committed
* @param $error
* An array with the following keys: %type, %message, %function, %file, %line.
Dries Buytaert
committed
* @param $fatal
* TRUE if the error is fatal.
*/
Dries Buytaert
committed
function _drupal_log_error($error, $fatal = FALSE) {
// Initialize a maintenance theme if the boostrap was not complete.
Dries Buytaert
committed
// Do it early because drupal_set_message() triggers a drupal_theme_initialize().
Dries Buytaert
committed
if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) {
unset($GLOBALS['theme']);
Dries Buytaert
committed
if (!defined('MAINTENANCE_MODE')) {
define('MAINTENANCE_MODE', 'error');
}
Dries Buytaert
committed
drupal_maintenance_theme();
}
Gábor Hojtsy
committed
Dries Buytaert
committed
// When running inside the testing framework, we relay the errors
// to the tested site by the way of HTTP headers.
Dries Buytaert
committed
if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+;/", $_SERVER['HTTP_USER_AGENT']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
Dries Buytaert
committed
// $number does not use drupal_static as it should not be reset
// as it uniquely identifies each PHP error.
Dries Buytaert
committed
static $number = 0;
$assertion = array(
Dries Buytaert
committed
$error['%message'],
$error['%type'],
Dries Buytaert
committed
array(
'function' => $error['%function'],
'file' => $error['%file'],
'line' => $error['%line'],
),
Dries Buytaert
committed
);
header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion)));
$number++;
}
Dries Buytaert
committed
try {
watchdog('php', '%type: %message in %function (line %line of %file).', $error, WATCHDOG_ERROR);
}
catch (Exception $e) {
// Ignore any additional watchdog exception, as that probably means
// that the database was not initialized correctly.
Dries Buytaert
committed
}
Dries Buytaert
committed
if ($fatal) {
drupal_set_header('500 Service unavailable (with message)');
}
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
if ($fatal) {
// When called from JavaScript, simply output the error message.
print t('%type: %message in %function (line %line of %file).', $error);
exit;
}
else {
// Display the message if the current error reporting level allows this type
// of message to be displayed, and unconditionnaly in update.php.
$error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL);
$display_error = $error_level == ERROR_REPORTING_DISPLAY_ALL || ($error_level == ERROR_REPORTING_DISPLAY_SOME && $error['%type'] != 'Notice');
if ($display_error || (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update')) {
drupal_set_message(t('%type: %message in %function (line %line of %file).', $error), 'error');
}
if ($fatal) {
drupal_set_title(t('Error'));
// We fallback to a maintenance page at this point, because the page generation
// itself can generate errors.
print theme('maintenance_page', t('The website encountered an unexpected error. Please try again later.'), FALSE);
exit;
Dries Buytaert
committed
}
/**
Dries Buytaert
committed
* Gets the last caller from a backtrace.
*
* @param $backtrace
* A standard PHP backtrace.
* @return
* An associative array with keys 'file', 'line' and 'function'.
*/
function _drupal_get_last_caller($backtrace) {
Angie Byron
committed
// Errors that occur inside PHP internal functions
// do not generate information about file and line.
while ($backtrace && !isset($backtrace[0]['line'])) {
array_shift($backtrace);
}
// The first trace is the call itself.
// It gives us the line and the file of the last call.
$call = $backtrace[0];
// The second call give us the function where the call originated.
if (isset($backtrace[1])) {
if (isset($backtrace[1]['class'])) {
$call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()';
}
else {
$call['function'] = $backtrace[1]['function'] . '()';
}
}
else {
$call['function'] = 'main()';
}
return $call;
}
array_walk($item, '_fix_gpc_magic');
}
else {
Neil Drumm
committed
/**
* Helper function to strip slashes from $_FILES skipping over the tmp_name keys
* since PHP generates single backslashes for file paths on Windows systems.
*
* tmp_name does not have backslashes added see
* http://php.net/manual/en/features.file-upload.php#42280
*/
function _fix_gpc_magic_files(&$item, $key) {
if ($key != 'tmp_name') {
if (is_array($item)) {
array_walk($item, '_fix_gpc_magic_files');
}
else {
$item = stripslashes($item);
}
}
}
* Fix double-escaping problems caused by "magic quotes" in some PHP installations.
Dries Buytaert
committed
$fixed = &drupal_static(__FUNCTION__, FALSE);
array_walk($_GET, '_fix_gpc_magic');
array_walk($_POST, '_fix_gpc_magic');
array_walk($_COOKIE, '_fix_gpc_magic');
array_walk($_REQUEST, '_fix_gpc_magic');
Neil Drumm
committed
array_walk($_FILES, '_fix_gpc_magic_files');
$fixed = TRUE;
Gábor Hojtsy
committed
* Translate strings to the page language or a given language.
* Human-readable text that will be displayed somewhere within a page should
* be run through the t() function.
*
* Examples:
* @code
* if (!$info || !$info['extension']) {
* form_set_error('picture_upload', t('The uploaded file was not an image.'));
* }
*
* $form['submit'] = array(
* '#type' => 'submit',
* '#value' => t('Log in'),
* );
* @endcode
*
* Any text within t() can be extracted by translators and changed into
* the equivalent text in their native language.
*
* Special variables called "placeholders" are used to signal dynamic
* information in a string which should not be translated. Placeholders
* can also be used for text that may change from time to time (such as
* link paths) to be changed without requiring updates to translations.
*
* For example:
* @code
* $output = t('There are currently %members and %visitors online.', array(
* '%members' => format_plural($total_users, '1 user', '@count users'),
* '%visitors' => format_plural($guests->count, '1 guest', '@count guests')));
* @endcode
*
* There are three styles of placeholders:
* - !variable, which indicates that the text should be inserted as-is. This is
* useful for inserting variables into things like e-mail.
* @code
* $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))));
* - @variable, which indicates that the text should be run through
* check_plain, to escape HTML characters. Use this for any output that's
* displayed within a Drupal page.
Angie Byron
committed
* drupal_set_title($title = t("@name's blog", array('@name' => $account->name)), PASS_THROUGH);
Dries Buytaert
committed
* - %variable, which indicates that the string should be HTML escaped and
* highlighted with theme_placeholder() which shows up by default as