diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 4853101cf39..1cfc6dac8a5 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,4 +1,25 @@
+Drupal 7.51, 2016-10-05
+-----------------------
+- The Update module now also checks for updates to a disabled theme that is
+ used as an admin theme.
+- Exceptions thrown in dblog_watchdog() are now caught and ignored.
+- Clarified the warning that appears when modules are missing or have moved.
+- Log messages are now XSS filtered on display.
+- Draggable tables now work on touch screen devices.
+- Added a setting for allowing double underscores in CSS identifiers
+ (https://www.drupal.org/node/2810369).
+- If a user navigates away from a page while an Ajax request is running they
+ will no longer get an error message saying "An Ajax HTTP request terminated
+ abnormally".
+- The system_region_list() API function now takes an optional third parameter
+ which allows region name translations to be skipped when they are not needed
+ (API addition: https://www.drupal.org/node/2810365).
+- Numerous performance improvements.
+- Numerous bug fixes.
+- Numerous API documentation improvements.
+- Additional automated test coverage.
+
Drupal 7.50, 2016-07-07
-----------------------
- Added a new "administer fields" permission for trusted users, which is
diff --git a/MAINTAINERS.txt b/MAINTAINERS.txt
index b076fd788b7..5603a432915 100644
--- a/MAINTAINERS.txt
+++ b/MAINTAINERS.txt
@@ -145,7 +145,6 @@ User experience and usability
Node Access
- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
- Ken Rickard 'agentrickard' https://www.drupal.org/u/agentrickard
-- Jess Myrbo 'xjm' https://www.drupal.org/u/xjm
Security team
@@ -268,7 +267,6 @@ System module
- ?
Taxonomy module
-- Jess Myrbo 'xjm' https://www.drupal.org/u/xjm
- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
- Benjamin Doherty 'bangpound' https://www.drupal.org/u/bangpound
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index a722f0bef0f..0d2e19c0cea 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -8,7 +8,7 @@
/**
* The current system version.
*/
-define('VERSION', '7.50');
+define('VERSION', '7.51');
/**
* Core API compatibility.
@@ -1088,8 +1088,8 @@ function _drupal_get_filename_perform_file_scan($type, $name) {
*/
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/2762241
- if (empty($name) || ($type == 'module' && $name == 'default')) {
+ // @todo Remove this in https://www.drupal.org/node/2383823
+ if (empty($name)) {
return;
}
@@ -1101,7 +1101,7 @@ function _drupal_get_filename_fallback_trigger_error($type, $name, $error_type)
// 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. In order to fix this, put the @type back in its original location. For more information, see the documentation page.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING);
+ _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 the documentation page.', 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 the documentation page.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING);
diff --git a/includes/common.inc b/includes/common.inc
index 9d682dc83d0..d2f54b31cdd 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -3900,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);
diff --git a/includes/database/database.inc b/includes/database/database.inc
index 21b7c22ac08..6879f699162 100644
--- a/includes/database/database.inc
+++ b/includes/database/database.inc
@@ -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'] : '');
@@ -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];
}
/**
diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc
index 4a44e6864b2..356e039f732 100644
--- a/includes/database/mysql/database.inc
+++ b/includes/database/mysql/database.inc
@@ -240,7 +240,7 @@ class DatabaseConnection_mysql extends DatabaseConnection {
// 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");
+ $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;
diff --git a/includes/file.inc b/includes/file.inc
index b15e4540a67..de9d17d6916 100644
--- a/includes/file.inc
+++ b/includes/file.inc
@@ -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);
diff --git a/includes/locale.inc b/includes/locale.inc
index f041659d300..11f1413eec6 100644
--- a/includes/locale.inc
+++ b/includes/locale.inc
@@ -667,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');
@@ -753,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);
diff --git a/includes/theme.inc b/includes/theme.inc
index ff54d6e2c54..9b606e9fb19 100644
--- a/includes/theme.inc
+++ b/includes/theme.inc
@@ -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);
@@ -2638,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();
}
diff --git a/misc/ajax.js b/misc/ajax.js
index bb4a6e14f99..c944ebbf246 100644
--- a/misc/ajax.js
+++ b/misc/ajax.js
@@ -476,7 +476,7 @@ Drupal.ajax.prototype.getEffect = function (response) {
* Handler for the form redirection error.
*/
Drupal.ajax.prototype.error = function (xmlhttprequest, uri, customMessage) {
- alert(Drupal.ajaxError(xmlhttprequest, uri, customMessage));
+ Drupal.displayAjaxError(Drupal.ajaxError(xmlhttprequest, uri, customMessage));
// Remove the progress element.
if (this.progress.element) {
$(this.progress.element).remove();
diff --git a/misc/autocomplete.js b/misc/autocomplete.js
index d71441b6c73..af090713c73 100644
--- a/misc/autocomplete.js
+++ b/misc/autocomplete.js
@@ -310,7 +310,7 @@ Drupal.ACDB.prototype.search = function (searchString) {
}
},
error: function (xmlhttp) {
- alert(Drupal.ajaxError(xmlhttp, db.uri));
+ Drupal.displayAjaxError(Drupal.ajaxError(xmlhttp, db.uri));
}
});
}, this.delay);
diff --git a/misc/drupal.js b/misc/drupal.js
index 427c4a1e29e..03eef50edc9 100644
--- a/misc/drupal.js
+++ b/misc/drupal.js
@@ -413,6 +413,29 @@ Drupal.getSelection = function (element) {
return { 'start': element.selectionStart, 'end': element.selectionEnd };
};
+/**
+ * Add a global variable which determines if the window is being unloaded.
+ *
+ * This is primarily used by Drupal.displayAjaxError().
+ */
+Drupal.beforeUnloadCalled = false;
+$(window).bind('beforeunload pagehide', function () {
+ Drupal.beforeUnloadCalled = true;
+});
+
+/**
+ * Displays a JavaScript error from an Ajax response when appropriate to do so.
+ */
+Drupal.displayAjaxError = function (message) {
+ // Skip displaying the message if the user deliberately aborted (for example,
+ // by reloading the page or navigating to a different page) while the Ajax
+ // request was still ongoing. See, for example, the discussion at
+ // http://stackoverflow.com/questions/699941/handle-ajax-error-when-a-user-clicks-refresh.
+ if (!Drupal.beforeUnloadCalled) {
+ alert(message);
+ }
+};
+
/**
* Build an error message from an Ajax response.
*/
diff --git a/misc/tabledrag.js b/misc/tabledrag.js
index 3cc270194f9..4e07784c7df 100644
--- a/misc/tabledrag.js
+++ b/misc/tabledrag.js
@@ -106,8 +106,10 @@ Drupal.tableDrag = function (table, tableSettings) {
// Add mouse bindings to the document. The self variable is passed along
// as event handlers do not have direct access to the tableDrag object.
- $(document).bind('mousemove', function (event) { return self.dragRow(event, self); });
- $(document).bind('mouseup', function (event) { return self.dropRow(event, self); });
+ $(document).bind('mousemove pointermove', function (event) { return self.dragRow(event, self); });
+ $(document).bind('mouseup pointerup', function (event) { return self.dropRow(event, self); });
+ $(document).bind('touchmove', function (event) { return self.dragRow(event.originalEvent.touches[0], self); });
+ $(document).bind('touchend', function (event) { return self.dropRow(event.originalEvent.touches[0], self); });
};
/**
@@ -274,7 +276,10 @@ Drupal.tableDrag.prototype.makeDraggable = function (item) {
});
// Add the mousedown action for the handle.
- handle.mousedown(function (event) {
+ handle.bind('mousedown touchstart pointerdown', function (event) {
+ if (event.originalEvent.type == "touchstart") {
+ event = event.originalEvent.touches[0];
+ }
// Create a new dragObject recording the event information.
self.dragObject = {};
self.dragObject.initMouseOffset = self.getMouseOffset(item, event);
diff --git a/modules/aggregator/aggregator.processor.inc b/modules/aggregator/aggregator.processor.inc
index 44ed549962a..534cca5777e 100644
--- a/modules/aggregator/aggregator.processor.inc
+++ b/modules/aggregator/aggregator.processor.inc
@@ -72,7 +72,7 @@ function aggregator_aggregator_remove($feed) {
*/
function aggregator_form_aggregator_admin_form_alter(&$form, $form_state) {
if (in_array('aggregator', variable_get('aggregator_processors', array('aggregator')))) {
- $info = module_invoke('aggregator', 'aggregator_process', 'info');
+ $info = module_invoke('aggregator', 'aggregator_process_info');
$items = drupal_map_assoc(array(3, 5, 10, 15, 20, 25), '_aggregator_items');
$period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
$period[AGGREGATOR_CLEAR_NEVER] = t('Never');
diff --git a/modules/block/block.module b/modules/block/block.module
index ca41da71cf9..73e11621137 100644
--- a/modules/block/block.module
+++ b/modules/block/block.module
@@ -285,8 +285,7 @@ function block_page_build(&$page) {
// Append region description if we are rendering the regions demo page.
$item = menu_get_item();
if ($item['path'] == 'admin/structure/block/demo/' . $theme) {
- $visible_regions = array_keys(system_region_list($theme, REGIONS_VISIBLE));
- foreach ($visible_regions as $region) {
+ foreach (system_region_list($theme, REGIONS_VISIBLE, FALSE) as $region) {
$description = '
' . $all_regions[$region] . '
';
$page[$region]['block_description'] = array(
'#markup' => $description,
diff --git a/modules/dblog/dblog.admin.inc b/modules/dblog/dblog.admin.inc
index 7c1c0e20f3e..0d5780cb018 100644
--- a/modules/dblog/dblog.admin.inc
+++ b/modules/dblog/dblog.admin.inc
@@ -294,11 +294,18 @@ function theme_dblog_message($variables) {
else {
$output = t($event->message, unserialize($event->variables));
}
+ // If the output is expected to be a link, strip all the tags and
+ // special characters by using filter_xss() without any allowed tags.
+ // If not, use filter_xss_admin() to allow some tags.
if ($variables['link'] && isset($event->wid)) {
- // Truncate message to 56 chars.
+ // Truncate message to 56 chars after stripping all the tags.
$output = truncate_utf8(filter_xss($output, array()), 56, TRUE, TRUE);
$output = l($output, 'admin/reports/event/' . $event->wid, array('html' => TRUE));
}
+ else {
+ // Prevent XSS in log detail pages.
+ $output = filter_xss_admin($output);
+ }
}
return $output;
}
diff --git a/modules/dblog/dblog.install b/modules/dblog/dblog.install
index abfd9a2c979..c2e41192198 100644
--- a/modules/dblog/dblog.install
+++ b/modules/dblog/dblog.install
@@ -154,6 +154,15 @@ function dblog_update_7002() {
db_add_index('watchdog', 'severity', array('severity'));
}
+/**
+ * Account for possible legacy systems where dblog was not installed.
+ */
+function dblog_update_7003() {
+ if (!db_table_exists('watchdog')) {
+ db_create_table('watchdog', drupal_get_schema_unprocessed('dblog', 'watchdog'));
+ }
+}
+
/**
* @} End of "addtogroup updates-7.x-extra".
*/
diff --git a/modules/dblog/dblog.module b/modules/dblog/dblog.module
index eb79faffcd2..df305a2c388 100644
--- a/modules/dblog/dblog.module
+++ b/modules/dblog/dblog.module
@@ -147,20 +147,27 @@ function dblog_watchdog(array $log_entry) {
if (!function_exists('drupal_substr')) {
require_once DRUPAL_ROOT . '/includes/unicode.inc';
}
- Database::getConnection('default', 'default')->insert('watchdog')
- ->fields(array(
- 'uid' => $log_entry['uid'],
- 'type' => drupal_substr($log_entry['type'], 0, 64),
- 'message' => $log_entry['message'],
- 'variables' => serialize($log_entry['variables']),
- 'severity' => $log_entry['severity'],
- 'link' => drupal_substr($log_entry['link'], 0, 255),
- 'location' => $log_entry['request_uri'],
- 'referer' => $log_entry['referer'],
- 'hostname' => drupal_substr($log_entry['ip'], 0, 128),
- 'timestamp' => $log_entry['timestamp'],
- ))
- ->execute();
+ try {
+ Database::getConnection('default', 'default')->insert('watchdog')
+ ->fields(array(
+ 'uid' => $log_entry['uid'],
+ 'type' => drupal_substr($log_entry['type'], 0, 64),
+ 'message' => $log_entry['message'],
+ 'variables' => serialize($log_entry['variables']),
+ 'severity' => $log_entry['severity'],
+ 'link' => drupal_substr($log_entry['link'], 0, 255),
+ 'location' => $log_entry['request_uri'],
+ 'referer' => $log_entry['referer'],
+ 'hostname' => drupal_substr($log_entry['ip'], 0, 128),
+ 'timestamp' => $log_entry['timestamp'],
+ ))
+ ->execute();
+ }
+ catch (Exception $e) {
+ // Exception is ignored so that watchdog does not break pages during the
+ // installation process or is not able to create the watchdog table during
+ // installation.
+ }
}
/**
diff --git a/modules/dblog/dblog.test b/modules/dblog/dblog.test
index a233d971bfa..b0a58ba4543 100644
--- a/modules/dblog/dblog.test
+++ b/modules/dblog/dblog.test
@@ -520,6 +520,33 @@ class DBLogTestCase extends DrupalWebTestCase {
$this->assertText(t('Database log cleared.'), 'Confirmation message found');
}
+ /**
+ * Verifies that exceptions are caught in dblog_watchdog().
+ */
+ protected function testDBLogException() {
+ $log = array(
+ 'type' => 'custom',
+ 'message' => 'Log entry added to test watchdog handling of Exceptions.',
+ 'variables' => array(),
+ 'severity' => WATCHDOG_NOTICE,
+ 'link' => NULL,
+ 'user' => $this->big_user,
+ 'uid' => isset($this->big_user->uid) ? $this->big_user->uid : 0,
+ 'request_uri' => request_uri(),
+ 'referer' => $_SERVER['HTTP_REFERER'],
+ 'ip' => ip_address(),
+ 'timestamp' => REQUEST_TIME,
+ );
+
+ // Remove watchdog table temporarily to simulate it missing during
+ // installation.
+ db_query("DROP TABLE {watchdog}");
+
+ // Add a watchdog entry.
+ // This should not throw an Exception, but fail silently.
+ dblog_watchdog($log);
+ }
+
/**
* Gets the database log event information from the browser page.
*
@@ -638,4 +665,32 @@ class DBLogTestCase extends DrupalWebTestCase {
// Document Object Model (DOM).
$this->assertLink(html_entity_decode($message_text), 0, $message);
}
+
+ /**
+ * Make sure HTML tags are filtered out in the log detail page.
+ */
+ public function testLogMessageSanitized() {
+ $this->drupalLogin($this->big_user);
+
+ // Make sure dangerous HTML tags are filtered out in log detail page.
+ $log = array(
+ 'uid' => 0,
+ 'type' => 'custom',
+ 'message' => " Lorem ipsum",
+ 'variables' => NULL,
+ 'severity' => WATCHDOG_NOTICE,
+ 'link' => 'foo/bar',
+ 'request_uri' => 'http://example.com?dblog=1',
+ 'referer' => 'http://example.org?dblog=2',
+ 'ip' => '0.0.1.0',
+ 'timestamp' => REQUEST_TIME,
+ );
+ dblog_watchdog($log);
+
+ $wid = db_query('SELECT MAX(wid) FROM {watchdog}')->fetchField();
+ $this->drupalGet('admin/reports/event/' . $wid);
+ $this->assertResponse(200);
+ $this->assertNoRaw("");
+ $this->assertRaw("alert('foo'); Lorem ipsum");
+ }
}
diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc
index ba377083247..7c0e3a154a5 100644
--- a/modules/field/field.crud.inc
+++ b/modules/field/field.crud.inc
@@ -189,7 +189,7 @@ function field_create_field($field) {
}
// Clear caches
- field_cache_clear(TRUE);
+ field_cache_clear();
// Invoke external hooks after the cache is cleared for API consistency.
module_invoke_all('field_create_field', $field);
@@ -288,7 +288,7 @@ function field_update_field($field) {
drupal_write_record('field_config', $field, $primary_key);
// Clear caches
- field_cache_clear(TRUE);
+ field_cache_clear();
// Invoke external hooks after the cache is cleared for API consistency.
module_invoke_all('field_update_field', $field, $prior_field, $has_data);
@@ -430,7 +430,7 @@ function field_delete_field($field_name) {
->execute();
// Clear the cache.
- field_cache_clear(TRUE);
+ field_cache_clear();
module_invoke_all('field_delete_field', $field);
}
diff --git a/modules/locale/locale.admin.inc b/modules/locale/locale.admin.inc
index e813962d02b..acf6eb2ebe1 100644
--- a/modules/locale/locale.admin.inc
+++ b/modules/locale/locale.admin.inc
@@ -1194,7 +1194,7 @@ function locale_translate_edit_form_submit($form, &$form_state) {
$translation = db_query("SELECT translation FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $key))->fetchField();
if (!empty($value)) {
// Only update or insert if we have a value to use.
- if (!empty($translation)) {
+ if (is_string($translation)) {
db_update('locales_target')
->fields(array(
'translation' => $value,
diff --git a/modules/locale/locale.test b/modules/locale/locale.test
index d2af76715a2..6fcf06fe5e6 100644
--- a/modules/locale/locale.test
+++ b/modules/locale/locale.test
@@ -393,6 +393,16 @@ class LocaleTranslationFunctionalTest extends DrupalWebTestCase {
// The indicator should not be here.
$this->assertNoRaw($language_indicator, 'String is translated.');
+ // Verify that a translation set which has an empty target string can be
+ // updated without any database error.
+ db_update('locales_target')
+ ->fields(array('translation' => ''))
+ ->condition('language', $langcode, '=')
+ ->condition('lid', $lid, '=')
+ ->execute();
+ $this->drupalPost('admin/config/regional/translate/edit/' . $lid, $edit, t('Save translations'));
+ $this->assertText(t('The string has been saved.'), 'The string has been saved.');
+
// Try to edit a non-existent string and ensure we're redirected correctly.
// Assuming we don't have 999,999 strings already.
$random_lid = 999999;
diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test
index 92aefe48fef..0f991c3c740 100644
--- a/modules/simpletest/tests/common.test
+++ b/modules/simpletest/tests/common.test
@@ -947,6 +947,31 @@ class DrupalHTMLIdentifierTestCase extends DrupalUnitTestCase {
// Verify that invalid characters (including non-breaking space) are stripped from the identifier.
$this->assertIdentical(drupal_clean_css_identifier('invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ identifier', array()), 'invalididentifier', 'Strip invalid characters.');
+
+ // Verify that double underscores are replaced in the identifier by default.
+ $identifier = 'css__identifier__with__double__underscores';
+ $expected = 'css--identifier--with--double--underscores';
+ $this->assertIdentical(drupal_clean_css_identifier($identifier), $expected, 'Verify double underscores are replaced with double hyphens by default.');
+
+ // Verify that double underscores are preserved in the identifier if the
+ // variable allow_css_double_underscores is set to TRUE.
+ $this->setAllowCSSDoubleUnderscores(TRUE);
+ $this->assertIdentical(drupal_clean_css_identifier($identifier), $identifier, 'Verify double underscores are preserved if the allow_css_double_underscores set to TRUE.');
+
+ // To avoid affecting other test cases, set the variable
+ // allow_css_double_underscores to FALSE which is the default value.
+ $this->setAllowCSSDoubleUnderscores(FALSE);
+ }
+
+ /**
+ * Set the variable allow_css_double_underscores and reset the cache.
+ *
+ * @param $value bool
+ * A new value to be set to allow_css_double_underscores.
+ */
+ function setAllowCSSDoubleUnderscores($value) {
+ $GLOBALS['conf']['allow_css_double_underscores'] = $value;
+ drupal_static_reset('drupal_clean_css_identifier:allow_css_double_underscores');
}
/**
@@ -1254,7 +1279,7 @@ class DrupalSetContentTestCase extends DrupalWebTestCase {
function testRegions() {
global $theme_key;
- $block_regions = array_keys(system_region_list($theme_key));
+ $block_regions = system_region_list($theme_key, REGIONS_ALL, FALSE);
$delimiter = $this->randomName(32);
$values = array();
// Set some random content for each region available.
diff --git a/modules/simpletest/tests/update_script_test.install b/modules/simpletest/tests/update_script_test.install
index 6955ef11d93..4024fb4a631 100644
--- a/modules/simpletest/tests/update_script_test.install
+++ b/modules/simpletest/tests/update_script_test.install
@@ -31,6 +31,19 @@ function update_script_test_requirements($phase) {
'severity' => REQUIREMENT_ERROR,
);
break;
+ case REQUIREMENT_INFO:
+ $requirements['update_script_test_stop'] = array(
+ 'title' => 'Update script test stop',
+ 'value' => 'Error',
+ 'description' => 'This is a requirements error provided by the update_script_test module to stop the page redirect for the info.',
+ 'severity' => REQUIREMENT_ERROR,
+ );
+ $requirements['update_script_test'] = array(
+ 'title' => 'Update script test',
+ 'description' => 'This is a requirements info provided by the update_script_test module.',
+ 'severity' => REQUIREMENT_INFO,
+ );
+ break;
}
}
diff --git a/modules/statistics/statistics.test b/modules/statistics/statistics.test
index 7e038d61207..50accd74147 100644
--- a/modules/statistics/statistics.test
+++ b/modules/statistics/statistics.test
@@ -35,7 +35,7 @@ class StatisticsTestCase extends DrupalWebTestCase {
'title' => 'test',
'path' => 'node/1',
'url' => 'http://example.com',
- 'hostname' => '192.168.1.1',
+ 'hostname' => '1.2.3.3',
'uid' => 0,
'sid' => 10,
'timer' => 10,
@@ -268,7 +268,7 @@ class StatisticsBlockVisitorsTestCase extends StatisticsTestCase {
*/
function testIPAddressBlocking() {
// IP address for testing.
- $test_ip_address = '192.168.1.1';
+ $test_ip_address = '1.2.3.3';
// Verify the IP address from accesslog appears on the top visitors page
// and that a 'block IP address' link is displayed.
diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc
index 8ef7d7c6b18..cdcc78fb649 100644
--- a/modules/system/system.admin.inc
+++ b/modules/system/system.admin.inc
@@ -2597,6 +2597,8 @@ function theme_status_report($variables) {
if (empty($requirement['#type'])) {
$severity = $severities[isset($requirement['severity']) ? (int) $requirement['severity'] : REQUIREMENT_OK];
$severity['icon'] = '' . $severity['title'] . '
';
+ // The requirement's 'value' key is optional, provide a default value.
+ $requirement['value'] = isset($requirement['value']) ? $requirement['value'] : '';
// Output table row(s)
if (!empty($requirement['description'])) {
diff --git a/modules/system/system.install b/modules/system/system.install
index fa794eb6bf9..ae55b892fed 100644
--- a/modules/system/system.install
+++ b/modules/system/system.install
@@ -3270,6 +3270,21 @@ function system_update_7080() {
db_change_field('date_format_locale', 'format', 'format', $spec);
}
+/**
+ * Remove the Drupal 6 default install profile if it is still in the database.
+ */
+function system_update_7081() {
+ // Sites which used the default install profile in Drupal 6 and then updated
+ // to Drupal 7.44 or earlier will still have a record of this install profile
+ // in the database that needs to be deleted.
+ db_delete('system')
+ ->condition('filename', 'profiles/default/default.profile')
+ ->condition('type', 'module')
+ ->condition('status', 0)
+ ->condition('schema_version', 0)
+ ->execute();
+}
+
/**
* @} End of "defgroup updates-7.x-extra".
* The next series of updates should start at 8000.
diff --git a/modules/system/system.module b/modules/system/system.module
index 8a080faeee5..59087c88485 100644
--- a/modules/system/system.module
+++ b/modules/system/system.module
@@ -2705,10 +2705,17 @@ function system_find_base_themes($themes, $key, $used_keys = array()) {
* @param $show
* Possible values: REGIONS_ALL or REGIONS_VISIBLE. Visible excludes hidden
* regions.
- * @return
- * An array of regions in the form $region['name'] = 'description'.
+ * @param bool $labels
+ * (optional) Boolean to specify whether the human readable machine names
+ * should be returned or not. Defaults to TRUE, but calling code can set
+ * this to FALSE for better performance, if it only needs machine names.
+ *
+ * @return array
+ * An associative array of regions in the form $region['name'] = 'description'
+ * if $labels is set to TRUE, or $region['name'] = 'name', if $labels is set
+ * to FALSE.
*/
-function system_region_list($theme_key, $show = REGIONS_ALL) {
+function system_region_list($theme_key, $show = REGIONS_ALL, $labels = TRUE) {
$themes = list_themes();
if (!isset($themes[$theme_key])) {
return array();
@@ -2719,10 +2726,14 @@ function system_region_list($theme_key, $show = REGIONS_ALL) {
// If requested, suppress hidden regions. See block_admin_display_form().
foreach ($info['regions'] as $name => $label) {
if ($show == REGIONS_ALL || !isset($info['regions_hidden']) || !in_array($name, $info['regions_hidden'])) {
- $list[$name] = t($label);
+ if ($labels) {
+ $list[$name] = t($label);
+ }
+ else {
+ $list[$name] = $name;
+ }
}
}
-
return $list;
}
@@ -2743,12 +2754,13 @@ function system_system_info_alter(&$info, $file, $type) {
*
* @param $theme
* The name of a theme.
+ *
* @return
* A string that is the region name.
*/
function system_default_region($theme) {
- $regions = array_keys(system_region_list($theme, REGIONS_VISIBLE));
- return isset($regions[0]) ? $regions[0] : '';
+ $regions = system_region_list($theme, REGIONS_VISIBLE, FALSE);
+ return $regions ? reset($regions) : '';
}
/**
@@ -3354,7 +3366,8 @@ function system_goto_action($entity, $context) {
*/
function system_block_ip_action() {
$ip = ip_address();
- db_insert('blocked_ips')
+ db_merge('blocked_ips')
+ ->key(array('ip' => $ip))
->fields(array('ip' => $ip))
->execute();
watchdog('action', 'Banned IP address %ip', array('%ip' => $ip));
@@ -3516,8 +3529,7 @@ function system_retrieve_file($url, $destination = NULL, $managed = FALSE, $repl
function system_page_alter(&$page) {
// Find all non-empty page regions, and add a theme wrapper function that
// allows them to be consistently themed.
- $regions = system_region_list($GLOBALS['theme']);
- foreach (array_keys($regions) as $region) {
+ foreach (system_region_list($GLOBALS['theme'], REGIONS_ALL, FALSE) as $region) {
if (!empty($page[$region])) {
$page[$region]['#theme_wrappers'][] = 'region';
$page[$region]['#region'] = $region;
diff --git a/modules/system/system.test b/modules/system/system.test
index 0542adfa000..ec71093dcde 100644
--- a/modules/system/system.test
+++ b/modules/system/system.test
@@ -726,7 +726,7 @@ class IPAddressBlockingTestCase extends DrupalWebTestCase {
// Block a valid IP address.
$edit = array();
- $edit['ip'] = '192.168.1.1';
+ $edit['ip'] = '1.2.3.3';
$this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add'));
$ip = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $edit['ip']))->fetchField();
$this->assertTrue($ip, t('IP address found in database.'));
@@ -734,7 +734,7 @@ class IPAddressBlockingTestCase extends DrupalWebTestCase {
// Try to block an IP address that's already blocked.
$edit = array();
- $edit['ip'] = '192.168.1.1';
+ $edit['ip'] = '1.2.3.3';
$this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add'));
$this->assertText(t('This IP address is already blocked.'));
@@ -770,6 +770,25 @@ class IPAddressBlockingTestCase extends DrupalWebTestCase {
// $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Save'));
// $this->assertText(t('You may not block your own IP address.'));
}
+
+ /**
+ * Test duplicate IP addresses are not present in the 'blocked_ips' table.
+ */
+ function testDuplicateIpAddress() {
+ drupal_static_reset('ip_address');
+ $submit_ip = $_SERVER['REMOTE_ADDR'] = '192.168.1.1';
+ system_block_ip_action();
+ system_block_ip_action();
+ $ip_count = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->rowCount();
+ $this->assertEqual('1', $ip_count);
+ drupal_static_reset('ip_address');
+ $submit_ip = $_SERVER['REMOTE_ADDR'] = ' ';
+ system_block_ip_action();
+ system_block_ip_action();
+ system_block_ip_action();
+ $ip_count = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->rowCount();
+ $this->assertEqual('1', $ip_count);
+ }
}
class CronRunTestCase extends DrupalWebTestCase {
@@ -2449,6 +2468,12 @@ class UpdateScriptFunctionalTest extends DrupalWebTestCase {
$this->assertText('This is a requirements error provided by the update_script_test module.');
$this->clickLink('try again');
$this->assertText('This is a requirements error provided by the update_script_test module.');
+
+ // Check if the optional 'value' key displays without a notice.
+ variable_set('update_script_test_requirement_type', REQUIREMENT_INFO);
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->assertText('This is a requirements info provided by the update_script_test module.');
+ $this->assertNoText('Notice: Undefined index: value in theme_status_report()');
}
/**
diff --git a/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info b/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info
new file mode 100644
index 00000000000..57dbf13e1c4
--- /dev/null
+++ b/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info
@@ -0,0 +1,4 @@
+name = Update test admin theme
+description = Test theme which is used as admin theme.
+core = 7.x
+hidden = TRUE
diff --git a/modules/update/tests/update_test.module b/modules/update/tests/update_test.module
index 6fe4bddea1e..594f80f0469 100644
--- a/modules/update/tests/update_test.module
+++ b/modules/update/tests/update_test.module
@@ -11,6 +11,7 @@
function update_test_system_theme_info() {
$themes['update_test_basetheme'] = drupal_get_path('module', 'update_test') . '/themes/update_test_basetheme/update_test_basetheme.info';
$themes['update_test_subtheme'] = drupal_get_path('module', 'update_test') . '/themes/update_test_subtheme/update_test_subtheme.info';
+ $themes['update_test_admintheme'] = drupal_get_path('module', 'update_test') . '/themes/update_test_admintheme/update_test_admintheme.info';
return $themes;
}
diff --git a/modules/update/update.compare.inc b/modules/update/update.compare.inc
index 072a0daaab3..e3e0de3bbaa 100644
--- a/modules/update/update.compare.inc
+++ b/modules/update/update.compare.inc
@@ -104,7 +104,13 @@ function update_get_projects() {
* @see update_get_projects()
*/
function _update_process_info_list(&$projects, $list, $project_type, $status) {
+ $admin_theme = variable_get('admin_theme', 'seven');
foreach ($list as $file) {
+ // The admin theme is a special case. It should always be considered enabled
+ // for the purposes of update checking.
+ if ($file->name === $admin_theme) {
+ $file->status = TRUE;
+ }
// A disabled base theme of an enabled sub-theme still has all of its code
// run by the sub-theme, so we include it in our "enabled" projects list.
if ($status && !$file->status && !empty($file->sub_themes)) {
diff --git a/modules/update/update.manager.inc b/modules/update/update.manager.inc
index 0b33a5f72a3..c7c4e4a68ad 100644
--- a/modules/update/update.manager.inc
+++ b/modules/update/update.manager.inc
@@ -59,7 +59,7 @@
* @see update_menu()
* @ingroup forms
*/
-function update_manager_update_form($form, $form_state = array(), $context) {
+function update_manager_update_form($form, $form_state, $context) {
if (!_update_manager_check_backends($form, 'update')) {
return $form;
}
diff --git a/modules/update/update.test b/modules/update/update.test
index 9e04cdaef1f..5ce5bb88b1c 100644
--- a/modules/update/update.test
+++ b/modules/update/update.test
@@ -462,6 +462,55 @@ class UpdateTestContribCase extends UpdateTestHelper {
$this->assertRaw(l(t('Update test base theme'), 'http://example.com/project/update_test_basetheme'), 'Link to the Update test base theme project appears.');
}
+ /**
+ * Tests that the admin theme is always notified about security updates.
+ */
+ function testUpdateAdminThemeSecurityUpdate() {
+ // Disable the admin theme.
+ db_update('system')
+ ->fields(array('status' => 0))
+ ->condition('type', 'theme')
+ ->condition('name', 'update_test_%', 'LIKE')
+ ->execute();
+
+ variable_set('admin_theme', 'update_test_admintheme');
+
+ // Define the initial state for core and the themes.
+ $system_info = array(
+ '#all' => array(
+ 'version' => '7.0',
+ ),
+ 'update_test_admintheme' => array(
+ 'project' => 'update_test_admintheme',
+ 'version' => '7.x-1.0',
+ 'hidden' => FALSE,
+ ),
+ 'update_test_basetheme' => array(
+ 'project' => 'update_test_basetheme',
+ 'version' => '7.x-1.1',
+ 'hidden' => FALSE,
+ ),
+ 'update_test_subtheme' => array(
+ 'project' => 'update_test_subtheme',
+ 'version' => '7.x-1.0',
+ 'hidden' => FALSE,
+ ),
+ );
+ variable_set('update_test_system_info', $system_info);
+ variable_set('update_check_disabled', FALSE);
+ $xml_mapping = array(
+ // This is enough because we don't check the update status of the admin
+ // theme. We want to check that the admin theme is included in the list.
+ 'drupal' => '0',
+ );
+ $this->refreshUpdateStatus($xml_mapping);
+ // The admin theme is displayed even if it's disabled.
+ $this->assertText('update_test_admintheme', "The admin theme is checked for update even if it's disabled");
+ // The other disabled themes are not displayed.
+ $this->assertNoText('update_test_basetheme', 'Disabled theme is not checked for update in the list.');
+ $this->assertNoText('update_test_subtheme', 'Disabled theme is not checked for update in the list.');
+ }
+
/**
* Tests that disabled themes are only shown when desired.
*/
@@ -800,4 +849,4 @@ class UpdateCoreUnitTestCase extends DrupalUnitTestCase {
$this->assertEqual($url, $expected, "When ? is present, '$url' should be '$expected'.");
}
-}
\ No newline at end of file
+}
diff --git a/modules/user/user.module b/modules/user/user.module
index 0ba9654bfa5..b818d79ab57 100644
--- a/modules/user/user.module
+++ b/modules/user/user.module
@@ -418,8 +418,6 @@ function user_load_by_name($name) {
*
* @return
* A fully-loaded $user object upon successful save or FALSE if the save failed.
- *
- * @todo D8: Drop $edit and fix user_save() to be consistent with others.
*/
function user_save($account, $edit = array(), $category = 'account') {
$transaction = db_transaction();
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index 4daeefd09e4..09143f57954 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -155,7 +155,7 @@
* These settings are available as of MySQL 5.5.14, and are defaults in
* MySQL 5.7.7 and up.
* - The PHP MySQL driver must support the utf8mb4 charset (libmysqlclient
- 5.5.3 and up, as well as mysqlnd 5.0.9 and up).
+ * 5.5.3 and up, as well as mysqlnd 5.0.9 and up).
* - The MySQL server must support the utf8mb4 charset (5.5.3 and up).
*
* You can optionally set prefixes for some or all database table names
@@ -625,3 +625,15 @@
* Remove the leading hash sign to enable.
*/
# $conf['theme_debug'] = TRUE;
+
+/**
+ * CSS identifier double underscores allowance:
+ *
+ * To allow CSS identifiers to contain double underscores (.example__selector)
+ * for Drupal's BEM-style naming standards, uncomment the line below.
+ * Note that if you change this value in existing sites, existing page styles
+ * may be broken.
+ *
+ * @see drupal_clean_css_identifier()
+ */
+# $conf['allow_css_double_underscores'] = TRUE;