core security update

This commit is contained in:
Bachir Soussi Chiadmi
2016-10-13 12:11:14 +02:00
parent 747127f643
commit 1a06561593
306 changed files with 7346 additions and 2431 deletions

View File

@@ -230,6 +230,10 @@
* functions.
*/
function ajax_render($commands = array()) {
// Although ajax_deliver() does this, some contributed and custom modules
// render Ajax responses without using that delivery callback.
ajax_set_verification_header();
// Ajax responses aren't rendered with html.tpl.php, so we have to call
// drupal_get_css() and drupal_get_js() here, in order to have new files added
// during this request to be loaded by the page. We only want to send back
@@ -390,7 +394,7 @@ function ajax_form_callback() {
if (!empty($form_state['triggering_element'])) {
$callback = $form_state['triggering_element']['#ajax']['callback'];
}
if (!empty($callback) && function_exists($callback)) {
if (!empty($callback) && is_callable($callback)) {
$result = $callback($form, $form_state);
if (!(is_array($result) && isset($result['#type']) && $result['#type'] == 'ajax')) {
@@ -487,6 +491,9 @@ function ajax_deliver($page_callback_result) {
}
}
// Let ajax.js know that this response is safe to process.
ajax_set_verification_header();
// Print the response.
$commands = ajax_prepare_response($page_callback_result);
$json = ajax_render($commands);
@@ -576,6 +583,29 @@ function ajax_prepare_response($page_callback_result) {
return $commands;
}
/**
* Sets a response header for ajax.js to trust the response body.
*
* It is not safe to invoke Ajax commands within user-uploaded files, so this
* header protects against those being invoked.
*
* @see Drupal.ajax.options.success()
*/
function ajax_set_verification_header() {
$added = &drupal_static(__FUNCTION__);
// User-uploaded files cannot set any response headers, so a custom header is
// used to indicate to ajax.js that this response is safe. Note that most
// Ajax requests bound using the Form API will be protected by having the URL
// flagged as trusted in Drupal.settings, so this header is used only for
// things like custom markup that gets Ajax behaviors attached.
if (empty($added)) {
drupal_add_http_header('X-Drupal-Ajax-Token', '1');
// Avoid sending the header twice.
$added = TRUE;
}
}
/**
* Performs end-of-Ajax-request tasks.
*
@@ -764,7 +794,12 @@ function ajax_pre_render_element($element) {
$element['#attached']['js'][] = array(
'type' => 'setting',
'data' => array('ajax' => array($element['#id'] => $settings)),
'data' => array(
'ajax' => array($element['#id'] => $settings),
'urlIsAjaxTrusted' => array(
$settings['url'] => TRUE,
),
),
);
// Indicate that Ajax processing was successful.

View File

@@ -460,10 +460,10 @@ function _batch_finished() {
if (isset($batch_set['file']) && is_file($batch_set['file'])) {
include_once DRUPAL_ROOT . '/' . $batch_set['file'];
}
if (function_exists($batch_set['finished'])) {
if (is_callable($batch_set['finished'])) {
$queue = _batch_queue($batch_set);
$operations = $queue->getAllItems();
$batch_set['finished']($batch_set['success'], $batch_set['results'], $operations, format_interval($batch_set['elapsed'] / 1000));
call_user_func($batch_set['finished'], $batch_set['success'], $batch_set['results'], $operations, format_interval($batch_set['elapsed'] / 1000));
}
}
}

View File

@@ -8,7 +8,7 @@
/**
* The current system version.
*/
define('VERSION', '7.36');
define('VERSION', '7.51');
/**
* Core API compatibility.
@@ -828,14 +828,21 @@ function drupal_settings_initialize() {
* @param $filename
* The filename of the item if it is to be set explicitly rather
* than by consulting the database.
* @param bool $trigger_error
* Whether to trigger an error when a file is missing or has unexpectedly
* moved. This defaults to TRUE, but can be set to FALSE by calling code that
* merely wants to check whether an item exists in the filesystem.
*
* @return
* The filename of the requested item or NULL if the item is not found.
*/
function drupal_get_filename($type, $name, $filename = NULL) {
function drupal_get_filename($type, $name, $filename = NULL, $trigger_error = TRUE) {
// The $files static variable will hold the locations of all requested files.
// We can be sure that any file listed in this static variable actually
// exists as all additions have gone through a file_exists() check.
// The location of files will not change during the request, so do not use
// drupal_static().
static $files = array(), $dirs = array();
static $files = array();
// Profiles are a special case: they have a fixed location and naming.
if ($type == 'profile') {
@@ -847,59 +854,41 @@ function drupal_get_filename($type, $name, $filename = NULL) {
}
if (!empty($filename) && file_exists($filename)) {
// Prime the static cache with the provided filename.
$files[$type][$name] = $filename;
}
elseif (isset($files[$type][$name])) {
// nothing
// This item had already been found earlier in the request, either through
// priming of the static cache (for example, in system_list()), through a
// lookup in the {system} table, or through a file scan (cached or not). Do
// nothing.
}
// Verify that we have an active database connection, before querying
// the database. This is required because this function is called both
// before we have a database connection (i.e. during installation) and
// when a database connection fails.
else {
// Look for the filename listed in the {system} table. Verify that we have
// an active database connection before doing so, since this function is
// called both before we have a database connection (i.e. during
// installation) and when a database connection fails.
$database_unavailable = TRUE;
try {
if (function_exists('db_query')) {
$file = db_query("SELECT filename FROM {system} WHERE name = :name AND type = :type", array(':name' => $name, ':type' => $type))->fetchField();
if ($file !== FALSE && file_exists(DRUPAL_ROOT . '/' . $file)) {
$files[$type][$name] = $file;
}
$database_unavailable = FALSE;
}
}
catch (Exception $e) {
// The database table may not exist because Drupal is not yet installed,
// or the database might be down. We have a fallback for this case so we
// hide the error completely.
// the database might be down, or we may have done a non-database cache
// flush while $conf['page_cache_without_database'] = TRUE and
// $conf['page_cache_invoke_hooks'] = TRUE. We have a fallback for these
// cases so we hide the error completely.
}
// Fallback to searching the filesystem if the database could not find the
// file or the file returned by the database is not found.
// Fall back to searching the filesystem if the database could not find the
// file or the file does not exist at the path returned by the database.
if (!isset($files[$type][$name])) {
// We have a consistent directory naming: modules, themes...
$dir = $type . 's';
if ($type == 'theme_engine') {
$dir = 'themes/engines';
$extension = 'engine';
}
elseif ($type == 'theme') {
$extension = 'info';
}
else {
$extension = $type;
}
if (!isset($dirs[$dir][$extension])) {
$dirs[$dir][$extension] = TRUE;
if (!function_exists('drupal_system_listing')) {
require_once DRUPAL_ROOT . '/includes/common.inc';
}
// Scan the appropriate directories for all files with the requested
// extension, not just the file we are currently looking for. This
// prevents unnecessary scans from being repeated when this function is
// called more than once in the same page request.
$matches = drupal_system_listing("/^" . DRUPAL_PHP_FUNCTION_PATTERN . "\.$extension$/", $dir, 'name', 0);
foreach ($matches as $matched_name => $file) {
$files[$type][$matched_name] = $file->uri;
}
}
$files[$type][$name] = _drupal_get_filename_fallback($type, $name, $trigger_error, $database_unavailable);
}
}
@@ -908,6 +897,256 @@ function drupal_get_filename($type, $name, $filename = NULL) {
}
}
/**
* Performs a cached file system scan as a fallback when searching for a file.
*
* This function looks for the requested file by triggering a file scan,
* caching the new location if the file has moved and caching the miss
* if the file is missing. If a file had been marked as missing in a previous
* file scan, or if it has been marked as moved and is still in the last known
* location, no new file scan will be performed.
*
* @param string $type
* The type of the item (theme, theme_engine, module, profile).
* @param string $name
* The name of the item for which the filename is requested.
* @param bool $trigger_error
* Whether to trigger an error when a file is missing or has unexpectedly
* moved.
* @param bool $database_unavailable
* Whether this function is being called because the Drupal database could
* not be queried for the file's location.
*
* @return
* The filename of the requested item or NULL if the item is not found.
*
* @see drupal_get_filename()
*/
function _drupal_get_filename_fallback($type, $name, $trigger_error, $database_unavailable) {
$file_scans = &_drupal_file_scan_cache();
$filename = NULL;
// If the cache indicates that the item is missing, or we can verify that the
// item exists in the location the cache says it exists in, use that.
if (isset($file_scans[$type][$name]) && ($file_scans[$type][$name] === FALSE || file_exists($file_scans[$type][$name]))) {
$filename = $file_scans[$type][$name];
}
// Otherwise, perform a new file scan to find the item.
else {
$filename = _drupal_get_filename_perform_file_scan($type, $name);
// Update the static cache, and mark the persistent cache for updating at
// the end of the page request. See drupal_file_scan_write_cache().
$file_scans[$type][$name] = $filename;
$file_scans['#write_cache'] = TRUE;
}
// If requested, trigger a user-level warning about the missing or
// unexpectedly moved file. If the database was unavailable, do not trigger a
// warning in the latter case, though, since if the {system} table could not
// be queried there is no way to know if the location found here was
// "unexpected" or not.
if ($trigger_error) {
$error_type = $filename === FALSE ? 'missing' : 'moved';
if ($error_type == 'missing' || !$database_unavailable) {
_drupal_get_filename_fallback_trigger_error($type, $name, $error_type);
}
}
// The cache stores FALSE for files that aren't found (to be able to
// distinguish them from files that have not yet been searched for), but
// drupal_get_filename() expects NULL for these instead, so convert to NULL
// before returning.
if ($filename === FALSE) {
$filename = NULL;
}
return $filename;
}
/**
* Returns the current list of cached file system scan results.
*
* @return
* An associative array tracking the most recent file scan results for all
* files that have had scans performed. The keys are the type and name of the
* item that was searched for, and the values can be either:
* - Boolean FALSE if the item was not found in the file system.
* - A string pointing to the location where the item was found.
*/
function &_drupal_file_scan_cache() {
$file_scans = &drupal_static(__FUNCTION__, array());
// The file scan results are stored in a persistent cache (in addition to the
// static cache) but because this function can be called before the
// persistent cache is available, we must merge any items that were found
// earlier in the page request into the results from the persistent cache.
if (!isset($file_scans['#cache_merge_done'])) {
try {
if (function_exists('cache_get')) {
$cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap');
if (!empty($cache->data)) {
// File scan results from the current request should take precedence
// over the results from the persistent cache, since they are newer.
$file_scans = drupal_array_merge_deep($cache->data, $file_scans);
}
// Set a flag to indicate that the persistent cache does not need to be
// merged again.
$file_scans['#cache_merge_done'] = TRUE;
}
}
catch (Exception $e) {
// Hide the error.
}
}
return $file_scans;
}
/**
* Performs a file system scan to search for a system resource.
*
* @param $type
* The type of the item (theme, theme_engine, module, profile).
* @param $name
* The name of the item for which the filename is requested.
*
* @return
* The filename of the requested item or FALSE if the item is not found.
*
* @see drupal_get_filename()
* @see _drupal_get_filename_fallback()
*/
function _drupal_get_filename_perform_file_scan($type, $name) {
// The location of files will not change during the request, so do not use
// drupal_static().
static $dirs = array(), $files = array();
// We have a consistent directory naming: modules, themes...
$dir = $type . 's';
if ($type == 'theme_engine') {
$dir = 'themes/engines';
$extension = 'engine';
}
elseif ($type == 'theme') {
$extension = 'info';
}
else {
$extension = $type;
}
// Check if we had already scanned this directory/extension combination.
if (!isset($dirs[$dir][$extension])) {
// Log that we have now scanned this directory/extension combination
// into a static variable so as to prevent unnecessary file scans.
$dirs[$dir][$extension] = TRUE;
if (!function_exists('drupal_system_listing')) {
require_once DRUPAL_ROOT . '/includes/common.inc';
}
// Scan the appropriate directories for all files with the requested
// extension, not just the file we are currently looking for. This
// prevents unnecessary scans from being repeated when this function is
// called more than once in the same page request.
$matches = drupal_system_listing("/^" . DRUPAL_PHP_FUNCTION_PATTERN . "\.$extension$/", $dir, 'name', 0);
foreach ($matches as $matched_name => $file) {
// Log the locations found in the file scan into a static variable.
$files[$type][$matched_name] = $file->uri;
}
}
// Return the results of the file system scan, or FALSE to indicate the file
// was not found.
return isset($files[$type][$name]) ? $files[$type][$name] : FALSE;
}
/**
* Triggers a user-level warning for missing or unexpectedly moved files.
*
* @param $type
* The type of the item (theme, theme_engine, module, profile).
* @param $name
* The name of the item for which the filename is requested.
* @param $error_type
* The type of the error ('missing' or 'moved').
*
* @see drupal_get_filename()
* @see _drupal_get_filename_fallback()
*/
function _drupal_get_filename_fallback_trigger_error($type, $name, $error_type) {
// Hide messages due to known bugs that will appear on a lot of sites.
// @todo Remove this in https://www.drupal.org/node/2383823
if (empty($name)) {
return;
}
// Make sure we only show any missing or moved file errors only once per
// request.
static $errors_triggered = array();
if (empty($errors_triggered[$type][$name][$error_type])) {
// Use _drupal_trigger_error_with_delayed_logging() here since these are
// triggered during low-level operations that cannot necessarily be
// interrupted by a watchdog() call.
if ($error_type == 'missing') {
_drupal_trigger_error_with_delayed_logging(format_string('The following @type is missing from the file system: %name. For information about how to fix this, see <a href="@documentation">the documentation page</a>.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING);
}
elseif ($error_type == 'moved') {
_drupal_trigger_error_with_delayed_logging(format_string('The following @type has moved within the file system: %name. In order to fix this, clear caches or put the @type back in its original location. For more information, see <a href="@documentation">the documentation page</a>.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING);
}
$errors_triggered[$type][$name][$error_type] = TRUE;
}
}
/**
* Invokes trigger_error() with logging delayed until the end of the request.
*
* This is an alternative to PHP's trigger_error() function which can be used
* during low-level Drupal core operations that need to avoid being interrupted
* by a watchdog() call.
*
* Normally, Drupal's error handler calls watchdog() in response to a
* trigger_error() call. However, this invokes hook_watchdog() which can run
* arbitrary code. If the trigger_error() happens in the middle of an
* operation such as a rebuild operation which should not be interrupted by
* arbitrary code, that could potentially break or trigger the rebuild again.
* This function protects against that by delaying the watchdog() call until
* the end of the current page request.
*
* This is an internal function which should only be called by low-level Drupal
* core functions. It may be removed in a future Drupal 7 release.
*
* @param string $error_msg
* The error message to trigger. As with trigger_error() itself, this is
* limited to 1024 bytes; additional characters beyond that will be removed.
* @param int $error_type
* (optional) The type of error. This should be one of the E_USER family of
* constants. As with trigger_error() itself, this defaults to E_USER_NOTICE
* if not provided.
*
* @see _drupal_log_error()
*/
function _drupal_trigger_error_with_delayed_logging($error_msg, $error_type = E_USER_NOTICE) {
$delay_logging = &drupal_static(__FUNCTION__, FALSE);
$delay_logging = TRUE;
trigger_error($error_msg, $error_type);
$delay_logging = FALSE;
}
/**
* Writes the file scan cache to the persistent cache.
*
* This cache stores all files marked as missing or moved after a file scan
* to prevent unnecessary file scans in subsequent requests. This cache is
* cleared in system_list_reset() (i.e. after a module/theme rebuild).
*/
function drupal_file_scan_write_cache() {
// Only write to the persistent cache if requested, and if we know that any
// data previously in the cache was successfully loaded and merged in by
// _drupal_file_scan_cache().
$file_scans = &_drupal_file_scan_cache();
if (isset($file_scans['#write_cache']) && isset($file_scans['#cache_merge_done'])) {
unset($file_scans['#write_cache']);
cache_set('_drupal_file_scan_cache', $file_scans, 'cache_bootstrap');
}
}
/**
* Loads the persistent variable table.
*
@@ -1055,7 +1294,7 @@ function drupal_page_get_cache($check_only = FALSE) {
* Determines the cacheability of the current page.
*
* @param $allow_caching
* Set to FALSE if you want to prevent this page to get cached.
* Set to FALSE if you want to prevent this page from being cached.
*
* @return
* TRUE if the current page can be cached, FALSE otherwise.
@@ -1261,7 +1500,11 @@ function drupal_page_header() {
$default_headers = array(
'Expires' => 'Sun, 19 Nov 1978 05:00:00 GMT',
'Cache-Control' => 'no-cache, must-revalidate, post-check=0, pre-check=0',
'Cache-Control' => 'no-cache, must-revalidate',
// Prevent browsers from sniffing a response and picking a MIME type
// different from the declared content-type, since that can lead to
// XSS and other vulnerabilities.
'X-Content-Type-Options' => 'nosniff',
);
drupal_send_headers($default_headers);
}
@@ -1435,6 +1678,23 @@ function drupal_unpack($obj, $field = 'data') {
* available to code that needs localization. See st() and get_t() for
* alternatives.
*
* @section sec_context String context
* Matching source strings are normally only translated once, and the same
* translation is used everywhere that has a matching string. However, in some
* cases, a certain English source string needs to have multiple translations.
* One example of this is the string "May", which could be used as either a
* full month name or a 3-letter abbreviated month. In other languages where
* the month name for May has more than 3 letters, you would need to provide
* two different translations (one for the full name and one abbreviated), and
* the correct form would need to be chosen, depending on how "May" is being
* used. To facilitate this, the "May" string should be provided with two
* different contexts in the $options parameter when calling t(). For example:
* @code
* t('May', array(), array('context' => 'Long month name')
* t('May', array(), array('context' => 'Abbreviated month name')
* @endcode
* See https://localize.drupal.org/node/2109 for more information.
*
* @param $string
* A string containing the English string to translate.
* @param $args
@@ -1445,8 +1705,9 @@ function drupal_unpack($obj, $field = 'data') {
* An associative array of additional options, with the following elements:
* - 'langcode' (defaults to the current language): The language code to
* translate to a language other than what is used to display the page.
* - 'context' (defaults to the empty context): The context the source string
* belongs to.
* - 'context' (defaults to the empty context): A string giving the context
* that the source string belongs to. See @ref sec_context above for more
* information.
*
* @return
* The translated string.
@@ -1776,7 +2037,7 @@ function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NO
* @see theme_status_messages()
*/
function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) {
if ($message) {
if ($message || $message === '0' || $message === 0) {
if (!isset($_SESSION['messages'][$type])) {
$_SESSION['messages'][$type] = array();
}
@@ -2464,6 +2725,9 @@ function _drupal_bootstrap_database() {
// the install or upgrade process.
spl_autoload_register('drupal_autoload_class');
spl_autoload_register('drupal_autoload_interface');
if (version_compare(PHP_VERSION, '5.4') >= 0) {
spl_autoload_register('drupal_autoload_trait');
}
}
/**
@@ -2779,10 +3043,14 @@ function language_list($field = 'language') {
}
/**
* Returns the default language used on the site
* Returns the default language, as an object, or one of its properties.
*
* @param $property
* Optional property of the language object to return
* (optional) The property of the language object to return.
*
* @return
* Either the language object for the default language used on the site,
* or the property of that object named in the $property parameter.
*/
function language_default($property = NULL) {
$language = variable_get('language_default', (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => ''));
@@ -2934,8 +3202,15 @@ function ip_address() {
// Eliminate all trusted IPs.
$untrusted = array_diff($forwarded, $reverse_proxy_addresses);
// The right-most IP is the most specific we can trust.
$ip_address = array_pop($untrusted);
if (!empty($untrusted)) {
// The right-most IP is the most specific we can trust.
$ip_address = array_pop($untrusted);
}
else {
// All IP addresses in the forwarded array are configured proxy IPs
// (and thus trusted). We take the leftmost IP.
$ip_address = array_shift($forwarded);
}
}
}
}
@@ -2952,7 +3227,9 @@ function ip_address() {
* Gets the schema definition of a table, or the whole database schema.
*
* The returned schema will include any modifications made by any
* module that implements hook_schema_alter().
* module that implements hook_schema_alter(). To get the schema without
* modifications, use drupal_get_schema_unprocessed().
*
*
* @param $table
* The name of the table. If not given, the schema of all tables is returned.
@@ -3107,6 +3384,22 @@ function drupal_autoload_class($class) {
return _registry_check_code('class', $class);
}
/**
* Confirms that a trait is available.
*
* This function is rarely called directly. Instead, it is registered as an
* spl_autoload() handler, and PHP calls it for us when necessary.
*
* @param string $trait
* The name of the trait to check or load.
*
* @return bool
* TRUE if the trait is currently available, FALSE otherwise.
*/
function drupal_autoload_trait($trait) {
return _registry_check_code('trait', $trait);
}
/**
* Checks for a resource in the registry.
*
@@ -3125,7 +3418,7 @@ function drupal_autoload_class($class) {
function _registry_check_code($type, $name = NULL) {
static $lookup_cache, $cache_update_needed;
if ($type == 'class' && class_exists($name) || $type == 'interface' && interface_exists($name)) {
if ($type == 'class' && class_exists($name) || $type == 'interface' && interface_exists($name) || $type == 'trait' && trait_exists($name)) {
return TRUE;
}
@@ -3158,7 +3451,7 @@ function _registry_check_code($type, $name = NULL) {
$cache_key = $type[0] . $name;
if (isset($lookup_cache[$cache_key])) {
if ($lookup_cache[$cache_key]) {
require_once DRUPAL_ROOT . '/' . $lookup_cache[$cache_key];
include_once DRUPAL_ROOT . '/' . $lookup_cache[$cache_key];
}
return (bool) $lookup_cache[$cache_key];
}
@@ -3183,7 +3476,7 @@ function _registry_check_code($type, $name = NULL) {
$lookup_cache[$cache_key] = $file;
if ($file) {
require_once DRUPAL_ROOT . '/' . $file;
include_once DRUPAL_ROOT . '/' . $file;
return TRUE;
}
else {

View File

@@ -14,6 +14,7 @@
*
* @param $bin
* The cache bin for which the cache object should be returned.
*
* @return DrupalCacheInterface
* The cache object associated with the specified bin.
*

View File

@@ -688,6 +688,13 @@ function drupal_goto($path = '', array $options = array(), $http_response_code =
$options['fragment'] = $destination['fragment'];
}
// In some cases modules call drupal_goto(current_path()). We need to ensure
// that such a redirect is not to an external URL.
if ($path === current_path() && empty($options['external']) && url_is_external($path)) {
// Force url() to generate a non-external URL.
$options['external'] = FALSE;
}
drupal_alter('drupal_goto', $path, $options, $http_response_code);
// The 'Location' HTTP header must be absolute.
@@ -753,7 +760,8 @@ function drupal_access_denied() {
* - 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, formatted as
* 'param=value&param=value&...'. Defaults to NULL.
* 'param=value&param=value&...'; to generate this, use http_build_query().
* Defaults to NULL.
* - max_redirects: An integer representing how many times a redirect
* may be followed. Defaults to 3.
* - timeout: A float representing the maximum number of seconds the function
@@ -778,6 +786,8 @@ function drupal_access_denied() {
* HTTP header names are case-insensitive (RFC 2616, section 4.2), so for
* easy access the array keys are returned in lower case.
* - data: A string containing the response body that was received.
*
* @see http_build_query()
*/
function drupal_http_request($url, array $options = array()) {
// Allow an alternate HTTP client library to replace Drupal's default
@@ -1057,6 +1067,12 @@ function drupal_http_request($url, array $options = array()) {
switch ($code) {
case 200: // OK
case 201: // Created
case 202: // Accepted
case 203: // Non-Authoritative Information
case 204: // No Content
case 205: // Reset Content
case 206: // Partial Content
case 304: // Not modified
break;
case 301: // Moved permanently
@@ -1522,7 +1538,7 @@ function _filter_xss_split($m, $store = FALSE) {
return '&lt;';
}
if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) {
if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9\-]+)([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) {
// Seriously malformed.
return '';
}
@@ -1754,9 +1770,15 @@ function format_rss_item($title, $link, $description, $args = array()) {
* - 'key': element name
* - 'value': element contents
* - 'attributes': associative array of element attributes
* - 'encoded': TRUE if 'value' is already encoded
*
* In both cases, 'value' can be a simple string, or it can be another array
* with the same format as $array itself for nesting.
*
* If 'encoded' is TRUE it is up to the caller to ensure that 'value' is either
* entity-encoded or CDATA-escaped. Using this option is not recommended when
* working with untrusted user input, since failing to escape the data
* correctly has security implications.
*/
function format_xml_elements($array) {
$output = '';
@@ -1769,7 +1791,7 @@ function format_xml_elements($array) {
}
if (isset($value['value']) && $value['value'] != '') {
$output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : check_plain($value['value'])) . '</' . $value['key'] . ">\n";
$output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : (!empty($value['encoded']) ? $value['value'] : check_plain($value['value']))) . '</' . $value['key'] . ">\n";
}
else {
$output .= " />\n";
@@ -2214,20 +2236,8 @@ function url($path = NULL, array $options = array()) {
'prefix' => ''
);
// A duplicate of the code from url_is_external() to avoid needing another
// function call, since performance inside url() is critical.
if (!isset($options['external'])) {
// Return an external link if $path contains an allowed absolute URL. Avoid
// calling drupal_strip_dangerous_protocols() if there is any slash (/),
// hash (#) or question_mark (?) before the colon (:) occurrence - if any -
// as this would clearly mean it is not a URL. If the path starts with 2
// slashes then it is always considered an external URL without an explicit
// protocol part.
$colonpos = strpos($path, ':');
$options['external'] = (strpos($path, '//') === 0)
|| ($colonpos !== FALSE
&& !preg_match('![/?#]!', substr($path, 0, $colonpos))
&& drupal_strip_dangerous_protocols($path) == $path);
$options['external'] = url_is_external($path);
}
// Preserve the original path before altering or aliasing.
@@ -2347,12 +2357,18 @@ function url($path = NULL, array $options = array()) {
*/
function url_is_external($path) {
$colonpos = strpos($path, ':');
// Avoid calling drupal_strip_dangerous_protocols() if there is any slash (/),
// hash (#) or question_mark (?) before the colon (:) occurrence - if any - as
// this would clearly mean it is not a URL. If the path starts with 2 slashes
// then it is always considered an external URL without an explicit protocol
// part.
// Some browsers treat \ as / so normalize to forward slashes.
$path = str_replace('\\', '/', $path);
// If the path starts with 2 slashes then it is always considered an external
// URL without an explicit protocol part.
return (strpos($path, '//') === 0)
// Leading control characters may be ignored or mishandled by browsers, so
// assume such a path may lead to an external location. The \p{C} character
// class matches all UTF-8 control, unassigned, and private characters.
|| (preg_match('/^\p{C}/u', $path) !== 0)
// Avoid calling drupal_strip_dangerous_protocols() if there is any slash
// (/), hash (#) or question_mark (?) before the colon (:) occurrence - if
// any - as this would clearly mean it is not a URL.
|| ($colonpos !== FALSE
&& !preg_match('![/?#]!', substr($path, 0, $colonpos))
&& drupal_strip_dangerous_protocols($path) == $path);
@@ -2637,6 +2653,15 @@ function drupal_deliver_html_page($page_callback_result) {
global $language;
drupal_add_http_header('Content-Language', $language->language);
// By default, do not allow the site to be rendered in an iframe on another
// domain, but provide a variable to override this. If the code running for
// this page request already set the X-Frame-Options header earlier, don't
// overwrite it here.
$frame_options = variable_get('x_frame_options', 'SAMEORIGIN');
if ($frame_options && is_null(drupal_get_http_header('X-Frame-Options'))) {
drupal_add_http_header('X-Frame-Options', $frame_options);
}
// Menu status constants are integers; page content is a string or array.
if (is_int($page_callback_result)) {
// @todo: Break these up into separate functions?
@@ -2751,6 +2776,7 @@ function drupal_page_footer() {
_registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);
drupal_cache_system_paths();
module_implements_write_cache();
drupal_file_scan_write_cache();
system_run_automated_cron();
}
@@ -2812,11 +2838,11 @@ function drupal_map_assoc($array, $function = NULL) {
* into script execution a call such as set_time_limit(20) is made, the
* script will run for a total of 45 seconds before timing out.
*
* It also means that it is possible to decrease the total time limit if
* the sum of the new time limit and the current time spent running the
* script is inferior to the original time limit. It is inherent to the way
* set_time_limit() works, it should rather be called with an appropriate
* value every time you need to allocate a certain amount of time
* If the current time limit is not unlimited it is possible to decrease the
* total time limit if the sum of the new time limit and the current time spent
* running the script is inferior to the original time limit. It is inherent to
* the way set_time_limit() works, it should rather be called with an
* appropriate value every time you need to allocate a certain amount of time
* to execute a task than only once at the beginning of the script.
*
* Before calling set_time_limit(), we check if this function is available
@@ -2833,7 +2859,11 @@ function drupal_map_assoc($array, $function = NULL) {
*/
function drupal_set_time_limit($time_limit) {
if (function_exists('set_time_limit')) {
@set_time_limit($time_limit);
$current = ini_get('max_execution_time');
// Do not set time limit if it is currently unlimited.
if ($current != 0) {
@set_time_limit($time_limit);
}
}
}
@@ -3014,6 +3044,13 @@ function drupal_add_html_head_link($attributes, $header = FALSE) {
*/
function drupal_add_css($data = NULL, $options = NULL) {
$css = &drupal_static(__FUNCTION__, array());
$count = &drupal_static(__FUNCTION__ . '_count', 0);
// If the $css variable has been reset with drupal_static_reset(), there is
// no longer any CSS being tracked, so set the counter back to 0 also.
if (count($css) === 0) {
$count = 0;
}
// Construct the options, taking the defaults into consideration.
if (isset($options)) {
@@ -3049,7 +3086,8 @@ function drupal_add_css($data = NULL, $options = NULL) {
}
// Always add a tiny value to the weight, to conserve the insertion order.
$options['weight'] += count($css) / 1000;
$options['weight'] += $count / 1000;
$count++;
// Add the data to the CSS array depending on the type.
switch ($options['type']) {
@@ -3802,7 +3840,7 @@ function drupal_load_stylesheet_content($contents, $optimize = FALSE) {
// Replaces @import commands with the actual stylesheet content.
// This happens recursively but omits external files.
$contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\s*\)?\s*;/', '_drupal_load_stylesheet', $contents);
$contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)(?!\/\/)([^\'"\()]+)[\'"]?\s*\)?\s*;/', '_drupal_load_stylesheet', $contents);
return $contents;
}
@@ -3862,6 +3900,21 @@ function drupal_delete_file_if_stale($uri) {
* The cleaned identifier.
*/
function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '')) {
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['allow_css_double_underscores'] = &drupal_static(__FUNCTION__ . ':allow_css_double_underscores');
}
$allow_css_double_underscores = &$drupal_static_fast['allow_css_double_underscores'];
if (!isset($allow_css_double_underscores)) {
$allow_css_double_underscores = variable_get('allow_css_double_underscores', FALSE);
}
// Preserve BEM-style double-underscores depending on custom setting.
if ($allow_css_double_underscores) {
$filter['__'] = '__';
}
// By default, we filter using Drupal's coding standards.
$identifier = strtr($identifier, $filter);
@@ -5212,6 +5265,11 @@ function _drupal_bootstrap_full() {
fix_gpc_magic();
// Load all enabled modules
module_load_all();
// Reset drupal_alter() and module_implements() static caches as these
// include implementations for vital modules only when called early on
// in the bootstrap.
drupal_static_reset('drupal_alter');
drupal_static_reset('module_implements');
// Make sure all stream wrappers are registered.
file_get_stream_wrappers();
// Ensure mt_rand is reseeded, to prevent random values from one page load
@@ -5308,8 +5366,8 @@ function drupal_page_set_cache() {
*
* Do not call this function from a test. Use $this->cronRun() instead.
*
* @return
* TRUE if cron ran successfully.
* @return bool
* TRUE if cron ran successfully and FALSE if cron is already running.
*/
function drupal_cron_run() {
// Allow execution to continue even if the request gets canceled.
@@ -5371,12 +5429,12 @@ function drupal_cron_run() {
// Do not run if queue wants to skip.
continue;
}
$function = $info['worker callback'];
$callback = $info['worker callback'];
$end = time() + (isset($info['time']) ? $info['time'] : 15);
$queue = DrupalQueue::get($queue_name);
while (time() < $end && ($item = $queue->claimItem())) {
try {
$function($item->data);
call_user_func($callback, $item->data);
$queue->deleteItem($item);
}
catch (Exception $e) {
@@ -6329,13 +6387,21 @@ function drupal_render_cid_parts($granularity = NULL) {
}
if (!empty($granularity)) {
$cache_per_role = $granularity & DRUPAL_CACHE_PER_ROLE;
$cache_per_user = $granularity & DRUPAL_CACHE_PER_USER;
// User 1 has special permissions outside of the role system, so when
// caching per role is requested, it should cache per user instead.
if ($user->uid == 1 && $cache_per_role) {
$cache_per_user = TRUE;
$cache_per_role = FALSE;
}
// 'PER_ROLE' and 'PER_USER' are mutually exclusive. 'PER_USER' can be a
// resource drag for sites with many users, so when a module is being
// equivocal, we favor the less expensive 'PER_ROLE' pattern.
if ($granularity & DRUPAL_CACHE_PER_ROLE) {
if ($cache_per_role) {
$cid_parts[] = 'r.' . implode(',', array_keys($user->roles));
}
elseif ($granularity & DRUPAL_CACHE_PER_USER) {
elseif ($cache_per_user) {
$cid_parts[] = "u.$user->uid";
}
@@ -7075,7 +7141,8 @@ function drupal_uninstall_schema($module) {
* specification of a schema, as it was defined in a module's
* hook_schema(). No additional default values will be set,
* hook_schema_alter() is not invoked and these unprocessed
* definitions won't be cached.
* definitions won't be cached. To retrieve the schema after
* hook_schema_alter() has been invoked use drupal_get_schema().
*
* This function can be used to retrieve a schema specification in
* hook_schema(), so it allows you to derive your tables from existing
@@ -7137,6 +7204,24 @@ function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRU
}
}
/**
* Retrieves the type for every field in a table schema.
*
* @param $table
* The name of the table from which to retrieve type information.
*
* @return
* An array of types, keyed by field name.
*/
function drupal_schema_field_types($table) {
$table_schema = drupal_get_schema($table);
$field_types = array();
foreach ($table_schema['fields'] as $field_name => $field_info) {
$field_types[$field_name] = isset($field_info['type']) ? $field_info['type'] : NULL;
}
return $field_types;
}
/**
* Retrieves a list of fields from a table schema.
*
@@ -7338,7 +7423,16 @@ function drupal_write_record($table, &$record, $primary_keys = array()) {
* Information stored in a module .info file:
* - name: The real name of the module for display purposes.
* - description: A brief description of the module.
* - dependencies: An array of shortnames of other modules this module requires.
* - dependencies: An array of dependency strings. Each is in the form
* 'project:module (versions)'; with the following meanings:
* - project: (optional) Project shortname, recommended to ensure uniqueness,
* if the module is part of a project hosted on drupal.org. If omitted,
* also omit the : that follows. The project name is currently ignored by
* Drupal core but is used for automated testing.
* - module: (required) Module shortname within the project.
* - (versions): Optional version information, consisting of one or more
* comma-separated operator/value pairs or simply version numbers, which
* can contain "x" as a wildcard. Examples: (>=7.22, <7.28), (7.x-3.x).
* - package: The name of the package of modules this module belongs to.
*
* See forum.info for an example of a module .info file.
@@ -7418,7 +7512,6 @@ function drupal_parse_info_file($filename) {
*/
function drupal_parse_info_format($data) {
$info = array();
$constants = get_defined_constants();
if (preg_match_all('
@^\s* # Start at the beginning of a line, ignoring leading whitespace
@@ -7458,8 +7551,8 @@ function drupal_parse_info_format($data) {
}
// Handle PHP constants.
if (isset($constants[$value])) {
$value = $constants[$value];
if (preg_match('/^\w+$/i', $value) && defined($value)) {
$value = constant($value);
}
// Insert actual value.
@@ -7623,7 +7716,12 @@ function debug($data, $label = NULL, $print_r = FALSE) {
* Parses a dependency for comparison by drupal_check_incompatibility().
*
* @param $dependency
* A dependency string, for example 'foo (>=7.x-4.5-beta5, 3.x)'.
* A dependency string, which specifies a module dependency, and optionally
* the project it comes from and versions that are supported. Supported
* formats include:
* - 'module'
* - 'project:module'
* - 'project:module (>=version, version)'
*
* @return
* An associative array with three keys:
@@ -7638,6 +7736,12 @@ function debug($data, $label = NULL, $print_r = FALSE) {
* @see drupal_check_incompatibility()
*/
function drupal_parse_dependency($dependency) {
$value = array();
// Split out the optional project name.
if (strpos($dependency, ':')) {
list($project_name, $dependency) = explode(':', $dependency);
$value['project'] = $project_name;
}
// We use named subpatterns and support every op that version_compare
// supports. Also, op is optional and defaults to equals.
$p_op = '(?P<operation>!=|==|=|<|<=|>|>=|<>)?';
@@ -7646,7 +7750,6 @@ function drupal_parse_dependency($dependency) {
$p_major = '(?P<major>\d+)';
// By setting the minor version to x, branches can be matched.
$p_minor = '(?P<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)';
$value = array();
$parts = explode('(', $dependency, 2);
$value['name'] = trim($parts[0]);
if (isset($parts[1])) {
@@ -7761,6 +7864,7 @@ function entity_get_info($entity_type = NULL) {
// Prepare entity schema fields SQL info for
// DrupalEntityControllerInterface::buildQuery().
if (isset($entity_info[$name]['base table'])) {
$entity_info[$name]['base table field types'] = drupal_schema_field_types($entity_info[$name]['base table']);
$entity_info[$name]['schema_fields_sql']['base table'] = drupal_schema_fields_sql($entity_info[$name]['base table']);
if (isset($entity_info[$name]['revision table'])) {
$entity_info[$name]['schema_fields_sql']['revision table'] = drupal_schema_fields_sql($entity_info[$name]['revision table']);

View File

@@ -296,6 +296,20 @@ abstract class DatabaseConnection extends PDO {
*/
protected $prefixReplace = array();
/**
* List of escaped database, table, and field names, keyed by unescaped names.
*
* @var array
*/
protected $escapedNames = array();
/**
* List of escaped aliases names, keyed by unescaped aliases.
*
* @var array
*/
protected $escapedAliases = array();
function __construct($dsn, $username, $password, $driver_options = array()) {
// Initialize and prepare the connection prefix.
$this->setPrefix(isset($this->connectionOptions['prefix']) ? $this->connectionOptions['prefix'] : '');
@@ -626,7 +640,7 @@ abstract class DatabaseConnection extends PDO {
* A sanitized version of the query comment string.
*/
protected function filterComment($comment = '') {
return preg_replace('/(\/\*\s*)|(\s*\*\/)/', '', $comment);
return strtr($comment, array('*' => ' * '));
}
/**
@@ -656,7 +670,7 @@ abstract class DatabaseConnection extends PDO {
* @return DatabaseStatementInterface
* This method will return one of: the executed statement, the number of
* rows affected by the query (not the number matched), or the generated
* insert IT of the last query, depending on the value of
* insert ID of the last query, depending on the value of
* $options['return']. Typically that value will be set by default or a
* query builder and should not be set by a user. If there is an error,
* this method will return NULL and may throw an exception if
@@ -919,11 +933,14 @@ abstract class DatabaseConnection extends PDO {
* For some database drivers, it may also wrap the table name in
* database-specific escape characters.
*
* @return
* @return string
* The sanitized table name string.
*/
public function escapeTable($table) {
return preg_replace('/[^A-Za-z0-9_.]+/', '', $table);
if (!isset($this->escapedNames[$table])) {
$this->escapedNames[$table] = preg_replace('/[^A-Za-z0-9_.]+/', '', $table);
}
return $this->escapedNames[$table];
}
/**
@@ -933,11 +950,14 @@ abstract class DatabaseConnection extends PDO {
* For some database drivers, it may also wrap the field name in
* database-specific escape characters.
*
* @return
* @return string
* The sanitized field name string.
*/
public function escapeField($field) {
return preg_replace('/[^A-Za-z0-9_.]+/', '', $field);
if (!isset($this->escapedNames[$field])) {
$this->escapedNames[$field] = preg_replace('/[^A-Za-z0-9_.]+/', '', $field);
}
return $this->escapedNames[$field];
}
/**
@@ -948,11 +968,14 @@ abstract class DatabaseConnection extends PDO {
* DatabaseConnection::escapeTable(), this doesn't allow the period (".")
* because that is not allowed in aliases.
*
* @return
* @return string
* The sanitized field name string.
*/
public function escapeAlias($field) {
return preg_replace('/[^A-Za-z0-9_]+/', '', $field);
if (!isset($this->escapedAliases[$field])) {
$this->escapedAliases[$field] = preg_replace('/[^A-Za-z0-9_]+/', '', $field);
}
return $this->escapedAliases[$field];
}
/**
@@ -1313,6 +1336,39 @@ abstract class DatabaseConnection extends PDO {
* also larger than the $existing_id if one was passed in.
*/
abstract public function nextId($existing_id = 0);
/**
* Checks whether utf8mb4 support is configurable in settings.php.
*
* @return bool
*/
public function utf8mb4IsConfigurable() {
// Since 4 byte UTF-8 is not supported by default, there is nothing to
// configure.
return FALSE;
}
/**
* Checks whether utf8mb4 support is currently active.
*
* @return bool
*/
public function utf8mb4IsActive() {
// Since 4 byte UTF-8 is not supported by default, there is nothing to
// activate.
return FALSE;
}
/**
* Checks whether utf8mb4 support is available on the current database system.
*
* @return bool
*/
public function utf8mb4IsSupported() {
// By default we assume that the database backend may not support 4 byte
// UTF-8.
return FALSE;
}
}
/**

View File

@@ -28,6 +28,12 @@ class DatabaseConnection_mysql extends DatabaseConnection {
$this->connectionOptions = $connection_options;
$charset = 'utf8';
// Check if the charset is overridden to utf8mb4 in settings.php.
if ($this->utf8mb4IsActive()) {
$charset = 'utf8mb4';
}
// The DSN should use either a socket or a host/port.
if (isset($connection_options['unix_socket'])) {
$dsn = 'mysql:unix_socket=' . $connection_options['unix_socket'];
@@ -39,7 +45,7 @@ class DatabaseConnection_mysql extends DatabaseConnection {
// Character set is added to dsn to ensure PDO uses the proper character
// set when escaping. This has security implications. See
// https://www.drupal.org/node/1201452 for further discussion.
$dsn .= ';charset=utf8';
$dsn .= ';charset=' . $charset;
$dsn .= ';dbname=' . $connection_options['database'];
// Allow PDO options to be overridden.
$connection_options += array(
@@ -51,6 +57,11 @@ class DatabaseConnection_mysql extends DatabaseConnection {
// Because MySQL's prepared statements skip the query cache, because it's dumb.
PDO::ATTR_EMULATE_PREPARES => TRUE,
);
if (defined('PDO::MYSQL_ATTR_MULTI_STATEMENTS')) {
// An added connection option in PHP 5.5.21+ to optionally limit SQL to a
// single statement like mysqli.
$connection_options['pdo'] += array(PDO::MYSQL_ATTR_MULTI_STATEMENTS => FALSE);
}
parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
@@ -58,10 +69,10 @@ class DatabaseConnection_mysql extends DatabaseConnection {
// certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci'
// for UTF-8.
if (!empty($connection_options['collation'])) {
$this->exec('SET NAMES utf8 COLLATE ' . $connection_options['collation']);
$this->exec('SET NAMES ' . $charset . ' COLLATE ' . $connection_options['collation']);
}
else {
$this->exec('SET NAMES utf8');
$this->exec('SET NAMES ' . $charset);
}
// Set MySQL init_commands if not already defined. Default Drupal's MySQL
@@ -76,10 +87,12 @@ class DatabaseConnection_mysql extends DatabaseConnection {
'init_commands' => array(),
);
$connection_options['init_commands'] += array(
'sql_mode' => "SET sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'",
'sql_mode' => "SET sql_mode = 'REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'",
);
// Set connection options.
$this->exec(implode('; ', $connection_options['init_commands']));
// Execute initial commands.
foreach ($connection_options['init_commands'] as $sql) {
$this->exec($sql);
}
}
public function __destruct() {
@@ -199,6 +212,42 @@ class DatabaseConnection_mysql extends DatabaseConnection {
}
}
}
public function utf8mb4IsConfigurable() {
return TRUE;
}
public function utf8mb4IsActive() {
return isset($this->connectionOptions['charset']) && $this->connectionOptions['charset'] === 'utf8mb4';
}
public function utf8mb4IsSupported() {
// Ensure that the MySQL driver supports utf8mb4 encoding.
$version = $this->getAttribute(PDO::ATTR_CLIENT_VERSION);
if (strpos($version, 'mysqlnd') !== FALSE) {
// The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
$version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
if (version_compare($version, '5.0.9', '<')) {
return FALSE;
}
}
else {
// The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
if (version_compare($version, '5.5.3', '<')) {
return FALSE;
}
}
// Ensure that the MySQL server supports large prefixes and utf8mb4.
try {
$this->query("CREATE TABLE {drupal_utf8mb4_test} (id VARCHAR(255), PRIMARY KEY(id(255))) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci ROW_FORMAT=DYNAMIC ENGINE=INNODB");
}
catch (Exception $e) {
return FALSE;
}
$this->query("DROP TABLE {drupal_utf8mb4_test}");
return TRUE;
}
}

View File

@@ -39,8 +39,8 @@ class DatabaseSchema_mysql extends DatabaseSchema {
$info['table'] = substr($table, ++$pos);
}
else {
$db_info = Database::getConnectionInfo();
$info['database'] = $db_info[$this->connection->getTarget()]['database'];
$db_info = $this->connection->getConnectionOptions();
$info['database'] = $db_info['database'];
$info['table'] = $table;
}
return $info;
@@ -81,7 +81,8 @@ class DatabaseSchema_mysql extends DatabaseSchema {
// Provide defaults if needed.
$table += array(
'mysql_engine' => 'InnoDB',
'mysql_character_set' => 'utf8',
// Allow the default charset to be overridden in settings.php.
'mysql_character_set' => $this->connection->utf8mb4IsActive() ? 'utf8mb4' : 'utf8',
);
$sql = "CREATE TABLE {" . $name . "} (\n";
@@ -109,6 +110,13 @@ class DatabaseSchema_mysql extends DatabaseSchema {
$sql .= ' COLLATE ' . $info['collation'];
}
// The row format needs to be either DYNAMIC or COMPRESSED in order to allow
// for the innodb_large_prefix setting to take effect, see
// https://dev.mysql.com/doc/refman/5.6/en/create-table.html
if ($this->connection->utf8mb4IsActive()) {
$sql .= ' ROW_FORMAT=DYNAMIC';
}
// Add table comment.
if (!empty($table['description'])) {
$sql .= ' COMMENT ' . $this->prepareComment($table['description'], self::COMMENT_MAX_TABLE);

View File

@@ -216,6 +216,14 @@ class DatabaseConnection_pgsql extends DatabaseConnection {
return $id;
}
public function utf8mb4IsActive() {
return TRUE;
}
public function utf8mb4IsSupported() {
return TRUE;
}
}
/**

View File

@@ -92,7 +92,8 @@ require_once dirname(__FILE__) . '/query.inc';
* specification). Each specification is an array containing the name of
* the referenced table ('table'), and an array of column mappings
* ('columns'). Column mappings are defined by key pairs ('source_column' =>
* 'referenced_column').
* 'referenced_column'). This key is for documentation purposes only; foreign
* keys are not created in the database, nor are they enforced by Drupal.
* - 'indexes': An associative array of indexes ('indexname' =>
* specification). Each specification is an array of one or more
* key column specifiers (see below) that form an index on the
@@ -144,6 +145,8 @@ require_once dirname(__FILE__) . '/query.inc';
* 'unique keys' => array(
* 'vid' => array('vid'),
* ),
* // For documentation purposes only; foreign keys are not created in the
* // database.
* 'foreign keys' => array(
* 'node_revision' => array(
* 'table' => 'node_revision',

View File

@@ -378,6 +378,14 @@ class DatabaseConnection_sqlite extends DatabaseConnection {
}
}
public function utf8mb4IsActive() {
return TRUE;
}
public function utf8mb4IsSupported() {
return TRUE;
}
}
/**

View File

@@ -14,8 +14,6 @@ class DatabaseTasks_sqlite extends DatabaseTasks {
/**
* Minimum engine version.
*
* @todo: consider upping to 3.6.8 in Drupal 8 to get SAVEPOINT support.
*/
public function minimumVersion() {
return '3.3.7';

View File

@@ -183,6 +183,11 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface {
}
}
// Ensure integer entity IDs are valid.
if (!empty($ids)) {
$this->cleanIds($ids);
}
// Load any remaining entities from the database. This is the case if $ids
// is set to FALSE (so we load all entities), if there are any ids left to
// load, if loading a revision, or if $conditions was passed without $ids.
@@ -223,6 +228,35 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface {
return $entities;
}
/**
* Ensures integer entity IDs are valid.
*
* The identifier sanitization provided by this method has been introduced
* as Drupal used to rely on the database to facilitate this, which worked
* correctly with MySQL but led to errors with other DBMS such as PostgreSQL.
*
* @param array $ids
* The entity IDs to verify. Non-integer IDs are removed from this array if
* the entity type requires IDs to be integers.
*/
protected function cleanIds(&$ids) {
$entity_info = entity_get_info($this->entityType);
if (isset($entity_info['base table field types'])) {
$id_type = $entity_info['base table field types'][$this->idKey];
if ($id_type == 'serial' || $id_type == 'int') {
$ids = array_filter($ids, array($this, 'filterId'));
$ids = array_map('intval', $ids);
}
}
}
/**
* Callback for array_filter that removes non-integer IDs.
*/
protected function filterId($id) {
return is_numeric($id) && $id == (int) $id;
}
/**
* Builds the query to load the entity.
*
@@ -412,7 +446,7 @@ class EntityFieldQueryException extends Exception {}
*
* This class allows finding entities based on entity properties (for example,
* node->changed), field values, and generic entity meta data (bundle,
* entity type, entity id, and revision ID). It is not possible to query across
* entity type, entity ID, and revision ID). It is not possible to query across
* multiple entity types. For example, there is no facility to find published
* nodes written by users created in the last hour, as this would require
* querying both node->status and user->created.
@@ -654,14 +688,36 @@ class EntityFieldQuery {
* @param $field
* Either a field name or a field array.
* @param $column
* The column that should hold the value to be matched.
* The column that should hold the value to be matched, defined in the
* hook_field_schema() of this field. If this is omitted then all of the
* other parameters are ignored, except $field, and this call will just be
* adding a condition that says that the field has a value, rather than
* testing the value itself.
* @param $value
* The value to test the column value against.
* The value to test the column value against. In most cases, this is a
* scalar. For more complex options, it is an array. The meaning of each
* element in the array is dependent on $operator.
* @param $operator
* The operator to be used to test the given value.
* The operator to be used to test the given value. The possible values are:
* - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These
* operators expect $value to be a literal of the same type as the
* column.
* - 'IN', 'NOT IN': These operators expect $value to be an array of
* literals of the same type as the column.
* - 'BETWEEN': This operator expects $value to be an array of two literals
* of the same type as the column.
* The operator can be omitted, and will default to 'IN' if the value is an
* array, or to '=' otherwise.
* @param $delta_group
* An arbitrary identifier: conditions in the same group must have the same
* $delta_group.
* $delta_group. For example, let's presume a multivalue field which has
* two columns, 'color' and 'shape', and for entity ID 1, there are two
* values: red/square and blue/circle. Entity ID 1 does not have values
* corresponding to 'red circle'; however if you pass 'red' and 'circle' as
* conditions, it will appear in the results -- by default queries will run
* against any combination of deltas. By passing the conditions with the
* same $delta_group it will ensure that only values attached to the same
* delta are matched, and entity 1 would then be excluded from the results.
* @param $language_group
* An arbitrary identifier: conditions in the same group must have the same
* $language_group.
@@ -736,9 +792,11 @@ class EntityFieldQuery {
* @param $field
* Either a field name or a field array.
* @param $column
* A column defined in the hook_field_schema() of this field. If this is
* omitted then the query will find only entities that have data in this
* field, using the entity and property conditions if there are any.
* The column that should hold the value to be matched, defined in the
* hook_field_schema() of this field. If this is omitted then all of the
* other parameters are ignored, except $field, and this call will just be
* adding a condition that says that the field has a value, rather than
* testing the value itself.
* @param $value
* The value to test the column value against. In most cases, this is a
* scalar. For more complex options, it is an array. The meaning of each
@@ -757,10 +815,10 @@ class EntityFieldQuery {
* @param $delta_group
* An arbitrary identifier: conditions in the same group must have the same
* $delta_group. For example, let's presume a multivalue field which has
* two columns, 'color' and 'shape', and for entity id 1, there are two
* two columns, 'color' and 'shape', and for entity ID 1, there are two
* values: red/square and blue/circle. Entity ID 1 does not have values
* corresponding to 'red circle', however if you pass 'red' and 'circle' as
* conditions, it will appear in the results - by default queries will run
* conditions, it will appear in the results -- by default queries will run
* against any combination of deltas. By passing the conditions with the
* same $delta_group it will ensure that only values attached to the same
* delta are matched, and entity 1 would then be excluded from the results.

View File

@@ -199,7 +199,16 @@ function _drupal_log_error($error, $fatal = FALSE) {
$number++;
}
watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']);
// Log the error immediately, unless this is a non-fatal error which has been
// triggered via drupal_trigger_error_with_delayed_logging(); in that case
// trigger it in a shutdown function. Fatal errors are always triggered
// immediately since for a fatal error the page request will end here anyway.
if (!$fatal && drupal_static('_drupal_trigger_error_with_delayed_logging')) {
drupal_register_shutdown_function('watchdog', 'php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']);
}
else {
watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']);
}
if ($fatal) {
drupal_add_http_header('Status', '500 Service unavailable (with message)');

View File

@@ -273,7 +273,9 @@ function file_default_scheme() {
* The normalized URI.
*/
function file_stream_wrapper_uri_normalize($uri) {
$scheme = file_uri_scheme($uri);
// Inline file_uri_scheme() function call for performance reasons.
$position = strpos($uri, '://');
$scheme = $position ? substr($uri, 0, $position) : FALSE;
if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
$target = file_uri_target($uri);
@@ -1785,7 +1787,7 @@ function file_validate_is_image(stdClass $file) {
/**
* Verifies that image dimensions are within the specified maximum and minimum.
*
* Non-image files will be ignored. If a image toolkit is available the image
* Non-image files will be ignored. If an image toolkit is available the image
* will be scaled to fit within the desired maximum dimensions.
*
* @param $file
@@ -2022,7 +2024,7 @@ function file_download() {
*
* @see file_transfer()
* @see file_download_access()
* @see hook_file_downlaod()
* @see hook_file_download()
*/
function file_download_headers($uri) {
// Let other modules provide headers and control access to the file.

View File

@@ -105,7 +105,8 @@
* generate the same form (or very similar forms) using different $form_ids
* can implement hook_forms(), which maps different $form_id values to the
* proper form constructor function. Examples may be found in node_forms(),
* and search_forms().
* and search_forms(). hook_forms() can also be used to define forms in
* classes.
* @param ...
* Any additional arguments are passed on to the functions called by
* drupal_get_form(), including the unique form constructor function. For
@@ -809,7 +810,7 @@ function drupal_retrieve_form($form_id, &$form_state) {
}
if (isset($form_definition['callback'])) {
$callback = $form_definition['callback'];
$form_state['build_info']['base_form_id'] = $callback;
$form_state['build_info']['base_form_id'] = isset($form_definition['base_form_id']) ? $form_definition['base_form_id'] : $callback;
}
// In case $form_state['wrapper_callback'] is not defined already, we also
// allow hook_forms() to define one.
@@ -830,7 +831,7 @@ function drupal_retrieve_form($form_id, &$form_state) {
// the actual form builder function ($callback) expects. This allows for
// pre-populating a form with common elements for certain forms, such as
// back/next/save buttons in multi-step form wizards. See drupal_build_form().
if (isset($form_state['wrapper_callback']) && function_exists($form_state['wrapper_callback'])) {
if (isset($form_state['wrapper_callback']) && is_callable($form_state['wrapper_callback'])) {
$form = call_user_func_array($form_state['wrapper_callback'], $args);
// Put the prepopulated $form into $args.
$args[0] = $form;
@@ -1128,6 +1129,17 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
drupal_alter($hooks, $form, $form_state, $form_id);
}
/**
* Helper function to call form_set_error() if there is a token error.
*/
function _drupal_invalid_token_set_form_error() {
$path = current_path();
$query = drupal_get_query_parameters();
$url = url($path, array('query' => $query));
// Setting this error will cause the form to fail validation.
form_set_error('form_token', t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => $url)));
}
/**
* Validates user-submitted form data in the $form_state array.
@@ -1162,16 +1174,11 @@ function drupal_validate_form($form_id, &$form, &$form_state) {
}
// If the session token was set by drupal_prepare_form(), ensure that it
// matches the current user's session.
// matches the current user's session. This is duplicate to code in
// form_builder() but left to protect any custom form handling code.
if (isset($form['#token'])) {
if (!drupal_valid_token($form_state['values']['form_token'], $form['#token'])) {
$path = current_path();
$query = drupal_get_query_parameters();
$url = url($path, array('query' => $query));
// Setting this error will cause the form to fail validation.
form_set_error('form_token', t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => $url)));
if (!drupal_valid_token($form_state['values']['form_token'], $form['#token']) || !empty($form_state['invalid_token'])) {
_drupal_invalid_token_set_form_error();
// Stop here and don't run any further validation handlers, because they
// could invoke non-safe operations which opens the door for CSRF
// vulnerabilities.
@@ -1827,6 +1834,20 @@ function form_builder($form_id, &$element, &$form_state) {
// from the POST data is set and matches the current form_id.
if ($form_state['programmed'] || (!empty($form_state['input']) && (isset($form_state['input']['form_id']) && ($form_state['input']['form_id'] == $form_id)))) {
$form_state['process_input'] = TRUE;
// If the session token was set by drupal_prepare_form(), ensure that it
// matches the current user's session.
$form_state['invalid_token'] = FALSE;
if (isset($element['#token'])) {
if (empty($form_state['input']['form_token']) || !drupal_valid_token($form_state['input']['form_token'], $element['#token'])) {
// Set an early form error to block certain input processing since that
// opens the door for CSRF vulnerabilities.
_drupal_invalid_token_set_form_error();
// This value is checked in _form_builder_handle_input_element().
$form_state['invalid_token'] = TRUE;
// Make sure file uploads do not get processed.
$_FILES = array();
}
}
}
else {
$form_state['process_input'] = FALSE;
@@ -1930,6 +1951,18 @@ function form_builder($form_id, &$element, &$form_state) {
$element['#attributes']['enctype'] = 'multipart/form-data';
}
// Allow Ajax submissions to the form action to bypass verification. This is
// especially useful for multipart forms, which cannot be verified via a
// response header.
$element['#attached']['js'][] = array(
'type' => 'setting',
'data' => array(
'urlIsAjaxTrusted' => array(
$element['#action'] => TRUE,
),
),
);
// If a form contains a single textfield, and the ENTER key is pressed
// within it, Internet Explorer submits the form with no POST data
// identifying any submit button. Other browsers submit POST data as though
@@ -1978,6 +2011,19 @@ function form_builder($form_id, &$element, &$form_state) {
* Adds the #name and #value properties of an input element before rendering.
*/
function _form_builder_handle_input_element($form_id, &$element, &$form_state) {
static $safe_core_value_callbacks = array(
'form_type_token_value',
'form_type_textarea_value',
'form_type_textfield_value',
'form_type_checkbox_value',
'form_type_checkboxes_value',
'form_type_radios_value',
'form_type_password_confirm_value',
'form_type_select_value',
'form_type_tableselect_value',
'list_boolean_allowed_values_callback',
);
if (!isset($element['#name'])) {
$name = array_shift($element['#parents']);
$element['#name'] = $name;
@@ -2056,7 +2102,14 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) {
// property, optionally filtered through $value_callback.
if ($input_exists) {
if (function_exists($value_callback)) {
$element['#value'] = $value_callback($element, $input, $form_state);
// Skip all value callbacks except safe ones like text if the CSRF
// token was invalid.
if (empty($form_state['invalid_token']) || in_array($value_callback, $safe_core_value_callbacks)) {
$element['#value'] = $value_callback($element, $input, $form_state);
}
else {
$input = NULL;
}
}
if (!isset($element['#value']) && isset($input)) {
$element['#value'] = $input;
@@ -2519,7 +2572,7 @@ function form_type_select_value($element, $input = FALSE) {
* for this element. Return nothing to use the default.
*/
function form_type_textarea_value($element, $input = FALSE) {
if ($input !== FALSE) {
if ($input !== FALSE && $input !== NULL) {
// This should be a string, but allow other scalars since they might be
// valid input in programmatic form submissions.
return is_scalar($input) ? (string) $input : '';
@@ -2662,8 +2715,8 @@ function _form_options_flatten($array) {
* - #required: (optional) Whether the user needs to select an option (TRUE)
* or not (FALSE). Defaults to FALSE.
* - #empty_option: (optional) The label to show for the first default option.
* By default, the label is automatically set to "- Please select -" for a
* required field and "- None -" for an optional field.
* By default, the label is automatically set to "- Select -" for a required
* field and "- None -" for an optional field.
* - #empty_value: (optional) The value for the first default option, which is
* used to determine whether the user submitted a value or not.
* - If #required is TRUE, this defaults to '' (an empty string).
@@ -2976,7 +3029,7 @@ function form_process_password_confirm($element) {
function password_confirm_validate($element, &$element_state) {
$pass1 = trim($element['pass1']['#value']);
$pass2 = trim($element['pass2']['#value']);
if (!empty($pass1) || !empty($pass2)) {
if (strlen($pass1) > 0 || strlen($pass2) > 0) {
if (strcmp($pass1, $pass2)) {
form_error($element, t('The specified passwords do not match.'));
}
@@ -3333,9 +3386,12 @@ function form_process_container($element, &$form_state) {
/**
* Returns HTML to wrap child elements in a container.
*
* Used for grouped form items. Can also be used as a #theme_wrapper for any
* Used for grouped form items. Can also be used as a theme wrapper for any
* renderable element, to surround it with a <div> and add attributes such as
* classes or an HTML id.
* classes or an HTML ID.
*
* See the @link forms_api_reference.html Form API reference @endlink for more
* information on the #theme_wrappers render array property.
*
* @param $variables
* An associative array containing:
@@ -3490,6 +3546,7 @@ function form_process_tableselect($element) {
'#return_value' => $key,
'#default_value' => isset($value[$key]) ? $key : NULL,
'#attributes' => $element['#attributes'],
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
);
}
else {
@@ -3910,6 +3967,34 @@ function theme_hidden($variables) {
return '<input' . drupal_attributes($element['#attributes']) . " />\n";
}
/**
* Process function to prepare autocomplete data.
*
* @param $element
* A textfield or other element with a #autocomplete_path.
*
* @return array
* The processed form element.
*/
function form_process_autocomplete($element) {
$element['#autocomplete_input'] = array();
if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) {
$element['#autocomplete_input']['#id'] = $element['#id'] .'-autocomplete';
// Force autocomplete to use non-clean URLs since this protects against the
// browser interpreting the path plus search string as an actual file.
$current_clean_url = isset($GLOBALS['conf']['clean_url']) ? $GLOBALS['conf']['clean_url'] : NULL;
$GLOBALS['conf']['clean_url'] = 0;
// Force the script path to 'index.php', in case the server is not
// configured to find it automatically. Normally it is the responsibility
// of the site to do this themselves using hook_url_outbound_alter() (see
// url()) but since this code is forcing non-clean URLs on sites that don't
// normally use them, it is done here instead.
$element['#autocomplete_input']['#url_value'] = url($element['#autocomplete_path'], array('absolute' => TRUE, 'script' => 'index.php'));
$GLOBALS['conf']['clean_url'] = $current_clean_url;
}
return $element;
}
/**
* Returns HTML for a textfield form element.
*
@@ -3928,14 +4013,14 @@ function theme_textfield($variables) {
_form_set_class($element, array('form-text'));
$extra = '';
if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) {
if ($element['#autocomplete_path'] && !empty($element['#autocomplete_input'])) {
drupal_add_library('system', 'drupal.autocomplete');
$element['#attributes']['class'][] = 'form-autocomplete';
$attributes = array();
$attributes['type'] = 'hidden';
$attributes['id'] = $element['#attributes']['id'] . '-autocomplete';
$attributes['value'] = url($element['#autocomplete_path'], array('absolute' => TRUE));
$attributes['id'] = $element['#autocomplete_input']['#id'];
$attributes['value'] = $element['#autocomplete_input']['#url_value'];
$attributes['disabled'] = 'disabled';
$attributes['class'][] = 'autocomplete';
$extra = '<input' . drupal_attributes($attributes) . ' />';
@@ -4409,7 +4494,7 @@ function element_validate_number($element, &$form_state) {
*
* Sample callback_batch_finished():
* @code
* function batch_test_finished($success, $results, $operations) {
* function my_finished_callback($success, $results, $operations) {
* // The 'success' parameter means no fatal PHP errors were detected. All
* // other error management should be handled using 'results'.
* if ($success) {

View File

@@ -362,7 +362,8 @@ function install_run_tasks(&$install_state) {
* Runs an individual installation task.
*
* @param $task
* An array of information about the task to be run.
* An array of information about the task to be run as returned by
* hook_install_tasks().
* @param $install_state
* An array of information about the current installation state. This is
* passed in by reference so that it can be modified by the task.
@@ -478,11 +479,15 @@ function install_run_task($task, &$install_state) {
* the page request evolves (for example, if an installation profile hasn't
* been selected yet, we don't yet know which profile tasks need to be run).
*
* You can override this using hook_install_tasks() or
* hook_install_tasks_alter().
*
* @param $install_state
* An array of information about the current installation state.
*
* @return
* A list of tasks to be performed, with associated metadata.
* A list of tasks to be performed, with associated metadata as returned by
* hook_install_tasks().
*/
function install_tasks_to_perform($install_state) {
// Start with a list of all currently available tasks.
@@ -804,6 +809,13 @@ function install_system_module(&$install_state) {
variable_set('install_profile_modules', array_diff($modules, array('system')));
$install_state['database_tables_exist'] = TRUE;
// Prevent the hook_requirements() check from telling us to convert the
// database to utf8mb4.
$connection = Database::getConnection();
if ($connection->utf8mb4IsConfigurable() && $connection->utf8mb4IsActive()) {
variable_set('drupal_all_databases_are_utf8mb4', TRUE);
}
}
/**
@@ -1585,7 +1597,9 @@ function install_finished(&$install_state) {
}
/**
* Batch callback for batch installation of modules.
* Implements callback_batch_operation().
*
* Performs batch installation of modules.
*/
function _install_module_batch($module, $module_name, &$context) {
// Install and enable the module right away, so that the module will be
@@ -1598,6 +1612,8 @@ function _install_module_batch($module, $module_name, &$context) {
}
/**
* Implements callback_batch_finished().
*
* 'Finished' callback for module installation batch.
*/
function _install_profile_modules_finished($success, $results, $operations) {

View File

@@ -750,7 +750,7 @@ function drupal_install_system() {
/**
* Uninstalls a given list of disabled modules.
*
* @param array $module_list
* @param string[] $module_list
* The modules to uninstall. It is the caller's responsibility to ensure that
* all modules in this list have already been disabled before this function
* is called.
@@ -769,6 +769,7 @@ function drupal_install_system() {
* included in $module_list).
*
* @see module_disable()
* @see module_enable()
*/
function drupal_uninstall_modules($module_list = array(), $uninstall_dependents = TRUE) {
if ($uninstall_dependents) {

View File

@@ -435,6 +435,13 @@ function locale_language_url_rewrite_url(&$path, &$options) {
switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) {
case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN:
if ($options['language']->domain) {
// Save the original base URL. If it contains a port, we need to
// retain it below.
if (!empty($options['base_url'])) {
// The colon in the URL scheme messes up the port checking below.
$normalized_base_url = str_replace(array('https://', 'http://'), '', $options['base_url']);
}
// Ask for an absolute URL with our modified base_url.
global $is_https;
$url_scheme = ($is_https) ? 'https://' : 'http://';
@@ -449,6 +456,19 @@ function locale_language_url_rewrite_url(&$path, &$options) {
// Apply the appropriate protocol to the URL.
$options['base_url'] = $url_scheme . $host;
// In case either the original base URL or the HTTP host contains a
// port, retain it.
$http_host = $_SERVER['HTTP_HOST'];
if (isset($normalized_base_url) && strpos($normalized_base_url, ':') !== FALSE) {
list($host, $port) = explode(':', $normalized_base_url);
$options['base_url'] .= ':' . $port;
}
elseif (strpos($http_host, ':') !== FALSE) {
list($host, $port) = explode(':', $http_host);
$options['base_url'] .= ':' . $port;
}
if (isset($options['https']) && variable_get('https', FALSE)) {
if ($options['https'] === TRUE) {
$options['base_url'] = str_replace('http://', 'https://', $options['base_url']);
@@ -523,6 +543,22 @@ function locale_language_url_rewrite_session(&$path, &$options) {
* possible attack vector (img).
*/
function locale_string_is_safe($string) {
// Some strings have tokens in them. For tokens in the first part of href or
// src HTML attributes, filter_xss() removes part of the token, the part
// before the first colon. filter_xss() assumes it could be an attempt to
// inject javascript. When filter_xss() removes part of tokens, it causes the
// string to not be translatable when it should be translatable. See
// LocaleStringIsSafeTest::testLocaleStringIsSafe().
//
// We can recognize tokens since they are wrapped with brackets and are only
// composed of alphanumeric characters, colon, underscore, and dashes. We can
// be sure these strings are safe to strip out before the string is checked in
// filter_xss() because no dangerous javascript will match that pattern.
//
// @todo Do not strip out the token. Fix filter_xss() to not incorrectly
// alter the string. https://www.drupal.org/node/2372127
$string = preg_replace('/\[[a-z0-9_-]+(:[a-z0-9_-]+)+\]/i', '', $string);
return decode_entities($string) == decode_entities(filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var')));
}
@@ -631,9 +667,6 @@ function locale_add_language($langcode, $name = NULL, $native = NULL, $direction
* translations).
*/
function _locale_import_po($file, $langcode, $mode, $group = NULL) {
// Try to allocate enough time to parse and import the data.
drupal_set_time_limit(240);
// Check if we have the language already in the database.
if (!db_query("SELECT COUNT(language) FROM {languages} WHERE language = :language", array(':language' => $langcode))->fetchField()) {
drupal_set_message(t('The language selected for import is not supported.'), 'error');
@@ -717,6 +750,12 @@ function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL, $group =
$lineno = 0;
while (!feof($fd)) {
// Refresh the time limit every 10 parsed rows to ensure there is always
// enough time to import the data for large PO files.
if (!($lineno % 10)) {
drupal_set_time_limit(30);
}
// A line should not be longer than 10 * 1024.
$line = fgets($fd, 10 * 1024);
@@ -2306,6 +2345,8 @@ function _locale_batch_build($files, $finished = NULL, $components = array()) {
}
/**
* Implements callback_batch_operation().
*
* Perform interface translation import as a batch step.
*
* @param $filepath
@@ -2324,6 +2365,8 @@ function _locale_batch_import($filepath, &$context) {
}
/**
* Implements callback_batch_finished().
*
* Finished callback of system page locale import batch.
* Inform the user of translation files imported.
*/
@@ -2334,6 +2377,8 @@ function _locale_batch_system_finished($success, $results) {
}
/**
* Implements callback_batch_finished().
*
* Finished callback of language addition locale import batch.
* Inform the user of translation files imported.
*/

View File

@@ -566,7 +566,7 @@ function _drupal_wrap_mail_line(&$line, $key, $values) {
// Use soft-breaks only for purely quoted or unindented text.
$line = wordwrap($line, 77 - $values['length'], $values['soft'] ? " \n" : "\n");
// Break really long words at the maximum width allowed.
$line = wordwrap($line, 996 - $values['length'], $values['soft'] ? " \n" : "\n");
$line = wordwrap($line, 996 - $values['length'], $values['soft'] ? " \n" : "\n", TRUE);
}
/**

View File

@@ -229,12 +229,20 @@ define('MENU_CONTEXT_INLINE', 0x0002);
define('MENU_FOUND', 1);
/**
* Internal menu status code -- Menu item was not found.
* Menu status code -- Not found.
*
* This can be used as the return value from a page callback, although it is
* preferable to use a load function to accomplish this; see the hook_menu()
* documentation for details.
*/
define('MENU_NOT_FOUND', 2);
/**
* Internal menu status code -- Menu item access is denied.
* Menu status code -- Access denied.
*
* This can be used as the return value from a page callback, although it is
* preferable to use an access callback to accomplish this; see the hook_menu()
* documentation for details.
*/
define('MENU_ACCESS_DENIED', 3);
@@ -431,7 +439,7 @@ function menu_set_item($path, $router_item) {
*
* @param $path
* The path; for example, 'node/5'. The function will find the corresponding
* node/% item and return that.
* node/% item and return that. Defaults to the current path.
* @param $router_item
* Internal use only.
*
@@ -1487,7 +1495,7 @@ function menu_tree_collect_node_links(&$tree, &$node_links) {
* menu_tree_collect_node_links().
*/
function menu_tree_check_access(&$tree, $node_links = array()) {
if ($node_links) {
if ($node_links && (user_access('access content') || user_access('bypass node access'))) {
$nids = array_keys($node_links);
$select = db_select('node', 'n');
$select->addField('n', 'nid');
@@ -2411,7 +2419,7 @@ function menu_set_active_trail($new_trail = NULL) {
// argument placeholders (%). Such links are not contained in regular
// menu trees, and have only been loaded for the additional
// translation that happens here, so as to be able to display them in
// the breadcumb for the current page.
// the breadcrumb for the current page.
// @see _menu_tree_check_access()
// @see _menu_link_translate()
if (strpos($link['href'], '%') !== FALSE) {
@@ -2613,10 +2621,30 @@ function menu_get_active_breadcrumb() {
*/
function menu_get_active_title() {
$active_trail = menu_get_active_trail();
$local_task_title = NULL;
foreach (array_reverse($active_trail) as $item) {
if (!(bool) ($item['type'] & MENU_IS_LOCAL_TASK)) {
return $item['title'];
// Local task titles are displayed as tabs and therefore should not be
// repeated as the page title. However, if the local task appears in a
// top-level menu, it is no longer a "local task" anymore (the front page
// of the site does not have tabs) so it is better to use the local task
// title in that case than to fall back on the front page link in the
// active trail (which is usually "Home" and would not make sense in this
// context).
if ((bool) ($item['type'] & MENU_IS_LOCAL_TASK)) {
// A local task title is being skipped; track it in case it needs to be
// used later.
$local_task_title = $item['title'];
}
else {
// This is not a local task, so use it for the page title (unless the
// conditions described above are met).
if (isset($local_task_title) && isset($item['href']) && $item['href'] == '<front>') {
return $local_task_title;
}
else {
return $item['title'];
}
}
}
}

View File

@@ -227,6 +227,10 @@ function system_list_reset() {
drupal_static_reset('list_themes');
cache_clear_all('bootstrap_modules', 'cache_bootstrap');
cache_clear_all('system_list', 'cache_bootstrap');
// Clean up the bootstrap file scan cache.
drupal_static_reset('_drupal_file_scan_cache');
cache_clear_all('_drupal_file_scan_cache', 'cache_bootstrap');
}
/**
@@ -320,16 +324,27 @@ function module_load_install($module) {
* The name of the included file, if successful; FALSE otherwise.
*/
function module_load_include($type, $module, $name = NULL) {
static $files = array();
if (!isset($name)) {
$name = $module;
}
$key = $type . ':' . $module . ':' . $name;
if (isset($files[$key])) {
return $files[$key];
}
if (function_exists('drupal_get_path')) {
$file = DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . "/$name.$type";
if (is_file($file)) {
require_once $file;
$files[$key] = $file;
return $file;
}
else {
$files[$key] = FALSE;
}
}
return FALSE;
}
@@ -365,20 +380,22 @@ function module_load_all_includes($type, $name = NULL) {
* - Invoke hook_modules_installed().
* - Invoke hook_modules_enabled().
*
* @param $module_list
* @param string[] $module_list
* An array of module names.
* @param $enable_dependencies
* @param bool $enable_dependencies
* If TRUE, dependencies will automatically be added and enabled in the
* correct order. This incurs a significant performance cost, so use FALSE
* if you know $module_list is already complete and in the correct order.
*
* @return
* @return bool
* FALSE if one or more dependencies are missing, TRUE otherwise.
*
* @see hook_install()
* @see hook_enable()
* @see hook_modules_installed()
* @see hook_modules_enabled()
* @see module_disable()
* @see drupal_uninstall_modules()
*/
function module_enable($module_list, $enable_dependencies = TRUE) {
if ($enable_dependencies) {
@@ -505,12 +522,15 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
/**
* Disables a given set of modules.
*
* @param $module_list
* @param string[] $module_list
* An array of module names.
* @param $disable_dependents
* @param bool $disable_dependents
* If TRUE, dependent modules will automatically be added and disabled in the
* correct order. This incurs a significant performance cost, so use FALSE
* if you know $module_list is already complete and in the correct order.
*
* @see drupal_uninstall_modules()
* @see module_enable()
*/
function module_disable($module_list, $disable_dependents = TRUE) {
if ($disable_dependents) {
@@ -676,12 +696,16 @@ function module_hook($module, $hook) {
/**
* Determines which modules are implementing a hook.
*
* @param $hook
* Lazy-loaded include files specified with "group" via hook_hook_info() or
* hook_module_implements_alter() will be automatically included by this
* function when necessary.
*
* @param string $hook
* The name of the hook (e.g. "help" or "menu").
* @param $sort
* @param bool $sort
* By default, modules are ordered by weight and filename, settings this option
* to TRUE, module list will be ordered by module name.
* @param $reset
* @param bool $reset
* For internal use only: Whether to force the stored list of hook
* implementations to be regenerated (such as after enabling a new module,
* before processing hook_enable).
@@ -696,8 +720,10 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['implementations'] = &drupal_static(__FUNCTION__);
$drupal_static_fast['verified'] = &drupal_static(__FUNCTION__ . ':verified');
}
$implementations = &$drupal_static_fast['implementations'];
$verified = &$drupal_static_fast['verified'];
// We maintain a persistent cache of hook implementations in addition to the
// static cache to avoid looping through every module and every hook on each
@@ -711,14 +737,19 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
// per request.
if ($reset) {
$implementations = array();
$verified = array();
cache_set('module_implements', array(), 'cache_bootstrap');
drupal_static_reset('module_hook_info');
drupal_static_reset('drupal_alter');
cache_clear_all('hook_info', 'cache_bootstrap');
cache_clear_all('system_cache_tables', 'cache');
return;
}
// Fetch implementations from cache.
// This happens on the first call to module_implements(*, *, FALSE) during a
// request, but also when $implementations have been reset, e.g. after
// module_enable().
if (empty($implementations)) {
$implementations = cache_get('module_implements', 'cache_bootstrap');
if ($implementations === FALSE) {
@@ -727,12 +758,17 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
else {
$implementations = $implementations->data;
}
// Forget all previously "verified" hooks, in case that $implementations
// were cleared via drupal_static_reset('module_implements') instead of
// module_implements(*, *, TRUE).
$verified = array();
}
if (!isset($implementations[$hook])) {
// The hook is not cached, so ensure that whether or not it has
// implementations, that the cache is updated at the end of the request.
$implementations['#write_cache'] = TRUE;
// Discover implementations for this hook.
$hook_info = module_hook_info();
$implementations[$hook] = array();
$list = module_list(FALSE, FALSE, $sort);
@@ -744,13 +780,31 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
$implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE;
}
}
// Allow modules to change the weight of specific implementations but avoid
// Allow modules to change the weight of specific implementations, but avoid
// an infinite loop.
if ($hook != 'module_implements_alter') {
// Remember the implementations before hook_module_implements_alter().
$implementations_before = $implementations[$hook];
drupal_alter('module_implements', $implementations[$hook], $hook);
// Verify implementations that were added or modified.
foreach (array_diff_assoc($implementations[$hook], $implementations_before) as $module => $group) {
// If drupal_alter('module_implements') changed or added a $group, the
// respective file needs to be included.
if ($group) {
module_load_include('inc', $module, "$module.$group");
}
// If a new implementation was added, verify that the function exists.
if (!function_exists($module . '_' . $hook)) {
unset($implementations[$hook][$module]);
}
}
}
// Implementations for this hook are now "verified".
$verified[$hook] = TRUE;
}
else {
elseif (!isset($verified[$hook])) {
// Implementations for this hook were in the cache, but they are not
// "verified" yet.
foreach ($implementations[$hook] as $module => $group) {
// If this hook implementation is stored in a lazy-loaded file, so include
// that file first.
@@ -769,6 +823,7 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
$implementations['#write_cache'] = TRUE;
}
}
$verified[$hook] = TRUE;
}
return array_keys($implementations[$hook]);
@@ -833,6 +888,11 @@ function module_hook_info() {
* @see module_implements()
*/
function module_implements_write_cache() {
// The list of implementations includes vital modules only before full
// bootstrap, so do not write cache if we are not fully bootstrapped yet.
if (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL) {
return;
}
$implementations = &drupal_static('module_implements');
if (isset($implementations['#write_cache'])) {
unset($implementations['#write_cache']);
@@ -880,7 +940,9 @@ function module_invoke($module, $hook) {
*
* @return
* An array of return values of the hook implementations. If modules return
* arrays from their implementations, those are merged into one array.
* arrays from their implementations, those are merged into one array
* recursively. Note: integer keys in arrays will be lost, as the merge is
* done using array_merge_recursive().
*
* @see drupal_alter()
*/

View File

@@ -347,7 +347,8 @@ function drupal_match_path($path, $patterns) {
* drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL) makes this function available.
*
* @return
* The current Drupal URL path.
* The current Drupal URL path. The path is untrusted user input and must be
* treated as such.
*
* @see request_path()
*/

View File

@@ -164,7 +164,7 @@ function _registry_parse_files($files) {
* (optional) Weight of the module.
*/
function _registry_parse_file($filename, $contents, $module = '', $weight = 0) {
if (preg_match_all('/^\s*(?:abstract|final)?\s*(class|interface)\s+([a-zA-Z0-9_]+)/m', $contents, $matches)) {
if (preg_match_all('/^\s*(?:abstract|final)?\s*(class|interface|trait)\s+([a-zA-Z0-9_]+)/m', $contents, $matches)) {
foreach ($matches[2] as $key => $name) {
db_merge('registry')
->key(array(

View File

@@ -163,7 +163,7 @@ function _drupal_session_write($sid, $value) {
try {
if (!drupal_save_session()) {
// We don't have anything to do if we are not allowed to save the session.
return;
return TRUE;
}
// Check whether $_SESSION has been changed in this request.
@@ -425,7 +425,7 @@ function _drupal_session_destroy($sid) {
// Nothing to do if we are not allowed to change the session.
if (!drupal_save_session()) {
return;
return TRUE;
}
// Delete session data.
@@ -446,6 +446,8 @@ function _drupal_session_destroy($sid) {
elseif (variable_get('https', FALSE)) {
_drupal_session_delete_cookie('S' . session_name(), TRUE);
}
return TRUE;
}
/**

View File

@@ -1248,6 +1248,7 @@ function path_to_theme() {
function drupal_find_theme_functions($cache, $prefixes) {
$implementations = array();
$functions = get_defined_functions();
$theme_functions = preg_grep('/^(' . implode(')|(', $prefixes) . ')_/', $functions['user']);
foreach ($cache as $hook => $info) {
foreach ($prefixes as $prefix) {
@@ -1264,7 +1265,7 @@ function drupal_find_theme_functions($cache, $prefixes) {
// intermediary suggestion.
$pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__');
if (!isset($info['base hook']) && !empty($pattern)) {
$matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $functions['user']);
$matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $theme_functions);
if ($matches) {
foreach ($matches as $match) {
$new_hook = substr($match, strlen($prefix) + 1);
@@ -1710,11 +1711,29 @@ function theme_status_messages($variables) {
* copy if none of the enabled modules or the active theme implement any
* preprocess or process functions or override this theme implementation.
*
* @param $variables
* An associative array containing the keys 'text', 'path', and 'options'.
* See the l() function for information about these variables.
* @param array $variables
* An associative array containing the keys:
* - text: The text of the link.
* - path: The internal path or external URL being linked to. It is used as
* the $path parameter of the url() function.
* - options: (optional) An array that defaults to empty, but can contain:
* - attributes: Can contain optional attributes:
* - class: must be declared in an array. Example: 'class' =>
* array('class_name1','class_name2').
* - title: must be a string. Example: 'title' => 'Example title'
* - Others are more flexible as long as they work with
* drupal_attributes($variables['options']['attributes]).
* - html: Boolean flag that tells whether text contains HTML or plain
* text. If set to TRUE, the text value will not be sanitized so the
calling function must ensure that it already contains safe HTML.
* The elements $variables['options']['attributes'] and
* $variables['options']['html'] are used in this function similarly to the
* way that $options['attributes'] and $options['html'] are used in l().
* The link itself is built by the url() function, which takes
* $variables['path'] and $variables['options'] as arguments.
*
* @see l()
* @see url()
*/
function theme_link($variables) {
return '<a href="' . check_plain(url($variables['path'], $variables['options'])) . '"' . drupal_attributes($variables['options']['attributes']) . '>' . ($variables['options']['html'] ? $variables['text'] : check_plain($variables['text'])) . '</a>';
@@ -1791,7 +1810,8 @@ function theme_links($variables) {
foreach ($links as $key => $link) {
$class = array($key);
// Add first, last and active classes to the list of links to help out themers.
// Add first, last and active classes to the list of links to help out
// themers.
if ($i == 1) {
$class[] = 'first';
}
@@ -1809,7 +1829,8 @@ function theme_links($variables) {
$output .= l($link['title'], $link['href'], $link);
}
elseif (!empty($link['title'])) {
// Some links are actually not links, but we wrap these in <span> for adding title and class attributes.
// Some links are actually not links, but we wrap these in <span> for
// adding title and class attributes.
if (empty($link['html'])) {
$link['title'] = check_plain($link['title']);
}
@@ -2618,7 +2639,7 @@ function template_preprocess_page(&$variables) {
// Move some variables to the top level for themer convenience and template cleanliness.
$variables['show_messages'] = $variables['page']['#show_messages'];
foreach (system_region_list($GLOBALS['theme']) as $region_key => $region_name) {
foreach (system_region_list($GLOBALS['theme'], REGIONS_ALL, FALSE) as $region_key) {
if (!isset($variables['page'][$region_key])) {
$variables['page'][$region_key] = array();
}

View File

@@ -795,6 +795,14 @@ function update_fix_d7_requirements() {
function update_fix_d7_install_profile() {
$profile = drupal_get_profile();
// 'Default' profile has been renamed to 'Standard' in D7.
// We change the profile here to prevent a broken record in the system table.
// See system_update_7049().
if ($profile == 'default') {
$profile = 'standard';
variable_set('install_profile', $profile);
}
$results = db_select('system', 's')
->fields('s', array('name', 'schema_version'))
->condition('name', $profile)
@@ -908,6 +916,8 @@ function update_get_d6_session_name() {
}
/**
* Implements callback_batch_operation().
*
* Performs one update and stores the results for display on the results page.
*
* If an update function completes successfully, it should return a message
@@ -1078,6 +1088,8 @@ function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $
}
/**
* Implements callback_batch_finished().
*
* Finishes the update process and stores the results for eventual display.
*
* After the updates run, all caches are flushed. The update results are

View File

@@ -264,6 +264,10 @@ function xmlrpc_server_call($xmlrpc_server, $methodname, $args) {
*/
function xmlrpc_server_multicall($methodcalls) {
// See http://www.xmlrpc.com/discuss/msgReader$1208
// To avoid multicall expansion attacks, limit the number of duplicate method
// calls allowed with a default of 1. Set to -1 for unlimited.
$duplicate_method_limit = variable_get('xmlrpc_multicall_duplicate_method_limit', 1);
$method_count = array();
$return = array();
$xmlrpc_server = xmlrpc_server_get();
foreach ($methodcalls as $call) {
@@ -273,10 +277,14 @@ function xmlrpc_server_multicall($methodcalls) {
$ok = FALSE;
}
$method = $call['methodName'];
$method_count[$method] = isset($method_count[$method]) ? $method_count[$method] + 1 : 1;
$params = $call['params'];
if ($method == 'system.multicall') {
$result = xmlrpc_error(-32600, t('Recursive calls to system.multicall are forbidden.'));
}
elseif ($duplicate_method_limit > 0 && $method_count[$method] > $duplicate_method_limit) {
$result = xmlrpc_error(-156579, t('Too many duplicate method calls in system.multicall.'));
}
elseif ($ok) {
$result = xmlrpc_server_call($xmlrpc_server, $method, $params);
}