Newer
Older
/**
* @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.
*/
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);
Gábor Hojtsy
committed
/**
* Create E_DEPRECATED constant for older PHP versions (<5.3).
*/
if (!defined('E_DEPRECATED')) {
define('E_DEPRECATED', 8192);
}
/**
* Error code indicating that the request made by drupal_http_request() exceeded
* the specified timeout.
*/
define('HTTP_REQUEST_TIMEOUT', -1);
/**
* Set content for a specified region.
*
* @param $region
* Page region the content is assigned to.
* @param $data
* Content to be set.
*/
function drupal_set_content($region = NULL, $data = NULL) {
static $content = array();
if (!is_null($region) && !is_null($data)) {
$content[$region][] = $data;
}
return $content;
}
/**
* Get assigned content.
*
* @param $region
* A specified region to fetch content for. If NULL, all regions will be
* returned.
Gábor Hojtsy
committed
* Content to be inserted between imploded array elements.
function drupal_get_content($region = NULL, $delimiter = ' ') {
$content = drupal_set_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.
function drupal_set_breadcrumb($breadcrumb = NULL) {
static $stored_breadcrumb;
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;
}
*/
function drupal_set_html_head($data = NULL) {
/**
* 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_set_html_head();
}
* Reset the static variable which holds the aliases mapped for this request.
function drupal_clear_path_cache() {
drupal_lookup_path('wipe');
* Note: When sending a Content-Type header, always include a 'charset' type,
* too. This is necessary to avoid security bugs (e.g. UTF-7 XSS).
*/
function drupal_set_header($header = NULL) {
// We use an array to guarantee there are no leading or trailing delimiters.
// Otherwise, header('') could get called when serving the page later, which
// ends HTTP headers prematurely on some PHP versions.
static $stored_headers = array();
$stored_headers[] = $header;
return implode("\n", $stored_headers);
function drupal_get_headers() {
return drupal_set_header();
}
/**
* Make any final alterations to the rendered xhtml.
*/
function drupal_final_markup($content) {
// Make sure that the charset is always specified as the first element of the
// head region to prevent encoding-based attacks.
return preg_replace('/<head[^>]*>/i', "\$0\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />", $content, 1);
}
* Add a feed URL for the current page.
*
* @param $url
* A url for the feed.
* @param $title
* The title of the feed.
function drupal_add_feed($url = NULL, $title = '') {
static $stored_feed_links = 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);
}
Gábor Hojtsy
committed
* @defgroup http_handling HTTP handling
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) {
$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 {
$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'])) {
Dries Buytaert
committed
return 'destination='. urlencode($_REQUEST['destination']);
}
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
}
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
Gábor Hojtsy
committed
* destination in either the $_REQUEST-array (i.e. by using
* the query string of an URI) or the $_REQUEST['edit']-array (i.e. by
* using a hidden form field). 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 rather than a print theme('page')
* statement in your menu callback.
*
* @param $path
* A Drupal path or a full URL.
Gábor Hojtsy
committed
* A URL-encoded query string to append to the link, or an array of query
* key/value-pairs without any URL-encoding. Passed to url().
* 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'])) {
}
else if (isset($_REQUEST['edit']['destination'])) {
$destination = $_REQUEST['edit']['destination'];
}
if ($destination) {
// Do not redirect to an absolute URL originating from user input.
$colonpos = strpos($destination, ':');
$absolute = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($destination, 0, $colonpos)));
if (!$absolute) {
extract(parse_url(urldecode($destination)));
}
}
$url = url($path, array('query' => $query, 'fragment' => $fragment, 'absolute' => TRUE));
// Remove newlines from the URL to avoid header injection attacks.
$url = str_replace(array("\n", "\r"), '', $url);
// 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
// Even though session_write_close() is registered as a shutdown function, we
// need all session data written to the database before redirecting.
Dries Buytaert
committed
session_write_close();
Dries Buytaert
committed
Dries Buytaert
committed
header('Location: '. $url, TRUE, $http_response_code);
// 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
/**
* Generates a site off-line message.
Dries Buytaert
committed
*/
function drupal_site_offline() {
drupal_maintenance_theme();
Gábor Hojtsy
committed
drupal_set_header($_SERVER['SERVER_PROTOCOL'] .' 503 Service unavailable');
drupal_set_title(t('Site off-line'));
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.
*/
Gábor Hojtsy
committed
drupal_set_header($_SERVER['SERVER_PROTOCOL'] .' 404 Not Found');
watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
// Keep old path for reference, and to allow forms to redirect to it.
if (!isset($_REQUEST['destination'])) {
$_REQUEST['destination'] = $_GET['q'];
}
Gábor Hojtsy
committed
// Set the active item in case there are tabs to display, or other
// dependencies on the path.
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) {
Gábor Hojtsy
committed
$return = t('The requested page could not be found.');
// To conserve CPU and bandwidth, omit the blocks.
Dries Buytaert
committed
print theme('page', $return, FALSE);
/**
* Generates a 403 error if the request is not allowed.
*/
function drupal_access_denied() {
Gábor Hojtsy
committed
drupal_set_header($_SERVER['SERVER_PROTOCOL'] .' 403 Forbidden');
Gábor Hojtsy
committed
watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
// Keep old path for reference, and to allow forms to redirect to it.
if (!isset($_REQUEST['destination'])) {
$_REQUEST['destination'] = $_GET['q'];
}
// Set the active item in case there are tabs to display or other
Gábor Hojtsy
committed
// dependencies on the path.
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) {
drupal_set_title(t('Access denied'));
$return = t('You are not authorized to access this page.');
print theme('page', $return);
* This is a flexible and powerful HTTP client implementation. Correctly handles
* GET, POST, PUT or any other HTTP requests. Handles redirects.
*
* @param $url
* A string containing a fully qualified URI.
* @param $headers
* An array containing an HTTP header => value pair.
* @param $method
* A string defining the HTTP request to use.
* @param $data
* A string containing data to include in the request.
* @param $retry
* An integer representing how many times to retry the request in case of a
* redirect.
* @param $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.
* An object containing the HTTP request headers, response code, protocol,
* status message, headers, data and redirect status.
function drupal_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3, $timeout = 30.0) {
Gábor Hojtsy
committed
global $db_prefix;
$result = new stdClass();
// Parse the URL and make sure we can handle the schema.
Dries Buytaert
committed
if ($uri == FALSE) {
$result->error = 'unable to parse URL';
Dries Buytaert
committed
$result->code = -1001;
Dries Buytaert
committed
return $result;
}
if (!isset($uri['scheme'])) {
$result->error = 'missing schema';
Dries Buytaert
committed
$result->code = -1002;
Dries Buytaert
committed
return $result;
}
timer_start(__FUNCTION__);
case 'feed':
$port = isset($uri['port']) ? $uri['port'] : 80;
$host = $uri['host'] . ($port != 80 ? ':'. $port : '');
$fp = @fsockopen($uri['host'], $port, $errno, $errstr, $timeout);
$port = isset($uri['port']) ? $uri['port'] : 443;
$host = $uri['host'] . ($port != 443 ? ':'. $port : '');
$fp = @fsockopen('ssl://'. $uri['host'], $port, $errno, $errstr, $timeout);
Dries Buytaert
committed
$result->code = -1003;
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);
Gábor Hojtsy
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'])) {
// RFC 2616: "non-standard ports MUST, default ports MAY be included".
// We don't add the port to prevent from breaking rewrite rules checking the
// host that do not take into account the port number.
'Host' => "Host: $host",
Dries Buytaert
committed
'User-Agent' => 'User-Agent: Drupal (+http://drupal.org/)',
Gábor Hojtsy
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($data);
if ($content_length > 0 || $method == 'POST' || $method == 'PUT') {
$defaults['Content-Length'] = 'Content-Length: '. $content_length;
}
// If the server url has a user then attempt to use basic authentication
if (isset($uri['user'])) {
$defaults['Authorization'] = 'Authorization: Basic '. base64_encode($uri['user'] . (!empty($uri['pass']) ? ":". $uri['pass'] : ''));
}
Gábor Hojtsy
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.
Gábor Hojtsy
committed
if (is_string($db_prefix) && preg_match("/^simpletest\d+$/", $db_prefix, $matches)) {
Dries Buytaert
committed
$defaults['User-Agent'] = 'User-Agent: ' . $matches[0];
Gábor Hojtsy
committed
}
$request = $method .' '. $path ." HTTP/1.0\r\n";
$request .= $data;
// Calculate how much time is left of the original timeout value.
$time_left = $timeout - timer_read(__FUNCTION__) / 1000;
if ($time_left > 0) {
stream_set_timeout($fp, floor($time_left), floor(1000000 * fmod($time_left, 1)));
fwrite($fp, $request);
}
while (!feof($fp)) {
// Calculate how much time is left of the original timeout value.
$time_left = $timeout - timer_read(__FUNCTION__) / 1000;
if ($time_left <= 0) {
$result->code = HTTP_REQUEST_TIMEOUT;
$result->error = 'request timed out';
return $result;
}
stream_set_timeout($fp, floor($time_left), floor(1000000 * fmod($time_left, 1)));
$chunk = fread($fp, 1024);
Dries Buytaert
committed
$response .= $chunk;
Gábor Hojtsy
committed
// Parse response headers from the response body.
// Be tolerant of malformed HTTP responses that separate header and body with
// \n\n or \r\r instead of \r\n\r\n. See http://drupal.org/node/183435
list($split, $result->data) = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2);
Dries Buytaert
committed
$split = preg_split("/\r\n|\n|\r/", $split);
list($protocol, $code, $status_message) = explode(' ', trim(array_shift($split)), 3);
$result->protocol = $protocol;
$result->status_message = $status_message;
Dries Buytaert
committed
while ($line = trim(array_shift($split))) {
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);
}
}
$responses = array(
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;
}
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'];
$timeout -= timer_read(__FUNCTION__) / 1000;
if ($timeout <= 0) {
$result->code = HTTP_REQUEST_TIMEOUT;
$result->error = 'request timed out';
}
elseif ($retry) {
$result = drupal_http_request($result->headers['Location'], $headers, $method, $data, --$retry, $timeout);
$result->redirect_code = $result->code;
}
$result->redirect_url = $location;
break;
default:
$result->error = $status_message;
* Log errors as defined by administrator.
* - 0 = Log errors to database.
* - 1 = Log errors to database and to screen.
Gábor Hojtsy
committed
function drupal_error_handler($errno, $message, $filename, $line, $context) {
Gábor Hojtsy
committed
// If the @ error suppression operator was used, error_reporting will have
// been temporarily set to 0.
Neil Drumm
committed
if (error_reporting() == 0) {
return;
}
Dries Buytaert
committed
$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');
Gábor Hojtsy
committed
// For database errors, we want the line number/file name of the place that
// the query was originally called, not _db_query().
if (isset($context[DB_ERROR])) {
$backtrace = array_reverse(debug_backtrace());
// List of functions where SQL queries can originate.
$query_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql');
// Determine where query function was called, and adjust line/file
// accordingly.
foreach ($backtrace as $index => $function) {
if (in_array($function['function'], $query_functions)) {
$line = $backtrace[$index]['line'];
$filename = $backtrace[$index]['file'];
break;
}
}
}
// Try to use filter_xss(). If it's too early in the bootstrap process for
// filter_xss() to be loaded, use check_plain() instead.
$entry = check_plain($types[$errno]) .': '. (function_exists('filter_xss') ? filter_xss($message) : check_plain($message)) .' in '. check_plain($filename) .' on line '. check_plain($line) .'.';
// Force display of error messages in update.php.
if (variable_get('error_level', 1) == 1 || strstr($_SERVER['SCRIPT_NAME'], 'update.php')) {
drupal_set_message($entry, 'error');
watchdog('php', '%message in %file on line %line.', array('%error' => $types[$errno], '%message' => $message, '%file' => $filename, '%line' => $line), WATCHDOG_ERROR);
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.
static $fixed = 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.
* @code
* drupal_set_title($title = t("@name's blog", array('@name' => $account->name)));
* @endcode
*
Dries Buytaert
committed
* - %variable, which indicates that the string should be HTML escaped and
* highlighted with theme_placeholder() which shows up by default as
* <em>emphasized</em>.
* $message = t('%name-from sent %name-to an e-mail.', array('%name-from' => $user->name, '%name-to' => $account->name));
* When using t(), try to put entire sentences and strings in one t() call.
* This makes it easier for translators, as it provides context as to what
* each word refers to. HTML markup within translation strings is allowed, but
* should be avoided if possible. The exception are embedded links; link
* titles add a context for translators, so should be kept in the main string.
* Here is an example of incorrect usage of t():
* @code
* $output .= t('<p>Go to the @contact-page.</p>', array('@contact-page' => l(t('contact page'), 'contact')));
* @endcode
*
* Here is an example of t() used correctly:
* @code
* $output .= '<p>'. t('Go to the <a href="@contact-page">contact page</a>.', array('@contact-page' => url('contact'))) .'</p>';
* @endcode
*
* Avoid escaping quotation marks wherever possible.
*
* Incorrect:
* @code
* $output .= t('Don\'t click me.');
* @endcode
*
* Correct:
* $output .= t("Don't click me.");
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
* Because t() is designed for handling code-based strings, in almost all
* cases, the actual string and not a variable must be passed through t().
*
* Extraction of translations is done based on the strings contained in t()
* calls. If a variable is passed through t(), the content of the variable
* cannot be extracted from the file for translation.
*
* Incorrect:
* @code
* $message = 'An error occurred.';
* drupal_set_message(t($message), 'error');
* $output .= t($message);
* @endcode
*
* Correct:
* @code
* $message = t('An error occurred.');
* drupal_set_message($message, 'error');
* $output .= $message;
* @endcode
*
* The only case in which variables can be passed safely through t() is when
* code-based versions of the same strings will be passed through t() (or
* otherwise extracted) elsewhere.
*
* In some cases, modules may include strings in code that can't use t()
* calls. For example, a module may use an external PHP application that
* produces strings that are loaded into variables in Drupal for output.
* In these cases, module authors may include a dummy file that passes the
* relevant strings through t(). This approach will allow the strings to be
* extracted.
*
* Sample external (non-Drupal) code:
* @code
* class Time {
* public $yesterday = 'Yesterday';
* public $today = 'Today';
* public $tomorrow = 'Tomorrow';
* }
* @endcode
*
* Sample dummy file.
* @code
* // Dummy function included in example.potx.inc.
* function example_potx() {
* $strings = array(
* t('Yesterday'),
* t('Today'),
* t('Tomorrow'),
* );
* // No return value needed, since this is a dummy function.
* }
* @endcode
*
* Having passed strings through t() in a dummy function, it is then
* okay to pass variables through t().
*
* Correct (if a dummy file was used):
* @code
* $time = new Time();
* $output .= t($time->today);
* @endcode
*
* However tempting it is, custom data from user input or other non-code
* sources should not be passed through t(). Doing so leads to the following
* problems and errors:
* - The t() system doesn't support updates to existing strings. When user
* data is updated, the next time it's passed through t() a new record is
* created instead of an update. The database bloats over time and any
* existing translations are orphaned with each update.
* - The t() system assumes any data it receives is in English. User data may
* be in another language, producing translation errors.
* - The "Built-in interface" text group in the locale system is used to
* produce translations for storage in .po files. When non-code strings are
* passed through t(), they are added to this text group, which is rendered
* inaccurate since it is a mix of actual interface strings and various user
* input strings of uncertain origin.
*
* Incorrect:
* @code
* $item = item_load();
* $output .= check_plain(t($item['title']));
* @endcode
*
* Instead, translation of these data can be done through the locale system,
* either directly or through helper functions provided by contributed
* modules.
* @see hook_locale()
*
* During installation, st() is used in place of t(). Code that may be called
* during installation or during normal operation should use the get_t()
* helper function.
* @see st()
* @see get_t()
*
* @param $args
* An associative array of replacements to make after translation. Incidences
* of any key in this array are replaced with the corresponding value. Based
* on the first character of the key, the value is escaped and/or themed:
* - !variable: inserted as is
* - @variable: escape plain text to HTML (check_plain)
* - %variable: escape text and theme as a placeholder for user-submitted
* content (check_plain + theme_placeholder)
Gábor Hojtsy
committed
* @param $langcode
* Optional language code to translate to a language other than what is used
* to display the page.
Gábor Hojtsy
committed
function t($string, $args = array(), $langcode = NULL) {
global $language;
static $custom_strings;
Gábor Hojtsy
committed
$langcode = isset($langcode) ? $langcode : $language->language;
// First, check for an array of customized strings. If present, use the array
// *instead of* database lookups. This is a high performance way to provide a
// handful of string replacements. See settings.php for examples.
// Cache the $custom_strings variable to improve performance.
Gábor Hojtsy
committed
if (!isset($custom_strings[$langcode])) {
$custom_strings[$langcode] = variable_get('locale_custom_strings_'. $langcode, array());
}
// Custom strings work for English too, even if locale module is disabled.
Gábor Hojtsy
committed
if (isset($custom_strings[$langcode][$string])) {
$string = $custom_strings[$langcode][$string];
}
// Translate with locale module if enabled.
Gábor Hojtsy
committed
elseif (function_exists('locale') && $langcode != 'en') {
$string = locale($string, $langcode);
Gábor Hojtsy
committed
if (empty($args)) {
// Transform arguments before inserting them.
foreach ($args as $key => $value) {
Neil Drumm
committed
switch ($key[0]) {
case '@':
// Escaped only.
Neil Drumm
committed
$args[$key] = check_plain($value);
break;
Neil Drumm
committed
case '%':
default:
// Escaped and placeholder.
Neil Drumm
committed
$args[$key] = theme('placeholder', $value);
break;
Neil Drumm
committed
case '!':
// Pass-through.
Neil Drumm
committed
}
}
Dries Buytaert
committed
/**
Gábor Hojtsy
committed
* Verifies the syntax of the given e-mail address.
Gábor Hojtsy
committed
* See RFC 2822 for details.
Dries Buytaert
committed
*
* A string containing an e-mail address.
Gábor Hojtsy
committed
* 1 if the email address is valid, 0 if it is invalid or empty, and FALSE if
* there is an input error (such as passing in an array instead of a string).
Dries Buytaert
committed
*/