diff --git a/.gitignore b/.gitignore
index 179ce96..1d8a4ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,10 @@
# Ignore configuration files that may contain sensitive information.
sites/*/settings*.php
-sites/sites.php
-sites/*.local
-sites/*.local*
-sites/default/files/*
-*.DS_store
# Ignore paths that contain user-generated content.
sites/*/files
sites/*/private
+
+# Ignore acquia dev desktop files
+sites/*.local
+sites/sites.php
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 02c9465..dbe104f 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,4 +1,155 @@
+Drupal 7.37, 2015-05-07
+-----------------------
+- Fixed a regression in Drupal 7.36 which caused certain kinds of content types
+ to become disabled if they were defined by a no-longer-enabled module.
+- Removed a confusing description regarding automatic time zone detection from
+ the user account form (minor UI and data structure change).
+- Allowed custom HTML tags with a dash in the name to pass through filter_xss()
+ when specified in the list of allowed tags.
+- Allowed hook_field_schema() implementations to specify indexes for fields
+ based on a fixed-length column prefix (rather than the entire column), as was
+ already allowed in hook_schema() implementations.
+- Fixed PDO exceptions on PostgreSQL when accessing invalid entity URLs.
+- Added a sites/all/libraries folder to the codebase, with instructions for
+ using it.
+- Added a description to the "Administer text formats and filters" permission
+ on the Permissions page (string change).
+- Numerous small bug fixes.
+- Numerous API documentation improvements.
+- Additional automated test coverage.
+
+Drupal 7.36, 2015-04-01
+-----------------------
+- Added a 'file_public_schema' variable which allows modules that define
+ publicly-accessible streams in hook_stream_wrappers() to bypass file download
+ access checks when processing managed file upload fields.
+- Fixed a bug that caused database query tags not to be added to search-related
+ database queries under many circumstances, and which prevented the
+ corresponding hook_query_TAG_alter() implementations from being called.
+- Fixed the "for" attribute on managed file upload field labels to improve
+ accessibility (minor markup change).
+- Added a 'javascript_always_use_jquery' variable which can be set to FALSE by
+ sites that may not need jQuery loaded on all pages, and a 'requires_jquery'
+ option to drupal_add_js() which modules can set to FALSE when adding
+ JavaScript files that have no dependency on jQuery (API addition:
+ https://www.drupal.org/node/2462717).
+- Fixed incorrect foreign keys in the User module's role_permission and
+ users_roles database tables.
+- Changed permission descriptions throughout Drupal core to consistently link
+ to relevant administrative pages, regardless of whether the user viewing the
+ Permissions page can view the page being linked to (minor UI change).
+- Fixed the drupal_add_region_content() function so that it actually adds
+ content to the page.
+- Added an 'image_suppress_itok_output' variable to allow sites already using
+ the existing 'image_allow_insecure_derivatives' variable to also prevent
+ security tokens from appearing in image derivative URLs.
+- Fixed double-escaping of theme names in the Block module administrative
+ interface (minor string change).
+- Added basic support for Xdebug when running automated tests.
+- Fixed a bug which caused previewing a node to remove elements from the node
+ being edited. With this fix, calling node_preview() will no longer modify the
+ passed-in node object (minor API change).
+- Added a user_has_role() function to check whether a user has a particular
+ role (API addition: https://www.drupal.org/node/2462411).
+- Fixed installation failures when an opcode cache is enabled.
+- Fixed a bug in the Drupal 6 to Drupal 7 upgrade path which caused private
+ files to be inaccessible.
+- Fixed a bug in the Drupal 6 to Drupal 7 upgrade path which caused user
+ pictures to be lost.
+- Fixed missing language code in hook_field_attach_view_alter() when it is
+ invoked from field_view_field().
+- Stopped sending ETag and Last-Modified headers for uncached page requests,
+ since they break caching for certain Varnish and Nginx configurations.
+- Changed the Simpletest module to allow PSR-4 test classes to be used in
+ Drupal 7.
+- Fixed a fatal error that occurred when using the Comment module's "Unpublish
+ comment containing keyword(s)" action.
+- Changed the "lang" attribute on language links to "xml:lang" so it validates
+ as XHTML (minor markup change).
+- Prevented the form API from allowing arrays to be submitted for various form
+ elements, such as textfields, textareas, and password fields (API change:
+ https://www.drupal.org/node/2462723).
+- Fixed a bug in the Contact module which caused the global user object to have
+ the incorrect name and e-mail address during the remainder of the page
+ request after the contact form is submitted.
+- Numerous small bug fixes.
+- Numerous API documentation improvements.
+- Additional automated test coverage.
+
+Drupal 7.35, 2015-03-18
+----------------------
+- Fixed security issues (multiple vulnerabilities). See SA-CORE-2015-001.
+
+Drupal 7.34, 2014-11-19
+----------------------
+- Fixed security issues (multiple vulnerabilities). See SA-CORE-2014-006.
+
+Drupal 7.33, 2014-11-07
+-----------------------
+- Began storing the file modification time of each module and theme in the
+ {system} database table so that contributed modules can use it to identify
+ recently changed modules and themes (minor data structure change to the
+ return value of system_get_info() and other related functions).
+- Added a "Did you mean?" feature to the run-tests.sh script for running
+ automated tests from the command line, to help developers who are attempting
+ to run a particular test class or group.
+- Changed the date format used in various HTTP headers output by Drupal core
+ from RFC 1123 format to RFC 7231 format.
+- Added a "block_cache_bypass_node_grants" variable to allow sites which have
+ node access modules enabled to use the block cache if desired (API addition).
+- Made image derivative generation HTTP requests return a 404 error (rather
+ than a 500 error) when the source image does not exist.
+- Fixed a bug which caused user pictures to be removed from the user object
+ after saving, and resulted in data loss if the user account was subsequently
+ re-saved.
+- Fixed a bug in which field_has_data() did not return TRUE for fields that
+ only had data in older entity revisions, leading to loss of the field's data
+ when the field configuration was edited.
+- Fixed a bug which caused the Ajax progress throbber to appear misaligned in
+ many situatons (minor styling change).
+- Prevented the Bartik theme from lower-casing the "Permalink" link on
+ comments, for improved multilingual support (minor UI change).
+- Added a "preferred_menu_links" tag to the database query that is used by
+ menu_link_get_preferred() to find the preferred menu link for a given path,
+ to make it easier to alter.
+- Increased the maximum allowed length of block titles to 255 characters
+ (database schema change to the {block} table).
+- Removed the Field module's field_modules_uninstalled() function, since it did
+ not do anything when it was invoked.
+- Added a "theme_hook_original" variable to templates and theme functions and
+ an optional sitewide theme debug mode, to provide contextual information in
+ the page's HTML to theme developers. The theme debug mode is based on the one
+ used with Twig in Drupal 8 and can be accessed by setting the "theme_debug"
+ variable to TRUE (API addition).
+- Added an entity_view_mode_prepare() API function to allow entity-defining
+ modules to properly invoke hook_entity_view_mode_alter(), and used it
+ throughout Drupal core to fix bugs with the invocation of that hook (API
+ change: https://www.drupal.org/node/2369141).
+- Security improvement: Made the database API's orderBy() method sanitize the
+ sort direction ("ASC" or "DESC") for queries built with db_select(), so that
+ calling code does not have to.
+- Changed the RDF module to consistently output RDF metadata for nodes and
+ comments near where the node is rendered in the HTML (minor markup and data
+ structure change).
+- Added an HTML class to RDFa metatags throughout Drupal to prevent them from
+ accidentally affecting the site appearance (minor markup change).
+- Fixed a bug in the Unicode requirements check which prevented installing
+ Drupal on PHP 5.6.
+- Fixed a bug which caused drupal_get_bootstrap_phase() to abort the bootstrap
+ when called early in the page request.
+- Renamed the "Search result" view mode to "Search result highlighting input"
+ to better reflect how it is used (UI change).
+- Improved database queries generated by EntityFieldQuery in the case where
+ delta or language condition groups are used, to reduce the number of INNER
+ JOINs (this is a minor data structure change affecting code which implements
+ hook_query_alter() on these queries).
+- Removed special-case behavior for file uploads which allowed user #1 to
+ bypass maximum file size and user quota limits.
+- Numerous small bug fixes.
+- Numerous API documentation improvements.
+- Additional automated test coverage.
+
Drupal 7.32, 2014-10-15
----------------------
- Fixed security issues (SQL injection). See SA-CORE-2014-005.
diff --git a/MAINTAINERS.txt b/MAINTAINERS.txt
index 3656314..f5cf6f8 100644
--- a/MAINTAINERS.txt
+++ b/MAINTAINERS.txt
@@ -27,7 +27,6 @@ Ajax system
- Earl Miles 'merlinofchaos' http://drupal.org/user/26979
Base system
-- Károly Négyesi 'chx' http://drupal.org/user/9446
- Damien Tournoud 'DamZ' http://drupal.org/user/22211
- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23
@@ -39,7 +38,6 @@ Cache system
- Nathaniel Catchpole 'catch' http://drupal.org/user/35733
Cron system
-- Károly Négyesi 'chx' http://drupal.org/user/9446
- Derek Wright 'dww' http://drupal.org/user/46549
Database system
@@ -55,10 +53,8 @@ Database system
- Sqlite driver
- Damien Tournoud 'DamZ' http://drupal.org/user/22211
- - Károly Négyesi 'chx' http://drupal.org/user/9446
Database update system
-- Károly Négyesi 'chx' http://drupal.org/user/9446
- Ashok Modi 'BTMash' http://drupal.org/user/60422
Entity system
@@ -71,7 +67,6 @@ File system
- Aaron Winborn 'aaron' http://drupal.org/user/33420
Form system
-- Károly Négyesi 'chx' http://drupal.org/user/9446
- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040
- Wolfgang Ziegler 'fago' http://drupal.org/user/16747
- Daniel F. Kudwien 'sun' http://drupal.org/user/54136
@@ -105,7 +100,6 @@ Markup
Menu system
- Peter Wolanin 'pwolanin' http://drupal.org/user/49851
-- Károly Négyesi 'chx' http://drupal.org/user/9446
Path system
- Dave Reid 'davereid' http://drupal.org/user/53892
@@ -261,7 +255,6 @@ Shortcut module
Simpletest module
- Jimmy Berry 'boombatower' http://drupal.org/user/214218
-- Károly Négyesi 'chx' http://drupal.org/user/9446
Statistics module
- Tim Millwood 'timmillwood' http://drupal.org/user/227849
diff --git a/includes/ajax.inc b/includes/ajax.inc
index 8446bf8..6e8e277 100644
--- a/includes/ajax.inc
+++ b/includes/ajax.inc
@@ -211,7 +211,7 @@
*
* When returning an Ajax command array, it is often useful to have
* status messages rendered along with other tasks in the command array.
- * In that case the the Ajax commands array may be constructed like this:
+ * In that case the Ajax commands array may be constructed like this:
* @code
* $commands = array();
* $commands[] = ajax_command_replace(NULL, $output);
@@ -276,7 +276,7 @@ function ajax_render($commands = array()) {
$extra_commands = array();
if (!empty($styles)) {
- $extra_commands[] = ajax_command_prepend('head', $styles);
+ $extra_commands[] = ajax_command_add_css($styles);
}
if (!empty($scripts_header)) {
$extra_commands[] = ajax_command_prepend('head', $scripts_header);
@@ -292,7 +292,7 @@ function ajax_render($commands = array()) {
$scripts = drupal_add_js();
if (!empty($scripts['settings'])) {
$settings = $scripts['settings'];
- array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $settings['data']), TRUE));
+ array_unshift($commands, ajax_command_settings(drupal_array_merge_deep_array($settings['data']), TRUE));
}
// Allow modules to alter any Ajax response.
@@ -1257,3 +1257,26 @@ function ajax_command_update_build_id($form) {
'new' => $form['#build_id'],
);
}
+
+/**
+ * Creates a Drupal Ajax 'add_css' command.
+ *
+ * This method will add css via ajax in a cross-browser compatible way.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.add_css()
+ * defined in misc/ajax.js.
+ *
+ * @param $styles
+ * A string that contains the styles to be added.
+ *
+ * @return
+ * An array suitable for use with the ajax_render() function.
+ *
+ * @see misc/ajax.js
+ */
+function ajax_command_add_css($styles) {
+ return array(
+ 'command' => 'add_css',
+ 'data' => $styles,
+ );
+}
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index c8d17f5..b572cde 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -8,7 +8,7 @@
/**
* The current system version.
*/
-define('VERSION', '7.32');
+define('VERSION', '7.37');
/**
* Core API compatibility.
@@ -248,6 +248,15 @@ define('REGISTRY_WRITE_LOOKUP_CACHE', 2);
*/
define('DRUPAL_PHP_FUNCTION_PATTERN', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*');
+/**
+ * A RFC7231 Compliant date.
+ *
+ * http://tools.ietf.org/html/rfc7231#section-7.1.1.1
+ *
+ * Example: Sun, 06 Nov 1994 08:49:37 GMT
+ */
+define('DATE_RFC7231', 'D, d M Y H:i:s \G\M\T');
+
/**
* Provides a caching wrapper to be used in place of large array structures.
*
@@ -520,9 +529,8 @@ function timer_stop($name) {
* Returns the appropriate configuration directory.
*
* Returns the configuration path based on the site's hostname, port, and
- * pathname. Uses find_conf_path() to find the current configuration directory.
- * See default.settings.php for examples on how the URL is converted to a
- * directory.
+ * pathname. See default.settings.php for examples on how the URL is converted
+ * to a directory.
*
* @param bool $require_settings
* Only configuration directories with an existing settings.php file
@@ -852,7 +860,7 @@ function drupal_get_filename($type, $name, $filename = NULL) {
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_exists(DRUPAL_ROOT . '/' . $file)) {
+ if ($file !== FALSE && file_exists(DRUPAL_ROOT . '/' . $file)) {
$files[$type][$name] = $file;
}
}
@@ -1237,23 +1245,10 @@ function drupal_send_headers($default_headers = array(), $only_default = FALSE)
* fresh page on every request. This prevents authenticated users from seeing
* locally cached pages.
*
- * Also give each page a unique ETag. This will force clients to include both
- * an If-Modified-Since header and an If-None-Match header when doing
- * conditional requests for the page (required by RFC 2616, section 13.3.4),
- * making the validation more robust. This is a workaround for a bug in Mozilla
- * Firefox that is triggered when Drupal's caching is enabled and the user
- * accesses Drupal via an HTTP proxy (see
- * https://bugzilla.mozilla.org/show_bug.cgi?id=269303): When an authenticated
- * user requests a page, and then logs out and requests the same page again,
- * Firefox may send a conditional request based on the page that was cached
- * locally when the user was logged in. If this page did not have an ETag
- * header, the request only contains an If-Modified-Since header. The date will
- * be recent, because with authenticated users the Last-Modified header always
- * refers to the time of the request. If the user accesses Drupal via a proxy
- * server, and the proxy already has a cached copy of the anonymous page with an
- * older Last-Modified date, the proxy may respond with 304 Not Modified, making
- * the client think that the anonymous and authenticated pageviews are
- * identical.
+ * ETag and Last-Modified headers are not set per default for authenticated
+ * users so that browsers do not send If-Modified-Since headers from
+ * authenticated user pages. drupal_serve_page_from_cache() will set appropriate
+ * ETag and Last-Modified headers for cached pages.
*
* @see drupal_page_set_cache()
*/
@@ -1266,9 +1261,7 @@ function drupal_page_header() {
$default_headers = array(
'Expires' => 'Sun, 19 Nov 1978 05:00:00 GMT',
- 'Last-Modified' => gmdate(DATE_RFC1123, REQUEST_TIME),
'Cache-Control' => 'no-cache, must-revalidate, post-check=0, pre-check=0',
- 'ETag' => '"' . REQUEST_TIME . '"',
);
drupal_send_headers($default_headers);
}
@@ -1336,7 +1329,7 @@ function drupal_serve_page_from_cache(stdClass $cache) {
drupal_add_http_header($name, $value);
}
- $default_headers['Last-Modified'] = gmdate(DATE_RFC1123, $cache->created);
+ $default_headers['Last-Modified'] = gmdate(DATE_RFC7231, $cache->created);
// HTTP/1.0 proxies does not support the Vary header, so prevent any caching
// by sending an Expires date in the past. HTTP/1.1 clients ignores the
@@ -1559,12 +1552,13 @@ function format_string($string, array $args = array()) {
* Also validates strings as UTF-8 to prevent cross site scripting attacks on
* Internet Explorer 6.
*
- * @param $text
+ * @param string $text
* The text to be checked or processed.
*
- * @return
- * An HTML safe version of $text, or an empty string if $text is not
- * valid UTF-8.
+ * @return string
+ * An HTML safe version of $text. If $text is not valid UTF-8, an empty string
+ * is returned and, on PHP < 5.4, a warning may be issued depending on server
+ * configuration (see @link https://bugs.php.net/bug.php?id=47494 @endlink).
*
* @see drupal_validate_utf8()
* @ingroup sanitization
@@ -1649,14 +1643,14 @@ function request_uri() {
* information about the passed-in exception is used.
* @param $variables
* Array of variables to replace in the message on display. Defaults to the
- * return value of drupal_decode_exception().
+ * return value of _drupal_decode_exception().
* @param $severity
* The severity of the message, as per RFC 3164.
* @param $link
* A link to associate with the message.
*
* @see watchdog()
- * @see drupal_decode_exception()
+ * @see _drupal_decode_exception()
*/
function watchdog_exception($type, Exception $exception, $message = NULL, $variables = array(), $severity = WATCHDOG_ERROR, $link = NULL) {
@@ -2176,7 +2170,7 @@ function drupal_anonymous_user() {
* drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
* @endcode
*
- * @param $phase
+ * @param int $phase
* A constant telling which phase to bootstrap to. When you bootstrap to a
* particular phase, all earlier phases are run automatically. Possible
* values:
@@ -2189,11 +2183,11 @@ function drupal_anonymous_user() {
* - DRUPAL_BOOTSTRAP_LANGUAGE: Finds out the language of the page.
* - DRUPAL_BOOTSTRAP_FULL: Fully loads Drupal. Validates and fixes input
* data.
- * @param $new_phase
+ * @param boolean $new_phase
* A boolean, set to FALSE if calling drupal_bootstrap from inside a
* function called from drupal_bootstrap (recursion).
*
- * @return
+ * @return int
* The most recently completed phase.
*/
function drupal_bootstrap($phase = NULL, $new_phase = TRUE) {
@@ -2215,12 +2209,13 @@ function drupal_bootstrap($phase = NULL, $new_phase = TRUE) {
// bootstrap state.
static $stored_phase = -1;
- // When not recursing, store the phase name so it's not forgotten while
- // recursing.
- if ($new_phase) {
- $final_phase = $phase;
- }
if (isset($phase)) {
+ // When not recursing, store the phase name so it's not forgotten while
+ // recursing but take care of not going backwards.
+ if ($new_phase && $phase >= $stored_phase) {
+ $final_phase = $phase;
+ }
+
// Call a phase if it has not been called before and is below the requested
// phase.
while ($phases && $phase > $stored_phase && $final_phase > $stored_phase) {
@@ -2486,6 +2481,26 @@ function _drupal_bootstrap_variables() {
// Load bootstrap modules.
require_once DRUPAL_ROOT . '/includes/module.inc';
module_load_all(TRUE);
+
+ // Sanitize the destination parameter (which is often used for redirects) to
+ // prevent open redirect attacks leading to other domains. Sanitize both
+ // $_GET['destination'] and $_REQUEST['destination'] to protect code that
+ // relies on either, but do not sanitize $_POST to avoid interfering with
+ // unrelated form submissions. The sanitization happens here because
+ // url_is_external() requires the variable system to be available.
+ if (isset($_GET['destination']) || isset($_REQUEST['destination'])) {
+ require_once DRUPAL_ROOT . '/includes/common.inc';
+ // If the destination is an external URL, remove it.
+ if (isset($_GET['destination']) && url_is_external($_GET['destination'])) {
+ unset($_GET['destination']);
+ unset($_REQUEST['destination']);
+ }
+ // If there's still something in $_REQUEST['destination'] that didn't come
+ // from $_GET, check it too.
+ if (isset($_REQUEST['destination']) && (!isset($_GET['destination']) || $_REQUEST['destination'] != $_GET['destination']) && url_is_external($_REQUEST['destination'])) {
+ unset($_REQUEST['destination']);
+ }
+ }
}
/**
@@ -2508,7 +2523,7 @@ function _drupal_bootstrap_page_header() {
* @see drupal_bootstrap()
*/
function drupal_get_bootstrap_phase() {
- return drupal_bootstrap();
+ return drupal_bootstrap(NULL, FALSE);
}
/**
@@ -2622,7 +2637,7 @@ function drupal_installation_attempted() {
*
* This would include implementations of hook_install(), which could run
* during the Drupal installation phase, and might also be run during
- * non-installation time, such as while installing the module from the the
+ * non-installation time, such as while installing the module from the
* module administration page.
*
* Example usage:
@@ -3151,10 +3166,13 @@ function _registry_check_code($type, $name = NULL) {
// This function may get called when the default database is not active, but
// there is no reason we'd ever want to not use the default database for
// this query.
- $file = Database::getConnection('default', 'default')->query("SELECT filename FROM {registry} WHERE name = :name AND type = :type", array(
- ':name' => $name,
- ':type' => $type,
- ))
+ $file = Database::getConnection('default', 'default')
+ ->select('registry', 'r', array('target' => 'default'))
+ ->fields('r', array('filename'))
+ // Use LIKE here to make the query case-insensitive.
+ ->condition('r.name', db_like($name), 'LIKE')
+ ->condition('r.type', $type)
+ ->execute()
->fetchField();
// Flag that we've run a lookup query and need to update the cache.
@@ -3328,11 +3346,9 @@ function registry_update() {
* @param $default_value
* Optional default value.
* @param $reset
- * TRUE to reset a specific named variable, or all variables if $name is NULL.
- * Resetting every variable should only be used, for example, for running
- * unit tests with a clean environment. Should be used only though via
- * function drupal_static_reset() and the return value should not be used in
- * this case.
+ * TRUE to reset one or all variables(s). This parameter is only used
+ * internally and should not be passed in; use drupal_static_reset() instead.
+ * (This function's return value should not be used when TRUE is passed in.)
*
* @return
* Returns a variable by reference.
@@ -3377,6 +3393,8 @@ function &drupal_static($name, $default_value = NULL, $reset = FALSE) {
*
* @param $name
* Name of the static variable to reset. Omit to reset all variables.
+ * Resetting all variables should only be used, for example, for running unit
+ * tests with a clean environment.
*/
function drupal_static_reset($name = NULL) {
drupal_static($name, NULL, TRUE);
@@ -3492,3 +3510,34 @@ function drupal_check_memory_limit($required, $memory_limit = NULL) {
// - The memory limit is greater than the memory required for the operation.
return ((!$memory_limit) || ($memory_limit == -1) || (parse_size($memory_limit) >= parse_size($required)));
}
+
+/**
+ * Invalidates a PHP file from any active opcode caches.
+ *
+ * If the opcode cache does not support the invalidation of individual files,
+ * the entire cache will be flushed.
+ *
+ * @param string $filepath
+ * The absolute path of the PHP file to invalidate.
+ */
+function drupal_clear_opcode_cache($filepath) {
+ if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300) {
+ // Below PHP 5.3, clearstatcache does not accept any function parameters.
+ clearstatcache();
+ }
+ else {
+ clearstatcache(TRUE, $filepath);
+ }
+
+ // Zend OPcache.
+ if (function_exists('opcache_invalidate')) {
+ opcache_invalidate($filepath, TRUE);
+ }
+ // APC.
+ if (function_exists('apc_delete_file')) {
+ // apc_delete_file() throws a PHP warning in case the specified file was
+ // not compiled yet.
+ // @see http://php.net/apc-delete-file
+ @apc_delete_file($filepath);
+ }
+}
diff --git a/includes/cache.inc b/includes/cache.inc
index 09f4d75..207bf66 100644
--- a/includes/cache.inc
+++ b/includes/cache.inc
@@ -98,9 +98,11 @@ function cache_get_multiple(array &$cids, $bin = 'cache') {
* @param $data
* The data to store in the cache. Complex data types will be automatically
* serialized before insertion. Strings will be stored as plain text and are
- * not serialized.
+ * not serialized. Some storage engines only allow objects up to a maximum of
+ * 1MB in size to be stored by default. When caching large arrays or similar,
+ * take care to ensure $data does not exceed this size.
* @param $bin
- * The cache bin to store the data in. Valid core values are:
+ * (optional) The cache bin to store the data in. Valid core values are:
* - cache: (default) Generic cache storage bin (used for theme registry,
* locale date, list of simpletest tests, etc.).
* - cache_block: Stores the content of various blocks.
@@ -119,7 +121,7 @@ function cache_get_multiple(array &$cids, $bin = 'cache') {
* the administrator panel.
* - cache_path: Stores the system paths that have an alias.
* @param $expire
- * One of the following values:
+ * (optional) One of the following values:
* - CACHE_PERMANENT: Indicates that the item should never be removed unless
* explicitly told to using cache_clear_all() with a cache ID.
* - CACHE_TEMPORARY: Indicates that the item should be removed at the next
@@ -254,10 +256,12 @@ interface DrupalCacheInterface {
* The cache ID of the data to store.
* @param $data
* The data to store in the cache. Complex data types will be automatically
- * serialized before insertion.
- * Strings will be stored as plain text and not serialized.
+ * serialized before insertion. Strings will be stored as plain text and not
+ * serialized. Some storage engines only allow objects up to a maximum of
+ * 1MB in size to be stored by default. When caching large arrays or
+ * similar, take care to ensure $data does not exceed this size.
* @param $expire
- * One of the following values:
+ * (optional) One of the following values:
* - CACHE_PERMANENT: Indicates that the item should never be removed unless
* explicitly told to using cache_clear_all() with a cache ID.
* - CACHE_TEMPORARY: Indicates that the item should be removed at the next
diff --git a/includes/common.inc b/includes/common.inc
index 477ecc0..cd30145 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -985,9 +985,10 @@ function drupal_http_request($url, array $options = array()) {
$response = preg_split("/\r\n|\n|\r/", $response);
// Parse the response status line.
- list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
- $result->protocol = $protocol;
- $result->status_message = $status_message;
+ $response_status_array = _drupal_parse_response_status(trim(array_shift($response)));
+ $result->protocol = $response_status_array['http_version'];
+ $result->status_message = $response_status_array['reason_phrase'];
+ $code = $response_status_array['response_code'];
$result->headers = array();
@@ -1078,12 +1079,43 @@ function drupal_http_request($url, array $options = array()) {
}
break;
default:
- $result->error = $status_message;
+ $result->error = $result->status_message;
}
return $result;
}
+/**
+ * Splits an HTTP response status line into components.
+ *
+ * See the @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html status line definition @endlink
+ * in RFC 2616.
+ *
+ * @param string $respone
+ * The response status line, for example 'HTTP/1.1 500 Internal Server Error'.
+ *
+ * @return array
+ * Keyed array containing the component parts. If the response is malformed,
+ * all possible parts will be extracted. 'reason_phrase' could be empty.
+ * Possible keys:
+ * - 'http_version'
+ * - 'response_code'
+ * - 'reason_phrase'
+ */
+function _drupal_parse_response_status($response) {
+ $response_array = explode(' ', trim($response), 3);
+ // Set up empty values.
+ $result = array(
+ 'reason_phrase' => '',
+ );
+ $result['http_version'] = $response_array[0];
+ $result['response_code'] = $response_array[1];
+ if (isset($response_array[2])) {
+ $result['reason_phrase'] = $response_array[2];
+ }
+ return $result;
+}
+
/**
* Helper function for determining hosts excluded from needing a proxy.
*
@@ -1490,7 +1522,7 @@ function _filter_xss_split($m, $store = FALSE) {
return '<';
}
- 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 '';
}
@@ -2182,14 +2214,20 @@ 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. Only
- // call the slow drupal_strip_dangerous_protocols() if $path contains a ':'
- // before any / ? or #. Note: we could use url_is_external($path) here, but
- // that would require another function call, and performance inside url() is
- // critical.
+ // 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'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path);
+ $options['external'] = (strpos($path, '//') === 0)
+ || ($colonpos !== FALSE
+ && !preg_match('![/?#]!', substr($path, 0, $colonpos))
+ && drupal_strip_dangerous_protocols($path) == $path);
}
// Preserve the original path before altering or aliasing.
@@ -2227,6 +2265,11 @@ function url($path = NULL, array $options = array()) {
return $path . $options['fragment'];
}
+ // Strip leading slashes from internal paths to prevent them becoming external
+ // URLs without protocol. /example.com should not be turned into
+ // //example.com.
+ $path = ltrim($path, '/');
+
global $base_url, $base_secure_url, $base_insecure_url;
// The base_url might be rewritten from the language rewrite in domain mode.
@@ -2304,10 +2347,15 @@ 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.
- return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $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.
+ return (strpos($path, '//') === 0)
+ || ($colonpos !== FALSE
+ && !preg_match('![/?#]!', substr($path, 0, $colonpos))
+ && drupal_strip_dangerous_protocols($path) == $path);
}
/**
@@ -2604,7 +2652,10 @@ function drupal_deliver_html_page($page_callback_result) {
// Keep old path for reference, and to allow forms to redirect to it.
if (!isset($_GET['destination'])) {
- $_GET['destination'] = $_GET['q'];
+ // Make sure that the current path is not interpreted as external URL.
+ if (!url_is_external($_GET['q'])) {
+ $_GET['destination'] = $_GET['q'];
+ }
}
$path = drupal_get_normal_path(variable_get('site_404', ''));
@@ -2633,7 +2684,10 @@ function drupal_deliver_html_page($page_callback_result) {
// Keep old path for reference, and to allow forms to redirect to it.
if (!isset($_GET['destination'])) {
- $_GET['destination'] = $_GET['q'];
+ // Make sure that the current path is not interpreted as external URL.
+ if (!url_is_external($_GET['q'])) {
+ $_GET['destination'] = $_GET['q'];
+ }
}
$path = drupal_get_normal_path(variable_get('site_403', ''));
@@ -3442,7 +3496,11 @@ function drupal_pre_render_styles($elements) {
$import_batch = array_slice($import, 0, 31);
$import = array_slice($import, 31);
$element = $style_element_defaults;
- $element['#value'] = implode("\n", $import_batch);
+ // This simplifies the JavaScript regex, allowing each line
+ // (separated by \n) to be treated as a completely different string.
+ // This means that we can use ^ and $ on one line at a time, and not
+ // worry about style tags since they'll never match the regex.
+ $element['#value'] = "\n" . implode("\n", $import_batch) . "\n";
$element['#attributes']['media'] = $group['media'];
$element['#browsers'] = $group['browsers'];
$elements[] = $element;
@@ -3744,7 +3802,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;
}
@@ -3768,7 +3826,7 @@ function _drupal_load_stylesheet($matches) {
// Alter all internal url() paths. Leave external paths alone. We don't need
// to normalize absolute paths here (i.e. remove folder/... segments) because
// that will be done later.
- return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)/i', 'url(\1'. $directory, $file);
+ return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)([^\'")]+)([\'"]?)\s*\)/i', 'url(\1' . $directory . '\2\3)', $file);
}
/**
@@ -4104,6 +4162,13 @@ function drupal_region_class($region) {
* else being the same, JavaScript added by a call to drupal_add_js() that
* happened later in the page request gets added to the page after one for
* which drupal_add_js() happened earlier in the page request.
+ * - requires_jquery: Set this to FALSE if the JavaScript you are adding does
+ * not have a dependency on jQuery. Defaults to TRUE, except for JavaScript
+ * settings where it defaults to FALSE. This is used on sites that have the
+ * 'javascript_always_use_jquery' variable set to FALSE; on those sites, if
+ * all the JavaScript added to the page by drupal_add_js() does not have a
+ * dependency on jQuery, then for improved front-end performance Drupal
+ * will not add jQuery and related libraries and settings to the page.
* - defer: If set to TRUE, the defer attribute is set on the ';
+ filter_format_save($format);
+ user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array(filter_permission_name($format) => 1));
+ $this->drupalGet('filter/tips');
+ $this->assertNoRaw($format->name, 'Text format name contains no xss.');
}
/**
@@ -1139,7 +1148,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase {
// Setup dummy filter object.
$filter = new stdClass();
$filter->settings = array(
- 'allowed_html' => ' -
-
- ',
+ 'allowed_html' => '
-
-
- ',
'filter_html_help' => 1,
'filter_html_nofollow' => 0,
);
@@ -1175,6 +1184,10 @@ class FilterUnitTestCase extends DrupalUnitTestCase {
$f = _filter_html('
', $filter);
$this->assertNoNormalized($f, 'onerror', 'HTML filter should remove empty on* attributes on default.');
+
+ // Custom tags are supported and should be allowed through.
+ $f = _filter_html('', $filter);
+ $this->assertNormalized($f, 'test-element', 'HTML filter should allow custom elements.');
}
/**
diff --git a/modules/forum/forum.info b/modules/forum/forum.info
index d23cc73..f91bc14 100644
--- a/modules/forum/forum.info
+++ b/modules/forum/forum.info
@@ -9,8 +9,8 @@ files[] = forum.test
configure = admin/structure/forum
stylesheets[all][] = forum.css
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/forum/forum.module b/modules/forum/forum.module
index 575de36..1224418 100644
--- a/modules/forum/forum.module
+++ b/modules/forum/forum.module
@@ -263,10 +263,10 @@ function _forum_node_check_node_type($node) {
* Implements hook_node_view().
*/
function forum_node_view($node, $view_mode) {
- $vid = variable_get('forum_nav_vocabulary', 0);
- $vocabulary = taxonomy_vocabulary_load($vid);
if (_forum_node_check_node_type($node)) {
if ($view_mode == 'full' && node_is_page($node)) {
+ $vid = variable_get('forum_nav_vocabulary', 0);
+ $vocabulary = taxonomy_vocabulary_load($vid);
// Breadcrumb navigation
$breadcrumb[] = l(t('Home'), NULL);
$breadcrumb[] = l($vocabulary->name, 'forum');
diff --git a/modules/help/help.info b/modules/help/help.info
index ba19af7..140cf87 100644
--- a/modules/help/help.info
+++ b/modules/help/help.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
files[] = help.test
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/image/image.info b/modules/image/image.info
index 0116877..91b1515 100644
--- a/modules/image/image.info
+++ b/modules/image/image.info
@@ -7,8 +7,8 @@ dependencies[] = file
files[] = image.test
configure = admin/config/media/image-styles
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/image/image.module b/modules/image/image.module
index c6a23f2..fac8de9 100644
--- a/modules/image/image.module
+++ b/modules/image/image.module
@@ -845,6 +845,12 @@ function image_style_deliver($style, $scheme) {
}
}
+ // Confirm that the original source image exists before trying to process it.
+ if (!is_file($image_uri)) {
+ watchdog('image', 'Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.', array('%source_image_path' => $image_uri, '%derivative_path' => $derivative_uri));
+ return MENU_NOT_FOUND;
+ }
+
// Don't start generating the image if the derivative already exists or if
// generation is in progress in another thread.
$lock_name = 'image_style_deliver:' . $style['name'] . ':' . drupal_hash_base64($image_uri);
@@ -854,6 +860,7 @@ function image_style_deliver($style, $scheme) {
// Tell client to retry again in 3 seconds. Currently no browsers are known
// to support Retry-After.
drupal_add_http_header('Status', '503 Service Unavailable');
+ drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
drupal_add_http_header('Retry-After', 3);
print t('Image generation in progress. Try again shortly.');
drupal_exit();
@@ -875,6 +882,7 @@ function image_style_deliver($style, $scheme) {
else {
watchdog('image', 'Unable to generate the derived image located at %path.', array('%path' => $derivative_uri));
drupal_add_http_header('Status', '500 Internal Server Error');
+ drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
print t('Error generating image.');
drupal_exit();
}
@@ -1019,7 +1027,15 @@ function image_style_url($style_name, $path) {
// The token query is added even if the 'image_allow_insecure_derivatives'
// variable is TRUE, so that the emitted links remain valid if it is changed
// back to the default FALSE.
- $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($style_name, $original_uri));
+ // However, sites which need to prevent the token query from being emitted at
+ // all can additionally set the 'image_suppress_itok_output' variable to TRUE
+ // to achieve that (if both are set, the security token will neither be
+ // emitted in the image derivative URL nor checked for in
+ // image_style_deliver()).
+ $token_query = array();
+ if (!variable_get('image_suppress_itok_output', FALSE)) {
+ $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($style_name, $original_uri));
+ }
// If not using clean URLs, the image derivative callback is only available
// with the query string. If the file does not exist, use url() to ensure
@@ -1031,8 +1047,12 @@ function image_style_url($style_name, $path) {
}
$file_url = file_create_url($uri);
- // Append the query string with the token.
- return $file_url . (strpos($file_url, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($token_query);
+ // Append the query string with the token, if necessary.
+ if ($token_query) {
+ $file_url .= (strpos($file_url, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($token_query);
+ }
+
+ return $file_url;
}
/**
diff --git a/modules/image/image.test b/modules/image/image.test
index 4a4aab0..3591979 100644
--- a/modules/image/image.test
+++ b/modules/image/image.test
@@ -173,6 +173,16 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase {
$this->_testImageStyleUrlAndPath('public', TRUE, TRUE);
}
+ /**
+ * Test that an invalid source image returns a 404.
+ */
+ function testImageStyleUrlForMissingSourceImage() {
+ $non_existent_uri = 'public://foo.png';
+ $generated_url = image_style_url($this->style_name, $non_existent_uri);
+ $this->drupalGet($generated_url);
+ $this->assertResponse(404, 'Accessing an image style URL with a source image that does not exist provides a 404 error response.');
+ }
+
/**
* Test image_style_url().
*/
@@ -320,6 +330,15 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase {
$this->drupalGet($nested_url);
$this->assertResponse(200, 'Image was accessible when a correct token was provided in the URL.');
+ // Suppress the security token in the URL, then get the URL of a file. Check
+ // that the security token is not present in the URL but that the image is
+ // still accessible.
+ variable_set('image_suppress_itok_output', TRUE);
+ $generate_url = image_style_url($this->style_name, $original_uri);
+ $this->assertIdentical(strpos($generate_url, IMAGE_DERIVATIVE_TOKEN . '='), FALSE, 'The security token does not appear in the image style URL.');
+ $this->drupalGet($generate_url);
+ $this->assertResponse(200, 'Image was accessible at the URL with a missing token.');
+
// Check that requesting a nonexistent image does not create any new
// directories in the file system.
$directory = $scheme . '://styles/' . $this->style_name . '/' . $scheme . '/' . $this->randomName();
diff --git a/modules/image/tests/image_module_test.info b/modules/image/tests/image_module_test.info
index 6d419e1..b2c6ce6 100644
--- a/modules/image/tests/image_module_test.info
+++ b/modules/image/tests/image_module_test.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = image_module_test.module
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/locale/locale.admin.inc b/modules/locale/locale.admin.inc
index b736f79..e813962 100644
--- a/modules/locale/locale.admin.inc
+++ b/modules/locale/locale.admin.inc
@@ -1139,11 +1139,11 @@ function locale_translate_edit_form($form, &$form_state, $lid) {
'#value' => $source->location
);
- // Include default form controls with empty values for all languages.
- // This ensures that the languages are always in the same order in forms.
+ // Include both translated and not yet translated target languages in the
+ // list. The source language is English for built-in strings and the default
+ // language for other strings.
$languages = language_list();
$default = language_default();
- // We don't need the default language value, that value is in $source.
$omit = $source->textgroup == 'default' ? 'en' : $default->language;
unset($languages[($omit)]);
$form['translations'] = array('#tree' => TRUE);
diff --git a/modules/locale/locale.info b/modules/locale/locale.info
index 3ca7bed..ec93d5c 100644
--- a/modules/locale/locale.info
+++ b/modules/locale/locale.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = locale.test
configure = admin/config/regional/language
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/locale/locale.test b/modules/locale/locale.test
index 9ffec9f..9086587 100644
--- a/modules/locale/locale.test
+++ b/modules/locale/locale.test
@@ -1202,7 +1202,7 @@ EOF;
* Helper function that returns a .po file with context.
*/
function getPoFileWithContext() {
- // Croatian (code hr) is one the the languages that have a different
+ // Croatian (code hr) is one of the languages that have a different
// form for the full name and the abbreviated name for the month May.
return <<< EOF
msgid ""
diff --git a/modules/locale/tests/locale_test.info b/modules/locale/tests/locale_test.info
index c86e996..f8e1b96 100644
--- a/modules/locale/tests/locale_test.info
+++ b/modules/locale/tests/locale_test.info
@@ -5,8 +5,8 @@ package = Testing
version = VERSION
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/menu/menu.info b/modules/menu/menu.info
index 8fd0c12..fbae5dd 100644
--- a/modules/menu/menu.info
+++ b/modules/menu/menu.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = menu.test
configure = admin/structure/menu
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/menu/menu.module b/modules/menu/menu.module
index 6444791..dc8f015 100644
--- a/modules/menu/menu.module
+++ b/modules/menu/menu.module
@@ -69,7 +69,7 @@ function menu_menu() {
'title' => 'Parent menu items',
'page callback' => 'menu_parent_options_js',
'type' => MENU_CALLBACK,
- 'access arguments' => array(TRUE),
+ 'access arguments' => array('administer menu'),
);
$items['admin/structure/menu/list'] = array(
'title' => 'List menus',
diff --git a/modules/menu/menu.test b/modules/menu/menu.test
index 95e0ee9..a9bdb5f 100644
--- a/modules/menu/menu.test
+++ b/modules/menu/menu.test
@@ -513,6 +513,23 @@ class MenuTestCase extends DrupalWebTestCase {
}
}
+ /**
+ * Test administrative users other than user 1 can access the menu parents AJAX callback.
+ */
+ public function testMenuParentsJsAccess() {
+ $admin = $this->drupalCreateUser(array('administer menu'));
+ $this->drupalLogin($admin);
+ // Just check access to the callback overall, the POST data is irrelevant.
+ $this->drupalGetAJAX('admin/structure/menu/parents');
+ $this->assertResponse(200);
+
+ // Do standard user tests.
+ // Login the user.
+ $this->drupalLogin($this->std_user);
+ $this->drupalGetAJAX('admin/structure/menu/parents');
+ $this->assertResponse(403);
+ }
+
/**
* Get standard menu link.
*/
diff --git a/modules/node/node.api.php b/modules/node/node.api.php
index 9502676..9a4d095 100644
--- a/modules/node/node.api.php
+++ b/modules/node/node.api.php
@@ -17,11 +17,14 @@
* During node operations (create, update, view, delete, etc.), there are
* several sets of hooks that get invoked to allow modules to modify the base
* node operation:
- * - Node-type-specific hooks: These hooks are only invoked on the primary
- * module, using the "base" return component of hook_node_info() as the
- * function prefix. For example, poll.module defines the base for the Poll
- * content type as "poll", so during creation of a poll node, hook_insert() is
- * only invoked by calling poll_insert().
+ * - Node-type-specific hooks: When defining a node type, hook_node_info()
+ * returns a 'base' component. Node-type-specific hooks are named
+ * base_hookname() instead of mymodule_hookname() (in a module called
+ * 'mymodule' for example). Only the node type's corresponding implementation
+ * is invoked. For example, poll_node_info() in poll.module defines the base
+ * for the 'poll' node type as 'poll'. So when a poll node is created,
+ * hook_insert() is invoked on poll_insert() only.
+ * Hooks that are node-type-specific are noted below.
* - All-module hooks: This set of hooks is invoked on all implementing modules,
* to allow other modules to modify what the primary node module is doing. For
* example, hook_node_insert() is invoked on all modules when creating a poll
@@ -195,7 +198,7 @@ function hook_node_grants($account, $op) {
if (user_access('access private content', $account)) {
$grants['example'] = array(1);
}
- $grants['example_owner'] = array($account->uid);
+ $grants['example_author'] = array($account->uid);
return $grants;
}
@@ -885,11 +888,10 @@ function hook_node_view_alter(&$build) {
* name as the key. Each sub-array has up to 10 attributes. Possible
* attributes:
* - name: (required) The human-readable name of the node type.
- * - base: (required) The base string used to construct callbacks
- * corresponding to this node type (for example, if base is defined as
- * example_foo, then example_foo_insert will be called when inserting a node
- * of that type). This string is usually the name of the module, but not
- * always.
+ * - base: (required) The base name for implementations of node-type-specific
+ * hooks that respond to this node type. Base is usually the name of the
+ * module or 'node_content', but not always. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
* - description: (required) A brief description of the node type.
* - help: (optional) Help information shown to the user when creating a node
* of this type.
@@ -1030,8 +1032,11 @@ function hook_node_type_delete($info) {
/**
* Respond to node deletion.
*
- * This hook is invoked only on the module that defines the node's content type
- * (use hook_node_delete() to respond to all node deletions).
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_node_delete() to respond to node deletion of all node types.
*
* This hook is invoked from node_delete_multiple() before hook_node_delete()
* is invoked and before field_attach_delete() is called.
@@ -1059,8 +1064,11 @@ function hook_delete($node) {
/**
* Act on a node object about to be shown on the add/edit form.
*
- * This hook is invoked only on the module that defines the node's content type
- * (use hook_node_prepare() to act on all node preparations).
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_node_prepare() to respond to node preparation of all node types.
*
* This hook is invoked from node_object_prepare() before the general
* hook_node_prepare() is invoked.
@@ -1089,6 +1097,13 @@ function hook_prepare($node) {
/**
* Display a node editing form.
*
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_form_BASE_FORM_ID_alter(), with base form ID 'node_form', to alter
+ * node forms for all node types.
+ *
* This hook, implemented by node modules, is called to retrieve the form
* that is displayed to create or edit a node. This form is displayed at path
* node/add/[node type] or node/[node ID]/edit.
@@ -1144,8 +1159,11 @@ function hook_form($node, &$form_state) {
/**
* Respond to creation of a new node.
*
- * This hook is invoked only on the module that defines the node's content type
- * (use hook_node_insert() to act on all node insertions).
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_node_insert() to respond to node insertion of all node types.
*
* This hook is invoked from node_save() after the node is inserted into the
* node table in the database, before field_attach_insert() is called, and
@@ -1168,8 +1186,11 @@ function hook_insert($node) {
/**
* Act on nodes being loaded from the database.
*
- * This hook is invoked only on the module that defines the node's content type
- * (use hook_node_load() to respond to all node loads).
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_node_load() to respond to node load of all node types.
*
* This hook is invoked during node loading, which is handled by entity_load(),
* via classes NodeController and DrupalDefaultEntityController. After the node
@@ -1202,8 +1223,11 @@ function hook_load($nodes) {
/**
* Respond to updates to a node.
*
- * This hook is invoked only on the module that defines the node's content type
- * (use hook_node_update() to act on all node updates).
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_node_update() to respond to node update of all node types.
*
* This hook is invoked from node_save() after the node is updated in the
* node table in the database, before field_attach_update() is called, and
@@ -1224,8 +1248,11 @@ function hook_update($node) {
/**
* Perform node validation before a node is created or updated.
*
- * This hook is invoked only on the module that defines the node's content type
- * (use hook_node_validate() to act on all node validations).
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_node_validate() to respond to node validation of all node types.
*
* This hook is invoked from node_validate(), after a user has finished
* editing the node and is previewing or submitting it. It is invoked at the end
@@ -1258,8 +1285,11 @@ function hook_validate($node, $form, &$form_state) {
/**
* Display a node.
*
- * This hook is invoked only on the module that defines the node's content type
- * (use hook_node_view() to act on all node views).
+ * This is a node-type-specific hook, which is invoked only for the node type
+ * being affected. See
+ * @link node_api_hooks Node API hooks @endlink for more information.
+ *
+ * Use hook_node_view() to respond to node view of all node types.
*
* This hook is invoked during node viewing after the node is fully loaded, so
* that the node type module can define a custom method for display, or add to
@@ -1269,6 +1299,10 @@ function hook_validate($node, $form, &$form_state) {
* The node to be displayed, as returned by node_load().
* @param $view_mode
* View mode, e.g. 'full', 'teaser', ...
+ * @param $langcode
+ * (optional) A language code to use for rendering. Defaults to the global
+ * content language of the current request.
+ *
* @return
* The passed $node parameter should be modified as necessary and returned so
* it can be properly presented. Nodes are prepared for display by assembling
@@ -1282,7 +1316,7 @@ function hook_validate($node, $form, &$form_state) {
*
* @ingroup node_api_hooks
*/
-function hook_view($node, $view_mode) {
+function hook_view($node, $view_mode, $langcode = NULL) {
if ($view_mode == 'full' && node_is_page($node)) {
$breadcrumb = array();
$breadcrumb[] = l(t('Home'), NULL);
diff --git a/modules/node/node.info b/modules/node/node.info
index 831a47f..a7b1145 100644
--- a/modules/node/node.info
+++ b/modules/node/node.info
@@ -9,8 +9,8 @@ required = TRUE
configure = admin/structure/types
stylesheets[all][] = node.css
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/node/node.install b/modules/node/node.install
index 76c2aec..0b0a7bd 100644
--- a/modules/node/node.install
+++ b/modules/node/node.install
@@ -933,6 +933,16 @@ function node_update_7014() {
db_add_index('node', 'language', array('language'));
}
+/**
+ * Enable node types that may have been erroneously disabled in Drupal 7.36.
+ */
+function node_update_7015() {
+ db_update('node_type')
+ ->fields(array('disabled' => 0))
+ ->condition('base', 'node_content')
+ ->execute();
+}
+
/**
* @} End of "addtogroup updates-7.x-extra".
*/
diff --git a/modules/node/node.module b/modules/node/node.module
index 5a4e019..7a6246d 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -210,7 +210,7 @@ function node_entity_info() {
'custom settings' => FALSE,
),
'search_result' => array(
- 'label' => t('Search result'),
+ 'label' => t('Search result highlighting input'),
'custom settings' => FALSE,
),
);
@@ -506,7 +506,8 @@ function node_type_load($name) {
* - custom: TRUE or FALSE indicating whether this type is defined by a module
* (FALSE) or by a user (TRUE) via Add Content Type.
* - modified: TRUE or FALSE indicating whether this type has been modified by
- * an administrator. Currently not used in any way.
+ * an administrator. When modifying an existing node type, set to TRUE, or
+ * the change will be ignored on node_types_rebuild().
* - locked: TRUE or FALSE indicating whether the administrator can change the
* machine name of this type.
* - disabled: TRUE or FALSE indicating whether this type has been disabled.
@@ -1397,12 +1398,7 @@ function node_build_content($node, $view_mode = 'full', $langcode = NULL) {
$node->content = array();
// Allow modules to change the view mode.
- $context = array(
- 'entity_type' => 'node',
- 'entity' => $node,
- 'langcode' => $langcode,
- );
- drupal_alter('entity_view_mode', $view_mode, $context);
+ $view_mode = key(entity_view_mode_prepare('node', array($node->nid => $node), $view_mode, $langcode));
// The 'view' hook can be implemented to overwrite the default function
// to display nodes.
@@ -1585,9 +1581,7 @@ function node_permission() {
),
'access content overview' => array(
'title' => t('Access the content overview page'),
- 'description' => user_access('access content overview')
- ? t('Get an overview of all content.', array('@url' => url('admin/content')))
- : t('Get an overview of all content.'),
+ 'description' => t('Get an overview of all content.', array('@url' => url('admin/content'))),
),
'access content' => array(
'title' => t('View published content'),
@@ -1615,7 +1609,7 @@ function node_permission() {
}
/**
- * Gathers the rankings from the the hook_ranking() implementations.
+ * Gathers the rankings from the hook_ranking() implementations.
*
* @param $query
* A query object that has been extended with the Search DB Extender.
@@ -2604,9 +2598,10 @@ function node_feed($nids = FALSE, $channel = array()) {
$node->link = url("node/$node->nid", array('absolute' => TRUE));
$node->rss_namespaces = array();
+ $account = user_load($node->uid);
$node->rss_elements = array(
array('key' => 'pubDate', 'value' => gmdate('r', $node->created)),
- array('key' => 'dc:creator', 'value' => $node->name),
+ array('key' => 'dc:creator', 'value' => format_username($account)),
array('key' => 'guid', 'value' => $node->nid . ' at ' . $base_url, 'attributes' => array('isPermaLink' => 'false'))
);
@@ -2664,15 +2659,26 @@ function node_feed($nids = FALSE, $channel = array()) {
* An array in the format expected by drupal_render().
*/
function node_view_multiple($nodes, $view_mode = 'teaser', $weight = 0, $langcode = NULL) {
- field_attach_prepare_view('node', $nodes, $view_mode, $langcode);
- entity_prepare_view('node', $nodes, $langcode);
$build = array();
+ $entities_by_view_mode = entity_view_mode_prepare('node', $nodes, $view_mode, $langcode);
+ foreach ($entities_by_view_mode as $entity_view_mode => $entities) {
+ field_attach_prepare_view('node', $entities, $entity_view_mode, $langcode);
+ entity_prepare_view('node', $entities, $langcode);
+
+ foreach ($entities as $entity) {
+ $build['nodes'][$entity->nid] = node_view($entity, $entity_view_mode, $langcode);
+ }
+ }
+
foreach ($nodes as $node) {
- $build['nodes'][$node->nid] = node_view($node, $view_mode, $langcode);
$build['nodes'][$node->nid]['#weight'] = $weight;
$weight++;
}
+ // Sort here, to preserve the input order of the entities that were passed to
+ // this function.
+ uasort($build['nodes'], 'element_sort');
$build['nodes']['#sorted'] = TRUE;
+
return $build;
}
@@ -3629,7 +3635,8 @@ function node_access_rebuild($batch_mode = FALSE) {
// Try to allocate enough time to rebuild node grants
drupal_set_time_limit(240);
- $nids = db_query("SELECT nid FROM {node}")->fetchCol();
+ // Rebuild newest nodes first so that recent content becomes available quickly.
+ $nids = db_query("SELECT nid FROM {node} ORDER BY nid DESC")->fetchCol();
foreach ($nids as $nid) {
$node = node_load($nid, NULL, TRUE);
// To preserve database integrity, only acquire grants if the node
diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc
index 6267463..cc3908e 100644
--- a/modules/node/node.pages.inc
+++ b/modules/node/node.pages.inc
@@ -371,35 +371,38 @@ function node_form_build_preview($form, &$form_state) {
* @see node_form_build_preview()
*/
function node_preview($node) {
- if (node_access('create', $node) || node_access('update', $node)) {
- _field_invoke_multiple('load', 'node', array($node->nid => $node));
+ // Clone the node before previewing it to prevent the node itself from being
+ // modified.
+ $cloned_node = clone $node;
+ if (node_access('create', $cloned_node) || node_access('update', $cloned_node)) {
+ _field_invoke_multiple('load', 'node', array($cloned_node->nid => $cloned_node));
// Load the user's name when needed.
- if (isset($node->name)) {
+ if (isset($cloned_node->name)) {
// The use of isset() is mandatory in the context of user IDs, because
// user ID 0 denotes the anonymous user.
- if ($user = user_load_by_name($node->name)) {
- $node->uid = $user->uid;
- $node->picture = $user->picture;
+ if ($user = user_load_by_name($cloned_node->name)) {
+ $cloned_node->uid = $user->uid;
+ $cloned_node->picture = $user->picture;
}
else {
- $node->uid = 0; // anonymous user
+ $cloned_node->uid = 0; // anonymous user
}
}
- elseif ($node->uid) {
- $user = user_load($node->uid);
- $node->name = $user->name;
- $node->picture = $user->picture;
+ elseif ($cloned_node->uid) {
+ $user = user_load($cloned_node->uid);
+ $cloned_node->name = $user->name;
+ $cloned_node->picture = $user->picture;
}
- $node->changed = REQUEST_TIME;
- $nodes = array($node->nid => $node);
+ $cloned_node->changed = REQUEST_TIME;
+ $nodes = array($cloned_node->nid => $cloned_node);
field_attach_prepare_view('node', $nodes, 'full');
// Display a preview of the node.
if (!form_get_errors()) {
- $node->in_preview = TRUE;
- $output = theme('node_preview', array('node' => $node));
- unset($node->in_preview);
+ $cloned_node->in_preview = TRUE;
+ $output = theme('node_preview', array('node' => $cloned_node));
+ unset($cloned_node->in_preview);
}
drupal_set_title(t('Preview'), PASS_THROUGH);
diff --git a/modules/node/node.test b/modules/node/node.test
index 0777e11..5c9118e 100644
--- a/modules/node/node.test
+++ b/modules/node/node.test
@@ -2782,8 +2782,8 @@ class NodeEntityViewModeAlterTest extends NodeWebTestCase {
$edit = array();
$langcode = LANGUAGE_NONE;
$edit["title"] = $this->randomName(8);
- $edit["body[$langcode][0][value]"] = t('Data that should appear only in the body for the node.');
- $edit["body[$langcode][0][summary]"] = t('Extra data that should appear only in the teaser for the node.');
+ $edit["body[$langcode][0][value]"] = 'Data that should appear only in the body for the node.';
+ $edit["body[$langcode][0][summary]"] = 'Extra data that should appear only in the teaser for the node.';
$this->drupalPost('node/add/page', $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit["title"]);
@@ -2801,6 +2801,45 @@ class NodeEntityViewModeAlterTest extends NodeWebTestCase {
$build = node_view($node);
$this->assertEqual($build['#view_mode'], 'teaser', 'The view mode has correctly been set to teaser.');
}
+
+ /**
+ * Tests fields that were previously hidden when the view mode is changed.
+ */
+ function testNodeViewModeChangeHiddenField() {
+ // Hide the tags field on the default display
+ $instance = field_info_instance('node', 'field_tags', 'article');
+ $instance['display']['default']['type'] = 'hidden';
+ field_update_instance($instance);
+
+ $web_user = $this->drupalCreateUser(array('create article content', 'edit own article content'));
+ $this->drupalLogin($web_user);
+
+ // Create a node.
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName(8);
+ $edit["body[$langcode][0][value]"] = 'Data that should appear only in the body for the node.';
+ $edit["body[$langcode][0][summary]"] = 'Extra data that should appear only in the teaser for the node.';
+ $edit["field_tags[$langcode]"] = 'Extra tag';
+ $this->drupalPost('node/add/article', $edit, t('Save'));
+
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+
+ // Set the flag to alter the view mode and view the node.
+ variable_set('node_test_change_view_mode', 'teaser');
+ $this->drupalGet('node/' . $node->nid);
+
+ // Check that teaser mode is viewed.
+ $this->assertText('Extra data that should appear only in the teaser for the node.', 'Teaser text present');
+ // Make sure body text is not present.
+ $this->assertNoText('Data that should appear only in the body for the node.', 'Body text not present');
+ // Make sure tags are present.
+ $this->assertText('Extra tag', 'Taxonomy term present');
+
+ // Test that the correct build mode has been set.
+ $build = node_view($node);
+ $this->assertEqual($build['#view_mode'], 'teaser', 'The view mode has correctly been set to teaser.');
+ }
}
/**
diff --git a/modules/node/tests/node_access_test.info b/modules/node/tests/node_access_test.info
index e6100db..6cc0b6f 100644
--- a/modules/node/tests/node_access_test.info
+++ b/modules/node/tests/node_access_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/node/tests/node_access_test.module b/modules/node/tests/node_access_test.module
index ec35c41..7932c55 100644
--- a/modules/node/tests/node_access_test.module
+++ b/modules/node/tests/node_access_test.module
@@ -211,7 +211,7 @@ function node_access_test_node_insert($node) {
}
/**
- * Implements hook_nodeapi_update().
+ * Implements hook_node_update().
*/
function node_access_test_node_update($node) {
_node_access_test_node_write($node);
diff --git a/modules/node/tests/node_test.info b/modules/node/tests/node_test.info
index 50d374b..9381677 100644
--- a/modules/node/tests/node_test.info
+++ b/modules/node/tests/node_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/node/tests/node_test_exception.info b/modules/node/tests/node_test_exception.info
index 3ddb410..3816caa 100644
--- a/modules/node/tests/node_test_exception.info
+++ b/modules/node/tests/node_test_exception.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/openid/openid.info b/modules/openid/openid.info
index 38a2c04..026b257 100644
--- a/modules/openid/openid.info
+++ b/modules/openid/openid.info
@@ -5,8 +5,8 @@ package = Core
core = 7.x
files[] = openid.test
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/openid/tests/openid_test.info b/modules/openid/tests/openid_test.info
index 8c94884..5dbba8b 100644
--- a/modules/openid/tests/openid_test.info
+++ b/modules/openid/tests/openid_test.info
@@ -6,8 +6,8 @@ core = 7.x
dependencies[] = openid
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/overlay/overlay.info b/modules/overlay/overlay.info
index d26406a..b599359 100644
--- a/modules/overlay/overlay.info
+++ b/modules/overlay/overlay.info
@@ -4,8 +4,8 @@ package = Core
version = VERSION
core = 7.x
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/path/path.info b/modules/path/path.info
index 19da1ce..c89294e 100644
--- a/modules/path/path.info
+++ b/modules/path/path.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = path.test
configure = admin/config/search/path
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/php/php.info b/modules/php/php.info
index a21a0e6..0700f9e 100644
--- a/modules/php/php.info
+++ b/modules/php/php.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
files[] = php.test
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/poll/poll.info b/modules/poll/poll.info
index 2092e04..4e49936 100644
--- a/modules/poll/poll.info
+++ b/modules/poll/poll.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = poll.test
stylesheets[all][] = poll.css
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/poll/poll.module b/modules/poll/poll.module
index 70eb65d..bfc72bf 100644
--- a/modules/poll/poll.module
+++ b/modules/poll/poll.module
@@ -191,7 +191,6 @@ function poll_node_info() {
'base' => 'poll',
'description' => t('A poll is a question with a set of possible responses. A poll, once created, automatically provides a simple running count of the number of votes received for each response.'),
'title_label' => t('Question'),
- 'has_body' => FALSE,
)
);
}
@@ -249,6 +248,7 @@ function poll_form($node, &$form_state) {
'#title' => check_plain($type->title_label),
'#required' => TRUE,
'#default_value' => $node->title,
+ '#maxlength' => 255,
'#weight' => -5,
);
@@ -720,7 +720,6 @@ function poll_view_voting($form, &$form_state, $node, $block = FALSE) {
'#type' => 'radios',
'#title' => t('Choices'),
'#title_display' => 'invisible',
- '#default_value' => -1,
'#options' => $list,
);
}
@@ -748,7 +747,7 @@ function poll_view_voting($form, &$form_state, $node, $block = FALSE) {
* Validation function for processing votes
*/
function poll_view_voting_validate($form, &$form_state) {
- if ($form_state['values']['choice'] == -1) {
+ if (empty($form_state['values']['choice'])) {
form_set_error( 'choice', t('Your vote could not be recorded because you did not select any of the choices.'));
}
}
@@ -925,7 +924,6 @@ function template_preprocess_poll_results(&$variables) {
*
* @see poll-bar.tpl.php
* @see poll-bar--block.tpl.php
- * @see theme_poll_bar()
*/
function template_preprocess_poll_bar(&$variables) {
if ($variables['block']) {
diff --git a/modules/poll/poll.test b/modules/poll/poll.test
index 35eea22..e24032d 100644
--- a/modules/poll/poll.test
+++ b/modules/poll/poll.test
@@ -315,6 +315,11 @@ class PollVoteTestCase extends PollTestCase {
$this->drupalLogin($vote_user);
+ // Record a vote without selecting any choice.
+ $edit = array();
+ $this->drupalPost('node/' . $poll_nid, $edit, t('Vote'));
+ $this->assertText(t('Your vote could not be recorded because you did not select any of the choices.'), 'Found the empty poll submission error message.');
+
// Record a vote for the first choice.
$edit = array(
'choice' => '1',
diff --git a/modules/profile/profile.info b/modules/profile/profile.info
index deab4fe..ce35360 100644
--- a/modules/profile/profile.info
+++ b/modules/profile/profile.info
@@ -11,8 +11,8 @@ configure = admin/config/people/profile
; See user_system_info_alter().
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/rdf/rdf.info b/modules/rdf/rdf.info
index cf3c929..f32d9dc 100644
--- a/modules/rdf/rdf.info
+++ b/modules/rdf/rdf.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
files[] = rdf.test
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/rdf/rdf.module b/modules/rdf/rdf.module
index 877b598..90a7382 100644
--- a/modules/rdf/rdf.module
+++ b/modules/rdf/rdf.module
@@ -190,17 +190,33 @@ function _rdf_get_default_mapping($type) {
* An RDF mapping structure or an empty array if no record was found.
*/
function _rdf_mapping_load($type, $bundle) {
- $mapping = db_select('rdf_mapping')
- ->fields(NULL, array('mapping'))
+ $mappings = _rdf_mapping_load_multiple($type, array($bundle));
+ return $mappings ? reset($mappings) : array();
+}
+
+/**
+ * Helper function to retrieve a set of RDF mappings from the database.
+ *
+ * @param $type
+ * The entity type of the mappings.
+ * @param $bundles
+ * The bundles the mappings refer to.
+ *
+ * @return
+ * An array of RDF mapping structures, or an empty array.
+ */
+function _rdf_mapping_load_multiple($type, array $bundles) {
+ $mappings = db_select('rdf_mapping')
+ ->fields(NULL, array('bundle', 'mapping'))
->condition('type', $type)
- ->condition('bundle', $bundle)
+ ->condition('bundle', $bundles)
->execute()
- ->fetchField();
+ ->fetchAllKeyed();
- if (!$mapping) {
- return array();
+ foreach ($mappings as $bundle => $mapping) {
+ $mappings[$bundle] = unserialize($mapping);
}
- return unserialize($mapping);
+ return $mappings;
}
/**
@@ -368,10 +384,13 @@ function rdf_modules_uninstalled($modules) {
function rdf_entity_info_alter(&$entity_info) {
// Loop through each entity type and its bundles.
foreach ($entity_info as $entity_type => $entity_type_info) {
- if (isset($entity_type_info['bundles'])) {
- foreach ($entity_type_info['bundles'] as $bundle => $bundle_info) {
- if ($mapping = _rdf_mapping_load($entity_type, $bundle)) {
- $entity_info[$entity_type]['bundles'][$bundle]['rdf_mapping'] = $mapping;
+ if (!empty($entity_type_info['bundles'])) {
+ $bundles = array_keys($entity_type_info['bundles']);
+ $mappings = _rdf_mapping_load_multiple($entity_type, $bundles);
+
+ foreach ($bundles as $bundle) {
+ if (isset($mappings[$bundle])) {
+ $entity_info[$entity_type]['bundles'][$bundle]['rdf_mapping'] = $mappings[$bundle];
}
else {
// If no mapping was found in the database, assign the default RDF
@@ -471,27 +490,17 @@ function rdf_preprocess_node(&$variables) {
$variables['attributes_array']['about'] = empty($variables['node_url']) ? NULL: $variables['node_url'];
$variables['attributes_array']['typeof'] = empty($variables['node']->rdf_mapping['rdftype']) ? NULL : $variables['node']->rdf_mapping['rdftype'];
- // Adds RDFa markup to the title of the node. Because the RDFa markup is
- // added to the tag which might contain HTML code, we specify an empty
- // datatype to ensure the value of the title read by the RDFa parsers is a
- // literal.
- $variables['title_attributes_array']['property'] = empty($variables['node']->rdf_mapping['title']['predicates']) ? NULL : $variables['node']->rdf_mapping['title']['predicates'];
- $variables['title_attributes_array']['datatype'] = '';
-
- // In full node mode, the title is not displayed by node.tpl.php so it is
- // added in the tag of the HTML page.
- if ($variables['page']) {
- $element = array(
- '#tag' => 'meta',
- '#attributes' => array(
- 'content' => $variables['node']->title,
- 'about' => $variables['node_url'],
+ // Adds RDFa markup about the title of the node to the title_suffix.
+ if (!empty($variables['node']->rdf_mapping['title']['predicates'])) {
+ $variables['title_suffix']['rdf_meta_title'] = array(
+ '#theme' => 'rdf_metadata',
+ '#metadata' => array(
+ array(
+ 'property' => $variables['node']->rdf_mapping['title']['predicates'],
+ 'content' => $variables['node']->title,
+ ),
),
);
- if (!empty($variables['node']->rdf_mapping['title']['predicates'])) {
- $element['#attributes']['property'] = $variables['node']->rdf_mapping['title']['predicates'];
- }
- drupal_add_html_head($element, 'rdf_node_title');
}
// Adds RDFa markup for the date.
@@ -511,35 +520,20 @@ function rdf_preprocess_node(&$variables) {
}
// Adds RDFa markup annotating the number of comments a node has.
- if (isset($variables['node']->comment_count) && !empty($variables['node']->rdf_mapping['comment_count']['predicates'])) {
- // Annotates the 'x comments' link in teaser view.
- if (isset($variables['content']['links']['comment']['#links']['comment-comments'])) {
- $comment_count_attributes['property'] = $variables['node']->rdf_mapping['comment_count']['predicates'];
- $comment_count_attributes['content'] = $variables['node']->comment_count;
- $comment_count_attributes['datatype'] = $variables['node']->rdf_mapping['comment_count']['datatype'];
- // According to RDFa parsing rule number 4, a new subject URI is created
- // from the href attribute if no rel/rev attribute is present. To get the
- // original node URL from the about attribute of the parent container we
- // set an empty rel attribute which triggers rule number 5. See
- // http://www.w3.org/TR/rdfa-syntax/#sec_5.5.
- $comment_count_attributes['rel'] = '';
- $variables['content']['links']['comment']['#links']['comment-comments']['attributes'] += $comment_count_attributes;
- }
- // In full node view, the number of comments is not displayed by
- // node.tpl.php so it is expressed in RDFa in the tag of the HTML
- // page.
- if ($variables['page'] && user_access('access comments')) {
- $element = array(
- '#tag' => 'meta',
- '#attributes' => array(
- 'about' => $variables['node_url'],
+ if (isset($variables['node']->comment_count) &&
+ !empty($variables['node']->rdf_mapping['comment_count']['predicates']) &&
+ user_access('access comments')) {
+ // Adds RDFa markup for the comment count near the node title as metadata.
+ $variables['title_suffix']['rdf_meta_comment_count'] = array(
+ '#theme' => 'rdf_metadata',
+ '#metadata' => array(
+ array(
'property' => $variables['node']->rdf_mapping['comment_count']['predicates'],
'content' => $variables['node']->comment_count,
'datatype' => $variables['node']->rdf_mapping['comment_count']['datatype'],
),
- );
- drupal_add_html_head($element, 'rdf_node_comment_count');
- }
+ ),
+ );
}
}
@@ -865,9 +859,9 @@ function theme_rdf_metadata($variables) {
$output = '';
foreach ($variables['metadata'] as $attributes) {
// Add a class so that developers viewing the HTML source can see why there
- // are empty tags in the document. The class can also be used to set
- // a CSS display:none rule in a theme where empty spans affect display.
+ // are empty tags in the document.
$attributes['class'][] = 'rdf-meta';
+ $attributes['class'][] = 'element-hidden';
// The XHTML+RDFa doctype allows either or syntax to
// be used, but for maximum browser compatibility, W3C recommends the
// former when serving pages using the text/html media type, see
diff --git a/modules/rdf/rdf.test b/modules/rdf/rdf.test
index 370dbb2..22c41f1 100644
--- a/modules/rdf/rdf.test
+++ b/modules/rdf/rdf.test
@@ -301,7 +301,7 @@ class RdfMappingDefinitionTestCase extends TaxonomyWebTestCase {
// Ensure the default bundle mapping for node is used. These attributes come
// from the node default bundle definition.
- $blog_title = $this->xpath("//meta[@property='dc:title' and @content='$node->title']");
+ $blog_title = $this->xpath("//div[@about='$url']/span[@property='dc:title' and @content='$node->title']");
$blog_meta = $this->xpath("//div[(@about='$url') and (@typeof='sioct:Weblog')]//span[contains(@property, 'dc:date') and contains(@property, 'dc:created') and @datatype='xsd:dateTime' and @content='$isoDate']");
$this->assertTrue(!empty($blog_title), 'Property dc:title is present in meta tag.');
$this->assertTrue(!empty($blog_meta), 'RDF type is present on post. Properties dc:date and dc:created are present on post date.');
@@ -324,7 +324,7 @@ class RdfMappingDefinitionTestCase extends TaxonomyWebTestCase {
$this->drupalGet('node/' . $node->nid);
// Ensure the mapping defined in rdf_module.test is used.
- $test_bundle_title = $this->xpath('//meta[@property="dc:title" and @content="' . $node->title . '"]');
+ $test_bundle_title = $this->xpath("//div[@about='$url']/span[@property='dc:title' and @content=\"$node->title\"]");
$test_bundle_meta = $this->xpath("//div[(@about='$url') and contains(@typeof, 'foo:mapping_install1') and contains(@typeof, 'bar:mapping_install2')]//span[contains(@property, 'dc:date') and contains(@property, 'dc:created') and @datatype='xsd:dateTime' and @content='$isoDate']");
$this->assertTrue(!empty($test_bundle_title), 'Property dc:title is present in meta tag.');
$this->assertTrue(!empty($test_bundle_meta), 'RDF type is present on post. Properties dc:date and dc:created are present on post date.');
@@ -343,7 +343,7 @@ class RdfMappingDefinitionTestCase extends TaxonomyWebTestCase {
// Ensure the default bundle mapping for node is used. These attributes come
// from the node default bundle definition.
- $random_bundle_title = $this->xpath("//meta[@property='dc:title' and @content='$node->title']");
+ $random_bundle_title = $this->xpath("//div[@about='$url']/span[@property='dc:title' and @content='$node->title']");
$random_bundle_meta = $this->xpath("//div[(@about='$url') and contains(@typeof, 'sioc:Item') and contains(@typeof, 'foaf:Document')]//span[contains(@property, 'dc:date') and contains(@property, 'dc:created') and @datatype='xsd:dateTime' and @content='$isoDate']");
$this->assertTrue(!empty($random_bundle_title), 'Property dc:title is present in meta tag.');
$this->assertTrue(!empty($random_bundle_meta), 'RDF type is present on post. Properties dc:date and dc:created are present on post date.');
@@ -461,15 +461,13 @@ class RdfCommentAttributesTestCase extends CommentHelperCase {
// Tests number of comments in teaser view.
$this->drupalGet('node');
- $comment_count_teaser = $this->xpath('//div[contains(@typeof, "sioc:Item")]//li[contains(@class, "comment-comments")]/a[contains(@property, "sioc:num_replies") and contains(@content, "2") and @datatype="xsd:integer"]');
+ $node_url = url('node/' . $this->node1->nid);
+ $comment_count_teaser = $this->xpath('//div[@about=:node-url]/span[@property="sioc:num_replies" and @content="2" and @datatype="xsd:integer"]', array(':node-url' => $node_url));
$this->assertTrue(!empty($comment_count_teaser), 'RDFa markup for the number of comments found on teaser view.');
- $comment_count_link = $this->xpath('//div[@about=:url]//a[contains(@property, "sioc:num_replies") and @rel=""]', array(':url' => url("node/{$this->node1->nid}")));
- $this->assertTrue(!empty($comment_count_link), 'Empty rel attribute found in comment count link.');
// Tests number of comments in full node view.
$this->drupalGet('node/' . $this->node1->nid);
- $node_url = url('node/' . $this->node1->nid);
- $comment_count_teaser = $this->xpath('/html/head/meta[@about=:node-url and @property="sioc:num_replies" and @content="2" and @datatype="xsd:integer"]', array(':node-url' => $node_url));
+ $comment_count_teaser = $this->xpath('//div[@about=:node-url]/span[@property="sioc:num_replies" and @content="2" and @datatype="xsd:integer"]', array(':node-url' => $node_url));
$this->assertTrue(!empty($comment_count_teaser), 'RDFa markup for the number of comments found on full node view.');
}
diff --git a/modules/rdf/tests/rdf_test.info b/modules/rdf/tests/rdf_test.info
index 6c9c845..1937aaf 100644
--- a/modules/rdf/tests/rdf_test.info
+++ b/modules/rdf/tests/rdf_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/search/search.extender.inc b/modules/search/search.extender.inc
index 6709466..72cea64 100644
--- a/modules/search/search.extender.inc
+++ b/modules/search/search.extender.inc
@@ -149,6 +149,17 @@ class SearchQuery extends SelectQueryExtender {
$this->searchExpression = $expression;
$this->type = $module;
+ // Add a search_* tag. This needs to be added before any preExecute methods
+ // for decorated queries are called, as $this->prepared will be set to TRUE
+ // and tags added in the execute method will never get used. For example,
+ // if $query is extended by 'SearchQuery' then 'PagerDefault', the
+ // search-specific tag will be added too late (when preExecute() has
+ // already been called from the PagerDefault extender), and as a
+ // consequence will not be available to hook_query_alter() implementations,
+ // nor will the correct hook_query_TAG_alter() implementations get invoked.
+ // See node_search_execute().
+ $this->addTag('search_' . $module);
+
return $this;
}
@@ -494,9 +505,8 @@ class SearchQuery extends SelectQueryExtender {
$this->orderBy('calculated_score', 'DESC');
}
- // Add tag and useful metadata.
+ // Add useful metadata.
$this
- ->addTag('search_' . $this->type)
->addMetaData('normalize', $this->normalize)
->fields('i', array('type', 'sid'));
diff --git a/modules/search/search.info b/modules/search/search.info
index 3118222..faf3e4d 100644
--- a/modules/search/search.info
+++ b/modules/search/search.info
@@ -8,8 +8,8 @@ files[] = search.test
configure = admin/config/search/settings
stylesheets[all][] = search.css
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/search/search.test b/modules/search/search.test
index 19f4e55..5f16db3 100644
--- a/modules/search/search.test
+++ b/modules/search/search.test
@@ -2048,6 +2048,47 @@ class SearchNodeAccessTest extends DrupalWebTestCase {
}
}
+/**
+ * Tests node search with query tags.
+ */
+class SearchNodeTagTest extends DrupalWebTestCase {
+ public $test_user;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node search query tags',
+ 'description' => 'Tests Node search tags functionality.',
+ 'group' => 'Search',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('search', 'search_node_tags');
+ node_access_rebuild();
+
+ // Create a test user and log in.
+ $this->test_user = $this->drupalCreateUser(array('search content'));
+ $this->drupalLogin($this->test_user);
+ }
+
+ /**
+ * Tests that the correct tags are available and hooks invoked.
+ */
+ function testNodeSearchQueryTags() {
+ $this->drupalCreateNode(array('body' => array(LANGUAGE_NONE => array(array('value' => 'testing testing testing.')))));
+
+ // Update the search index.
+ module_invoke_all('update_index');
+ search_update_totals();
+
+ $edit = array('keys' => 'testing');
+ $this->drupalPost('search/node', $edit, t('Search'));
+
+ $this->assertTrue(variable_get('search_node_tags_test_query_tag', FALSE), 'hook_query_alter() was invoked and the query contained the "search_node" tag.');
+ $this->assertTrue(variable_get('search_node_tags_test_query_tag_hook', FALSE), 'hook_query_search_node_alter() was invoked.');
+ }
+}
+
/**
* Tests searching with locale values set.
*/
diff --git a/modules/search/tests/search_embedded_form.info b/modules/search/tests/search_embedded_form.info
index d49303f..d09408e 100644
--- a/modules/search/tests/search_embedded_form.info
+++ b/modules/search/tests/search_embedded_form.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/search/tests/search_extra_type.info b/modules/search/tests/search_extra_type.info
index cc6536d..da02811 100644
--- a/modules/search/tests/search_extra_type.info
+++ b/modules/search/tests/search_extra_type.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/search/tests/search_node_tags.info b/modules/search/tests/search_node_tags.info
new file mode 100644
index 0000000..325723f
--- /dev/null
+++ b/modules/search/tests/search_node_tags.info
@@ -0,0 +1,12 @@
+name = "Test search node tags"
+description = "Support module for Node search tags testing."
+package = Testing
+version = VERSION
+core = 7.x
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
+project = "drupal"
+datestamp = "1430973154"
+
diff --git a/modules/search/tests/search_node_tags.module b/modules/search/tests/search_node_tags.module
new file mode 100644
index 0000000..b66dd9e
--- /dev/null
+++ b/modules/search/tests/search_node_tags.module
@@ -0,0 +1,23 @@
+hasTag('search_node')) {
+ variable_set('search_node_tags_test_query_tag', TRUE);
+ }
+}
+
+/**
+ * Implements hook_query_TAG_alter().
+ */
+function search_node_tags_query_search_node_alter(QueryAlterableInterface $query) {
+ variable_set('search_node_tags_test_query_tag_hook', TRUE);
+}
diff --git a/modules/shortcut/shortcut.info b/modules/shortcut/shortcut.info
index 64d7abd..83cf47d 100644
--- a/modules/shortcut/shortcut.info
+++ b/modules/shortcut/shortcut.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = shortcut.test
configure = admin/config/user-interface/shortcut
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php
index 8022bf3..fb5c6a6 100644
--- a/modules/simpletest/drupal_web_test_case.php
+++ b/modules/simpletest/drupal_web_test_case.php
@@ -143,15 +143,7 @@ protected function assert($status, $message = '', $group = 'Other', array $calle
);
// Store assertion for display after the test has completed.
- try {
- $connection = Database::getConnection('default', 'simpletest_original_default');
- }
- catch (DatabaseConnectionNotDefinedException $e) {
- // If the test was not set up, the simpletest_original_default
- // connection does not exist.
- $connection = Database::getConnection('default', 'default');
- }
- $connection
+ self::getDatabaseConnection()
->insert('simpletest')
->fields($assertion)
->execute();
@@ -166,6 +158,25 @@ protected function assert($status, $message = '', $group = 'Other', array $calle
}
}
+ /**
+ * Returns the database connection to the site running Simpletest.
+ *
+ * @return DatabaseConnection
+ * The database connection to use for inserting assertions.
+ */
+ public static function getDatabaseConnection() {
+ try {
+ $connection = Database::getConnection('default', 'simpletest_original_default');
+ }
+ catch (DatabaseConnectionNotDefinedException $e) {
+ // If the test was not set up, the simpletest_original_default
+ // connection does not exist.
+ $connection = Database::getConnection('default', 'default');
+ }
+
+ return $connection;
+ }
+
/**
* Store an assertion from outside the testing context.
*
@@ -205,7 +216,8 @@ public static function insertAssert($test_id, $test_class, $status, $message = '
'file' => $caller['file'],
);
- return db_insert('simpletest')
+ return self::getDatabaseConnection()
+ ->insert('simpletest')
->fields($assertion)
->execute();
}
@@ -221,7 +233,8 @@ public static function insertAssert($test_id, $test_class, $status, $message = '
* @see DrupalTestCase::insertAssert()
*/
public static function deleteAssert($message_id) {
- return (bool) db_delete('simpletest')
+ return (bool) self::getDatabaseConnection()
+ ->delete('simpletest')
->condition('message_id', $message_id)
->execute();
}
@@ -435,10 +448,10 @@ protected function error($message = '', $group = 'Other', array $caller = NULL)
}
/**
- * Logs verbose message in a text file.
+ * Logs a verbose message in a text file.
*
- * The a link to the vebose message will be placed in the test results via
- * as a passing assertion with the text '[verbose message]'.
+ * The link to the verbose message will be placed in the test results as a
+ * passing assertion with the text '[verbose message]'.
*
* @param $message
* The verbose message to be stored.
@@ -1756,14 +1769,24 @@ protected function curlInitialize() {
protected function curlExec($curl_options, $redirect = FALSE) {
$this->curlInitialize();
- // cURL incorrectly handles URLs with a fragment by including the
- // fragment in the request to the server, causing some web servers
- // to reject the request citing "400 - Bad Request". To prevent
- // this, we strip the fragment from the request.
- // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0.
- if (!empty($curl_options[CURLOPT_URL]) && strpos($curl_options[CURLOPT_URL], '#')) {
- $original_url = $curl_options[CURLOPT_URL];
- $curl_options[CURLOPT_URL] = strtok($curl_options[CURLOPT_URL], '#');
+ if (!empty($curl_options[CURLOPT_URL])) {
+ // Forward XDebug activation if present.
+ if (isset($_COOKIE['XDEBUG_SESSION'])) {
+ $options = drupal_parse_url($curl_options[CURLOPT_URL]);
+ $options += array('query' => array());
+ $options['query'] += array('XDEBUG_SESSION_START' => $_COOKIE['XDEBUG_SESSION']);
+ $curl_options[CURLOPT_URL] = url($options['path'], $options);
+ }
+
+ // cURL incorrectly handles URLs with a fragment by including the
+ // fragment in the request to the server, causing some web servers
+ // to reject the request citing "400 - Bad Request". To prevent
+ // this, we strip the fragment from the request.
+ // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0.
+ if (strpos($curl_options[CURLOPT_URL], '#')) {
+ $original_url = $curl_options[CURLOPT_URL];
+ $curl_options[CURLOPT_URL] = strtok($curl_options[CURLOPT_URL], '#');
+ }
}
$url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL];
@@ -2234,8 +2257,13 @@ protected function drupalPostAJAX($path, $edit, $triggering_element, $ajax_path
if ($wrapperNode) {
// ajax.js adds an enclosing DIV to work around a Safari bug.
$newDom = new DOMDocument();
+ // DOM can load HTML soup. But, HTML soup can throw warnings,
+ // suppress them.
$newDom->loadHTML('' . $command['data'] . '
');
- $newNode = $dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE);
+ // Suppress warnings thrown when duplicate HTML IDs are
+ // encountered. This probably means we are replacing an element
+ // with the same ID.
+ $newNode = @$dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE);
$method = isset($command['method']) ? $command['method'] : $ajax_settings['method'];
// The "method" is a jQuery DOM manipulation function. Emulate
// each one using PHP's DOMNode API.
@@ -2288,6 +2316,8 @@ protected function drupalPostAJAX($path, $edit, $triggering_element, $ajax_path
break;
case 'restripe':
break;
+ case 'add_css':
+ break;
}
}
$content = $dom->saveHTML();
@@ -2682,28 +2712,26 @@ protected function assertNoLinkByHref($href, $message = '', $group = 'Other') {
*
* Will click the first link found with this link text by default, or a later
* one if an index is given. Match is case sensitive with normalized space.
- * The label is translated label. There is an assert for successful click.
+ * The label is translated label.
+ *
+ * If the link is discovered and clicked, the test passes. Fail otherwise.
*
* @param $label
* Text between the anchor tags.
* @param $index
* Link position counting from zero.
* @return
- * Page on success, or FALSE on failure.
+ * Page contents on success, or FALSE on failure.
*/
protected function clickLink($label, $index = 0) {
$url_before = $this->getUrl();
$urls = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label));
-
if (isset($urls[$index])) {
$url_target = $this->getAbsoluteUrl($urls[$index]['href']);
- }
-
- $this->assertTrue(isset($urls[$index]), t('Clicked link %label (@url_target) from @url_before', array('%label' => $label, '@url_target' => $url_target, '@url_before' => $url_before)), t('Browser'));
-
- if (isset($url_target)) {
+ $this->pass(t('Clicked link %label (@url_target) from @url_before', array('%label' => $label, '@url_target' => $url_target, '@url_before' => $url_before)), 'Browser');
return $this->drupalGet($url_target);
}
+ $this->fail(t('Link %label does not exist on @url_before', array('%label' => $label, '@url_before' => $url_before)), 'Browser');
return FALSE;
}
diff --git a/modules/simpletest/files/css_test_files/css_input_with_import.css b/modules/simpletest/files/css_test_files/css_input_with_import.css
index 87afcb3..484db83 100644
--- a/modules/simpletest/files/css_test_files/css_input_with_import.css
+++ b/modules/simpletest/files/css_test_files/css_input_with_import.css
@@ -1,5 +1,7 @@
+@import url("http://example.com/style.css");
+@import url("//example.com/style.css");
@import "import1.css";
@import "import2.css";
diff --git a/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css b/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css
index 698d9aa..a2af7b3 100644
--- a/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css
+++ b/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css
@@ -1,4 +1,4 @@
-ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}
+@import url("http://example.com/style.css");@import url("//example.com/style.css");ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}
p,select{font:1em/160% Verdana,sans-serif;color:#494949;}
body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this
.is
diff --git a/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css b/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css
index 19323c1..bc3c7b6 100644
--- a/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css
+++ b/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css
@@ -1,5 +1,7 @@
+@import url("http://example.com/style.css");
+@import url("//example.com/style.css");
ul, select {
font: 1em/160% Verdana, sans-serif;
@@ -7,6 +9,21 @@ ul, select {
}
.ui-icon{background-image: url(images/icon.png);}
+/* Test data URI images with different quote styles. */
+.data .double-quote {
+ /* http://stackoverflow.com/a/13139830/11023 */
+ background-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
+}
+
+.data .single-quote {
+ background-image: url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');
+}
+
+.data .no-quote {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);
+}
+
+
p, select {
font: 1em/160% Verdana, sans-serif;
color: #494949;
diff --git a/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.optimized.css b/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.optimized.css
index aba3b21..816039d 100644
--- a/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.optimized.css
+++ b/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.optimized.css
@@ -1,4 +1,4 @@
-ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(../images/icon.png);}
+ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(../images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}
p,select{font:1em/160% Verdana,sans-serif;color:#494949;}
body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this
.is
diff --git a/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css b/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css
index 710d8f1..6d7151b 100644
--- a/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css
+++ b/modules/simpletest/files/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css
@@ -7,6 +7,21 @@ ul, select {
}
.ui-icon{background-image: url(../images/icon.png);}
+/* Test data URI images with different quote styles. */
+.data .double-quote {
+ /* http://stackoverflow.com/a/13139830/11023 */
+ background-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
+}
+
+.data .single-quote {
+ background-image: url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');
+}
+
+.data .no-quote {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);
+}
+
+
p, select {
font: 1em/160% Verdana, sans-serif;
color: #494949;
diff --git a/modules/simpletest/files/css_test_files/import1.css b/modules/simpletest/files/css_test_files/import1.css
index 3d5842e..e53d6d5 100644
--- a/modules/simpletest/files/css_test_files/import1.css
+++ b/modules/simpletest/files/css_test_files/import1.css
@@ -3,4 +3,18 @@ ul, select {
font: 1em/160% Verdana, sans-serif;
color: #494949;
}
-.ui-icon{background-image: url(images/icon.png);}
\ No newline at end of file
+.ui-icon{background-image: url(images/icon.png);}
+
+/* Test data URI images with different quote styles. */
+.data .double-quote {
+ /* http://stackoverflow.com/a/13139830/11023 */
+ background-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
+}
+
+.data .single-quote {
+ background-image: url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');
+}
+
+.data .no-quote {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);
+}
diff --git a/modules/simpletest/files/image-test-transparent-out-of-range.gif b/modules/simpletest/files/image-test-transparent-out-of-range.gif
new file mode 100644
index 0000000..a54df7a
Binary files /dev/null and b/modules/simpletest/files/image-test-transparent-out-of-range.gif differ
diff --git a/modules/simpletest/simpletest.info b/modules/simpletest/simpletest.info
index 5090faf..025d032 100644
--- a/modules/simpletest/simpletest.info
+++ b/modules/simpletest/simpletest.info
@@ -56,8 +56,8 @@ files[] = tests/upgrade/update.trigger.test
files[] = tests/upgrade/update.field.test
files[] = tests/upgrade/update.user.test
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/simpletest.module b/modules/simpletest/simpletest.module
index 3103af0..91f0f90 100644
--- a/modules/simpletest/simpletest.module
+++ b/modules/simpletest/simpletest.module
@@ -328,25 +328,32 @@ function simpletest_test_get_all() {
// Also discover PSR-0 test classes, if the PHP version allows it.
if (version_compare(PHP_VERSION, '5.3') > 0) {
- // Select all PSR-0 classes in the Tests namespace of all modules.
+ // Select all PSR-0 and PSR-4 classes in the Tests namespace of all
+ // modules.
$system_list = db_query("SELECT name, filename FROM {system}")->fetchAllKeyed();
foreach ($system_list as $name => $filename) {
- // Build directory in which the test files would reside.
- $tests_dir = DRUPAL_ROOT . '/' . dirname($filename) . '/lib/Drupal/' . $name . '/Tests';
- // Scan it for test files if it exists.
- if (is_dir($tests_dir)) {
- $files = file_scan_directory($tests_dir, '/.*\.php/');
- if (!empty($files)) {
- $basedir = DRUPAL_ROOT . '/' . dirname($filename) . '/lib/';
- foreach ($files as $file) {
- // Convert the file name into the namespaced class name.
- $replacements = array(
- '/' => '\\',
- $basedir => '',
- '.php' => '',
- );
- $classes[] = strtr($file->uri, $replacements);
+ $module_dir = DRUPAL_ROOT . '/' . dirname($filename);
+ // Search both the 'lib/Drupal/mymodule' directory (for PSR-0 classes)
+ // and the 'src' directory (for PSR-4 classes).
+ foreach(array('lib/Drupal/' . $name, 'src') as $subdir) {
+ // Build directory in which the test files would reside.
+ $tests_dir = $module_dir . '/' . $subdir . '/Tests';
+ // Scan it for test files if it exists.
+ if (is_dir($tests_dir)) {
+ $files = file_scan_directory($tests_dir, '/.*\.php/');
+ if (!empty($files)) {
+ foreach ($files as $file) {
+ // Convert the file name into the namespaced class name.
+ $replacements = array(
+ '/' => '\\',
+ $module_dir . '/' => '',
+ 'lib/' => '',
+ 'src/' => 'Drupal\\' . $name . '\\',
+ '.php' => '',
+ );
+ $classes[] = strtr($file->uri, $replacements);
+ }
}
}
}
@@ -406,17 +413,20 @@ function simpletest_classloader_register() {
// Only register PSR-0 class loading if we are on PHP 5.3 or higher.
if (version_compare(PHP_VERSION, '5.3') > 0) {
- spl_autoload_register('_simpletest_autoload_psr0');
+ spl_autoload_register('_simpletest_autoload_psr4_psr0');
}
}
/**
- * Autoload callback to find PSR-0 test classes.
+ * Autoload callback to find PSR-4 and PSR-0 test classes.
+ *
+ * Looks in the 'src/Tests' and in the 'lib/Drupal/mymodule/Tests' directory of
+ * modules for the class.
*
* This will only work on classes where the namespace is of the pattern
* "Drupal\$extension\Tests\.."
*/
-function _simpletest_autoload_psr0($class) {
+function _simpletest_autoload_psr4_psr0($class) {
// Static cache for extension paths.
// This cache is lazily filled as soon as it is needed.
@@ -446,14 +456,26 @@ function _simpletest_autoload_psr0($class) {
$namespace = substr($class, 0, $nspos);
$classname = substr($class, $nspos + 1);
- // Build the filepath where we expect the class to be defined.
- $path = dirname($extensions[$extension]) . '/lib/' .
- str_replace('\\', '/', $namespace) . '/' .
+ // Try the PSR-4 location first, and the PSR-0 location as a fallback.
+ // Build the PSR-4 filepath where we expect the class to be defined.
+ $psr4_path = dirname($extensions[$extension]) . '/src/' .
+ str_replace('\\', '/', substr($namespace, strlen('Drupal\\' . $extension . '\\'))) . '/' .
str_replace('_', '/', $classname) . '.php';
// Include the file, if it does exist.
- if (file_exists($path)) {
- include $path;
+ if (file_exists($psr4_path)) {
+ include $psr4_path;
+ }
+ else {
+ // Build the PSR-0 filepath where we expect the class to be defined.
+ $psr0_path = dirname($extensions[$extension]) . '/lib/' .
+ str_replace('\\', '/', $namespace) . '/' .
+ str_replace('_', '/', $classname) . '.php';
+
+ // Include the file, if it does exist.
+ if (file_exists($psr0_path)) {
+ include $psr0_path;
+ }
}
}
}
diff --git a/modules/simpletest/simpletest.test b/modules/simpletest/simpletest.test
index dde162e..f22ef95 100644
--- a/modules/simpletest/simpletest.test
+++ b/modules/simpletest/simpletest.test
@@ -703,7 +703,9 @@ class SimpleTestDiscoveryTestCase extends DrupalWebTestCase {
$classes_all = simpletest_test_get_all();
foreach (array(
'Drupal\\simpletest\\Tests\\PSR0WebTest',
+ 'Drupal\\simpletest\\Tests\\PSR4WebTest',
'Drupal\\psr_0_test\\Tests\\ExampleTest',
+ 'Drupal\\psr_4_test\\Tests\\ExampleTest',
) as $class) {
$this->assert(!empty($classes_all['SimpleTest'][$class]), t('Class @class must be discovered by simpletest_test_get_all().', array('@class' => $class)));
}
@@ -726,15 +728,20 @@ class SimpleTestDiscoveryTestCase extends DrupalWebTestCase {
// Don't expect PSR-0 tests to be discovered on older PHP versions.
return;
}
- // This one is provided by simpletest itself via PSR-0.
+ // These are provided by simpletest itself via PSR-0 and PSR-4.
$this->assertText('PSR0 web test');
+ $this->assertText('PSR4 web test');
$this->assertText('PSR0 example test: PSR-0 in disabled modules.');
+ $this->assertText('PSR4 example test: PSR-4 in disabled modules.');
$this->assertText('PSR0 example test: PSR-0 in nested subfolders.');
+ $this->assertText('PSR4 example test: PSR-4 in nested subfolders.');
// Test each test individually.
foreach (array(
'Drupal\\psr_0_test\\Tests\\ExampleTest',
'Drupal\\psr_0_test\\Tests\\Nested\\NestedExampleTest',
+ 'Drupal\\psr_4_test\\Tests\\ExampleTest',
+ 'Drupal\\psr_4_test\\Tests\\Nested\\NestedExampleTest',
) as $class) {
$this->drupalGet('admin/config/development/testing');
$edit = array($class => TRUE);
diff --git a/modules/simpletest/src/Tests/PSR4WebTest.php b/modules/simpletest/src/Tests/PSR4WebTest.php
new file mode 100644
index 0000000..24c8d89
--- /dev/null
+++ b/modules/simpletest/src/Tests/PSR4WebTest.php
@@ -0,0 +1,18 @@
+ 'PSR4 web test',
+ 'description' => 'We want to assert that this PSR-4 test case is being discovered.',
+ 'group' => 'SimpleTest',
+ );
+ }
+
+ function testArithmetics() {
+ $this->assert(1 + 1 == 2, '1 + 1 == 2');
+ }
+}
diff --git a/modules/simpletest/tests/actions_loop_test.info b/modules/simpletest/tests/actions_loop_test.info
index 39e7506..2adea5e 100644
--- a/modules/simpletest/tests/actions_loop_test.info
+++ b/modules/simpletest/tests/actions_loop_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/ajax.test b/modules/simpletest/tests/ajax.test
index a0c7be8..afe0230 100644
--- a/modules/simpletest/tests/ajax.test
+++ b/modules/simpletest/tests/ajax.test
@@ -293,7 +293,7 @@ class AJAXCommandsTestCase extends AJAXTestCase {
$this->assertCommand($commands, $expected, "'changed' AJAX command (with asterisk) issued with correct selector");
// Tests the 'css' command.
- $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("Set the the '#box' div to be blue.")));
+ $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("Set the '#box' div to be blue.")));
$expected = array(
'command' => 'css',
'selector' => '#css_div',
@@ -368,6 +368,14 @@ class AJAXCommandsTestCase extends AJAXTestCase {
'settings' => array('ajax_forms_test' => array('foo' => 42)),
);
$this->assertCommand($commands, $expected, "'settings' AJAX command issued with correct data");
+
+ // Tests the 'add_css' command.
+ $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'add_css' command")));
+ $expected = array(
+ 'command' => 'add_css',
+ 'data' => 'my/file.css',
+ );
+ $this->assertCommand($commands, $expected, "'add_css' AJAX command issued with correct data");
}
}
diff --git a/modules/simpletest/tests/ajax_forms_test.info b/modules/simpletest/tests/ajax_forms_test.info
index 2eed3f5..1d3b3fb 100644
--- a/modules/simpletest/tests/ajax_forms_test.info
+++ b/modules/simpletest/tests/ajax_forms_test.info
@@ -5,8 +5,8 @@ package = Testing
version = VERSION
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/ajax_forms_test.module b/modules/simpletest/tests/ajax_forms_test.module
index 2840422..de2fa0b 100644
--- a/modules/simpletest/tests/ajax_forms_test.module
+++ b/modules/simpletest/tests/ajax_forms_test.module
@@ -157,7 +157,7 @@ function ajax_forms_test_ajax_commands_form($form, &$form_state) {
// Shows the Ajax 'css' command.
$form['css_command_example'] = array(
- '#value' => t("Set the the '#box' div to be blue."),
+ '#value' => t("Set the '#box' div to be blue."),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_css_callback',
@@ -254,6 +254,15 @@ function ajax_forms_test_ajax_commands_form($form, &$form_state) {
),
);
+ // Shows the Ajax 'add_css' command.
+ $form['add_css_command_example'] = array(
+ '#type' => 'submit',
+ '#value' => t("AJAX 'add_css' command"),
+ '#ajax' => array(
+ 'callback' => 'ajax_forms_test_advanced_commands_add_css_callback',
+ ),
+ );
+
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
@@ -406,6 +415,15 @@ function ajax_forms_test_advanced_commands_settings_callback($form, $form_state)
return array('#type' => 'ajax', '#commands' => $commands);
}
+/**
+ * Ajax callback for 'add_css'.
+ */
+function ajax_forms_test_advanced_commands_add_css_callback($form, $form_state) {
+ $commands = array();
+ $commands[] = ajax_command_add_css('my/file.css');
+ return array('#type' => 'ajax', '#commands' => $commands);
+}
+
/**
* This form and its related submit and callback functions demonstrate
* not validating another form element when a single Ajax element is triggered.
diff --git a/modules/simpletest/tests/ajax_test.info b/modules/simpletest/tests/ajax_test.info
index d14a189..e42587d 100644
--- a/modules/simpletest/tests/ajax_test.info
+++ b/modules/simpletest/tests/ajax_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/batch_test.info b/modules/simpletest/tests/batch_test.info
index 32ac130..30702d6 100644
--- a/modules/simpletest/tests/batch_test.info
+++ b/modules/simpletest/tests/batch_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/bootstrap.test b/modules/simpletest/tests/bootstrap.test
index 5dcde32..ece1cd9 100644
--- a/modules/simpletest/tests/bootstrap.test
+++ b/modules/simpletest/tests/bootstrap.test
@@ -144,7 +144,7 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase {
$this->assertResponse(200, 'Conditional request without If-None-Match returned 200 OK.');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
- $this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DATE_RFC1123, strtotime($last_modified) + 1), 'If-None-Match: ' . $etag));
+ $this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DATE_RFC7231, strtotime($last_modified) + 1), 'If-None-Match: ' . $etag));
$this->assertResponse(200, 'Conditional request with new a If-Modified-Since date newer than Last-Modified returned 200 OK.');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
@@ -153,6 +153,8 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase {
$this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified, 'If-None-Match: ' . $etag));
$this->assertResponse(200, 'Conditional request returned 200 OK for authenticated user.');
$this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Absense of Page was not cached.');
+ $this->assertFalse($this->drupalGetHeader('ETag'), 'ETag HTTP headers are not present for logged in users.');
+ $this->assertFalse($this->drupalGetHeader('Last-Modified'), 'Last-Modified HTTP headers are not present for logged in users.');
}
/**
@@ -286,6 +288,35 @@ class BootstrapVariableTestCase extends DrupalWebTestCase {
}
+/**
+ * Tests the auto-loading behavior of the code registry.
+ */
+class BootstrapAutoloadTestCase extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Code registry',
+ 'description' => 'Test that the code registry functions correctly.',
+ 'group' => 'Bootstrap',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('drupal_autoload_test');
+ }
+
+ /**
+ * Tests that autoloader name matching is not case sensitive.
+ */
+ function testAutoloadCase() {
+ // Test interface autoloader.
+ $this->assertTrue(drupal_autoload_interface('drupalautoloadtestinterface'), 'drupal_autoload_interface() recognizes DrupalAutoloadTestInterface in lower case.');
+ // Test class autoloader.
+ $this->assertTrue(drupal_autoload_class('drupalautoloadtestclass'), 'drupal_autoload_class() recognizes DrupalAutoloadTestClass in lower case.');
+ }
+
+}
+
/**
* Test hook_boot() and hook_exit().
*/
@@ -546,3 +577,85 @@ class BootstrapOverrideServerVariablesTestCase extends DrupalUnitTestCase {
}
}
}
+
+/**
+ * Tests for $_GET['destination'] and $_REQUEST['destination'] validation.
+ */
+class BootstrapDestinationTestCase extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'URL destination validation',
+ 'description' => 'Test that $_GET[\'destination\'] and $_REQUEST[\'destination\'] cannot contain external URLs.',
+ 'group' => 'Bootstrap',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('system_test');
+ }
+
+ /**
+ * Tests that $_GET/$_REQUEST['destination'] only contain internal URLs.
+ *
+ * @see _drupal_bootstrap_variables()
+ * @see system_test_get_destination()
+ * @see system_test_request_destination()
+ */
+ public function testDestination() {
+ $test_cases = array(
+ array(
+ 'input' => 'node',
+ 'output' => 'node',
+ 'message' => "Standard internal example node path is present in the 'destination' parameter.",
+ ),
+ array(
+ 'input' => '/example.com',
+ 'output' => '/example.com',
+ 'message' => 'Internal path with one leading slash is allowed.',
+ ),
+ array(
+ 'input' => '//example.com/test',
+ 'output' => '',
+ 'message' => 'External URL without scheme is not allowed.',
+ ),
+ array(
+ 'input' => 'example:test',
+ 'output' => 'example:test',
+ 'message' => 'Internal URL using a colon is allowed.',
+ ),
+ array(
+ 'input' => 'http://example.com',
+ 'output' => '',
+ 'message' => 'External URL is not allowed.',
+ ),
+ array(
+ 'input' => 'javascript:alert(0)',
+ 'output' => 'javascript:alert(0)',
+ 'message' => 'Javascript URL is allowed because it is treated as an internal URL.',
+ ),
+ );
+ foreach ($test_cases as $test_case) {
+ // Test $_GET['destination'].
+ $this->drupalGet('system-test/get-destination', array('query' => array('destination' => $test_case['input'])));
+ $this->assertIdentical($test_case['output'], $this->drupalGetContent(), $test_case['message']);
+ // Test $_REQUEST['destination']. There's no form to submit to, so
+ // drupalPost() won't work here; this just tests a direct $_POST request
+ // instead.
+ $curl_parameters = array(
+ CURLOPT_URL => $this->getAbsoluteUrl('system-test/request-destination'),
+ CURLOPT_POST => TRUE,
+ CURLOPT_POSTFIELDS => 'destination=' . urlencode($test_case['input']),
+ CURLOPT_HTTPHEADER => array(),
+ );
+ $post_output = $this->curlExec($curl_parameters);
+ $this->assertIdentical($test_case['output'], $post_output, $test_case['message']);
+ }
+
+ // Make sure that 404 pages do not populate $_GET['destination'] with
+ // external URLs.
+ variable_set('site_404', 'system-test/get-destination');
+ $this->drupalGet('http://example.com', array('external' => FALSE));
+ $this->assertIdentical('', $this->drupalGetContent(), 'External URL is not allowed on 404 pages.');
+ }
+}
diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test
index f6e03b0..fcc9791 100644
--- a/modules/simpletest/tests/common.test
+++ b/modules/simpletest/tests/common.test
@@ -209,7 +209,16 @@ class CommonURLUnitTest extends DrupalWebTestCase {
// Test that drupal can recognize an absolute URL. Used to prevent attack vectors.
$this->assertTrue(url_is_external($url), 'Correctly identified an external URL.');
+ // External URL without an explicit protocol.
+ $url = '//drupal.org/foo/bar?foo=bar&bar=baz&baz#foo';
+ $this->assertTrue(url_is_external($url), 'Correctly identified an external URL without a protocol part.');
+
+ // Internal URL starting with a slash.
+ $url = '/drupal.org';
+ $this->assertFalse(url_is_external($url), 'Correctly identified an internal URL with a leading slash.');
+
// Test the parsing of absolute URLs.
+ $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo';
$result = array(
'path' => 'http://drupal.org/foo/bar',
'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''),
@@ -349,6 +358,17 @@ class CommonURLUnitTest extends DrupalWebTestCase {
$query = array($this->randomName(5) => $this->randomName(5));
$result = url($url, array('query' => $query));
$this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result, 'External URL query string can be extended with a custom query string in $options.');
+
+ // Verify that an internal URL does not result in an external URL without
+ // protocol part.
+ $url = '/drupal.org';
+ $result = url($url);
+ $this->assertTrue(strpos($result, '//') === FALSE, 'Internal URL does not turn into an external URL.');
+
+ // Verify that an external URL without protocol part is recognized as such.
+ $url = '//drupal.org';
+ $result = url($url);
+ $this->assertEqual($url, $result, 'External URL without protocol is not altered.');
}
}
@@ -661,6 +681,10 @@ class CascadingStylesheetsTestCase extends DrupalWebTestCase {
drupal_add_css($css);
$styles = drupal_get_css();
$this->assertTrue(strpos($styles, $css) > 0, 'Rendered CSS includes the added stylesheet.');
+ // Verify that newlines are properly added inside style tags.
+ $query_string = variable_get('css_js_query_string', '0');
+ $css_processed = "";
+ $this->assertEqual(trim($styles), $css_processed, 'Rendered CSS includes newlines inside style tags for JavaScript use.');
}
/**
@@ -914,9 +938,11 @@ class CascadingStylesheetsUnitTest extends DrupalUnitTestCase {
* Tests basic CSS loading with and without optimization via drupal_load_stylesheet().
*
* Known tests:
- * - Retain white-space in selectors. (http://drupal.org/node/472820)
- * - Proper URLs in imported files. (http://drupal.org/node/265719)
- * - Retain pseudo-selectors. (http://drupal.org/node/460448)
+ * - Retain white-space in selectors. (https://drupal.org/node/472820)
+ * - Proper URLs in imported files. (https://drupal.org/node/265719)
+ * - Retain pseudo-selectors. (https://drupal.org/node/460448)
+ * - Don't adjust data URIs. (https://drupal.org/node/2142441)
+ * - Files imported from external URLs. (https://drupal.org/node/2014851)
*/
function testLoadCssBasic() {
// Array of files to test living in 'simpletest/files/css_test_files/'.
@@ -1082,6 +1108,74 @@ class DrupalHTTPRequestTestCase extends DrupalWebTestCase {
}
}
+/**
+ * Tests parsing of the HTTP response status line.
+ */
+class DrupalHTTPResponseStatusLineTest extends DrupalUnitTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Drupal HTTP request response status parsing',
+ 'description' => 'Perform unit tests on _drupal_parse_response_status().',
+ 'group' => 'System',
+ );
+ }
+
+ /**
+ * Tests parsing HTTP response status line.
+ */
+ public function testStatusLine() {
+ // Grab the big array of test data from statusLineData().
+ $data = $this->statusLineData();
+ foreach($data as $test_case) {
+ $test_data = array_shift($test_case);
+ $expected = array_shift($test_case);
+
+ $outcome = _drupal_parse_response_status($test_data);
+
+ foreach(array_keys($expected) as $key) {
+ $this->assertIdentical($outcome[$key], $expected[$key]);
+ }
+ }
+ }
+
+ /**
+ * Data provider for testStatusLine().
+ *
+ * @return array
+ * Test data.
+ */
+ protected function statusLineData() {
+ return array(
+ array(
+ 'HTTP/1.1 200 OK',
+ array(
+ 'http_version' => 'HTTP/1.1',
+ 'response_code' => '200',
+ 'reason_phrase' => 'OK',
+ ),
+ ),
+ // Data set with no reason phrase.
+ array(
+ 'HTTP/1.1 200',
+ array(
+ 'http_version' => 'HTTP/1.1',
+ 'response_code' => '200',
+ 'reason_phrase' => '',
+ ),
+ ),
+ // Arbitrary strings.
+ array(
+ 'version code multi word explanation',
+ array(
+ 'http_version' => 'version',
+ 'response_code' => 'code',
+ 'reason_phrase' => 'multi word explanation',
+ ),
+ ),
+ );
+ }
+}
+
/**
* Testing drupal_add_region_content and drupal_get_region_content.
*/
@@ -1347,6 +1441,127 @@ class JavaScriptTestCase extends DrupalWebTestCase {
$this->assertTrue(strpos($javascript, $inline) > 0, 'Rendered JavaScript footer returns the inline code.');
}
+ /**
+ * Test the 'javascript_always_use_jquery' variable.
+ */
+ function testJavaScriptAlwaysUseJQuery() {
+ // The default front page of the site should use jQuery and other standard
+ // scripts and settings.
+ $this->drupalGet('');
+ $this->assertRaw('misc/jquery.js', 'Default behavior: The front page of the site includes jquery.js.');
+ $this->assertRaw('misc/drupal.js', 'Default behavior: The front page of the site includes drupal.js.');
+ $this->assertRaw('Drupal.settings', 'Default behavior: The front page of the site includes Drupal settings.');
+ $this->assertRaw('basePath', 'Default behavior: The front page of the site includes the basePath Drupal setting.');
+
+ // The default front page should not use jQuery and other standard scripts
+ // and settings when the 'javascript_always_use_jquery' variable is set to
+ // FALSE.
+ variable_set('javascript_always_use_jquery', FALSE);
+ $this->drupalGet('');
+ $this->assertNoRaw('misc/jquery.js', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include jquery.js.');
+ $this->assertNoRaw('misc/drupal.js', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include drupal.js.');
+ $this->assertNoRaw('Drupal.settings', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include Drupal settings.');
+ $this->assertNoRaw('basePath', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include the basePath Drupal setting.');
+ variable_del('javascript_always_use_jquery');
+
+ // When only settings have been added via drupal_add_js(), drupal_get_js()
+ // should still return jQuery and other standard scripts and settings.
+ $this->resetStaticVariables();
+ drupal_add_js(array('testJavaScriptSetting' => 'test'), 'setting');
+ $javascript = drupal_get_js();
+ $this->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes jquery.js.');
+ $this->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes drupal.js.');
+ $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes Drupal.settings.');
+ $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes the basePath Drupal setting.');
+ $this->assertTrue(strpos($javascript, 'testJavaScriptSetting') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes the added Drupal settings.');
+
+ // When only settings have been added via drupal_add_js() and the
+ // 'javascript_always_use_jquery' variable is set to FALSE, drupal_get_js()
+ // should not return jQuery and other standard scripts and settings, nor
+ // should it return the requested settings (since they cannot actually be
+ // addded to the page without jQuery).
+ $this->resetStaticVariables();
+ variable_set('javascript_always_use_jquery', FALSE);
+ drupal_add_js(array('testJavaScriptSetting' => 'test'), 'setting');
+ $javascript = drupal_get_js();
+ $this->assertTrue(strpos($javascript, 'misc/jquery.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include jquery.js.');
+ $this->assertTrue(strpos($javascript, 'misc/drupal.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include drupal.js.');
+ $this->assertTrue(strpos($javascript, 'Drupal.settings') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include Drupal.settings.');
+ $this->assertTrue(strpos($javascript, 'basePath') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include the basePath Drupal setting.');
+ $this->assertTrue(strpos($javascript, 'testJavaScriptSetting') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include the added Drupal settings.');
+ variable_del('javascript_always_use_jquery');
+
+ // When a regular file has been added via drupal_add_js(), drupal_get_js()
+ // should return jQuery and other standard scripts and settings.
+ $this->resetStaticVariables();
+ drupal_add_js('misc/collapse.js');
+ $javascript = drupal_get_js();
+ $this->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes jquery.js.');
+ $this->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes drupal.js.');
+ $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes Drupal.settings.');
+ $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the basePath Drupal setting.');
+ $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the custom file.');
+
+ // When a regular file has been added via drupal_add_js() and the
+ // 'javascript_always_use_jquery' variable is set to FALSE, drupal_get_js()
+ // should still return jQuery and other standard scripts and settings
+ // (since the file is assumed to require jQuery by default).
+ $this->resetStaticVariables();
+ variable_set('javascript_always_use_jquery', FALSE);
+ drupal_add_js('misc/collapse.js');
+ $javascript = drupal_get_js();
+ $this->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes jquery.js.');
+ $this->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes drupal.js.');
+ $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes Drupal.settings.');
+ $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the basePath Drupal setting.');
+ $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the custom file.');
+ variable_del('javascript_always_use_jquery');
+
+ // When a file that does not require jQuery has been added via
+ // drupal_add_js(), drupal_get_js() should still return jQuery and other
+ // standard scripts and settings by default.
+ $this->resetStaticVariables();
+ drupal_add_js('misc/collapse.js', array('requires_jquery' => FALSE));
+ $javascript = drupal_get_js();
+ $this->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes jquery.js.');
+ $this->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes drupal.js.');
+ $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes Drupal.settings.');
+ $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes the basePath Drupal setting.');
+ $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes the custom file.');
+
+ // When a file that does not require jQuery has been added via
+ // drupal_add_js() and the 'javascript_always_use_jquery' variable is set
+ // to FALSE, drupal_get_js() should not return jQuery and other standard
+ // scripts and setting, but it should still return the requested file.
+ $this->resetStaticVariables();
+ variable_set('javascript_always_use_jquery', FALSE);
+ drupal_add_js('misc/collapse.js', array('requires_jquery' => FALSE));
+ $javascript = drupal_get_js();
+ $this->assertTrue(strpos($javascript, 'misc/jquery.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include jquery.js.');
+ $this->assertTrue(strpos($javascript, 'misc/drupal.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include drupal.js.');
+ $this->assertTrue(strpos($javascript, 'Drupal.settings') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include Drupal.settings.');
+ $this->assertTrue(strpos($javascript, 'basePath') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include the basePath Drupal setting.');
+ $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes the custom file.');
+ variable_del('javascript_always_use_jquery');
+
+ // When 'javascript_always_use_jquery' is set to FALSE and a file that does
+ // not require jQuery is added, followed by one that does, drupal_get_js()
+ // should return jQuery and other standard scripts and settings, in
+ // addition to both of the requested files.
+ $this->resetStaticVariables();
+ variable_set('javascript_always_use_jquery', FALSE);
+ drupal_add_js('misc/collapse.js', array('requires_jquery' => FALSE));
+ drupal_add_js('misc/ajax.js');
+ $javascript = drupal_get_js();
+ $this->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes jquery.js.');
+ $this->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes drupal.js.');
+ $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes Drupal.settings.');
+ $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes the basePath Drupal setting.');
+ $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes the first custom file.');
+ $this->assertTrue(strpos($javascript, 'misc/ajax.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes the second custom file.');
+ variable_del('javascript_always_use_jquery');
+ }
+
/**
* Test drupal_add_js() sets preproccess to false when cache is set to false.
*/
@@ -1575,6 +1790,15 @@ class JavaScriptTestCase extends DrupalWebTestCase {
$query_string = variable_get('css_js_query_string', '0');
$this->assertRaw(drupal_get_path('module', 'node') . '/node.js?' . $query_string, 'Query string was appended correctly to js.');
}
+
+ /**
+ * Resets static variables related to adding JavaScript to a page.
+ */
+ function resetStaticVariables() {
+ drupal_static_reset('drupal_add_js');
+ drupal_static_reset('drupal_add_library');
+ drupal_static_reset('drupal_get_library');
+ }
}
/**
diff --git a/modules/simpletest/tests/common_test.info b/modules/simpletest/tests/common_test.info
index 527234b..4d57307 100644
--- a/modules/simpletest/tests/common_test.info
+++ b/modules/simpletest/tests/common_test.info
@@ -7,8 +7,8 @@ stylesheets[all][] = common_test.css
stylesheets[print][] = common_test.print.css
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/common_test_cron_helper.info b/modules/simpletest/tests/common_test_cron_helper.info
index 44b084c..a3b3ae4 100644
--- a/modules/simpletest/tests/common_test_cron_helper.info
+++ b/modules/simpletest/tests/common_test_cron_helper.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/database_test.info b/modules/simpletest/tests/database_test.info
index 526c3cf..9269455 100644
--- a/modules/simpletest/tests/database_test.info
+++ b/modules/simpletest/tests/database_test.info
@@ -5,8 +5,8 @@ package = Testing
version = VERSION
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/database_test.test b/modules/simpletest/tests/database_test.test
index 209bf68..9c533be 100644
--- a/modules/simpletest/tests/database_test.test
+++ b/modules/simpletest/tests/database_test.test
@@ -238,7 +238,7 @@ class DatabaseConnectionTestCase extends DatabaseTestCase {
// Open the default target so we have an object to compare.
$db1 = Database::getConnection('default', 'default');
- // Try to close the the default connection, then open a new one.
+ // Try to close the default connection, then open a new one.
Database::closeConnection('default', 'default');
$db2 = Database::getConnection('default', 'default');
@@ -1947,6 +1947,15 @@ class DatabaseSelectOrderedTestCase extends DatabaseTestCase {
$this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
}
+
+ /**
+ * Tests that the sort direction is sanitized properly.
+ */
+ function testOrderByEscaping() {
+ $query = db_select('test')->orderBy('name', 'invalid direction');
+ $order_bys = $query->getOrderBy();
+ $this->assertEqual($order_bys['name'], 'ASC', 'Invalid order by direction is converted to ASC.');
+ }
}
/**
@@ -3391,7 +3400,7 @@ class DatabaseQueryTestCase extends DatabaseTestCase {
public function testArrayArgumentsSQLInjection() {
// Attempt SQL injection and verify that it does not work.
$condition = array(
- "1 ;INSERT INTO {test} SET name = 'test12345678'; -- " => '',
+ "1 ;INSERT INTO {test} (name) VALUES ('test12345678'); -- " => '',
'1' => '',
);
try {
@@ -3445,12 +3454,14 @@ class DatabaseTransactionTestCase extends DatabaseTestCase {
}
/**
- * Helper method for transaction unit test. This "outer layer" transaction
- * starts and then encapsulates the "inner layer" transaction. This nesting
- * is used to evaluate whether the the database transaction API properly
- * supports nesting. By "properly supports," we mean the outer transaction
- * continues to exist regardless of what functions are called and whether
- * those functions start their own transactions.
+ * Helper method for transaction unit test.
+ *
+ * This "outer layer" transaction starts and then encapsulates the
+ * "inner layer" transaction. This nesting is used to evaluate whether the
+ * database transaction API properly supports nesting. By "properly supports,"
+ * we mean the outer transaction continues to exist regardless of what
+ * functions are called and whether those functions start their own
+ * transactions.
*
* In contrast, a typical database would commit the outer transaction, start
* a new transaction for the inner layer, commit the inner layer transaction,
diff --git a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info
new file mode 100644
index 0000000..1321b93
--- /dev/null
+++ b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info
@@ -0,0 +1,14 @@
+name = "Drupal code registry test"
+description = "Support module for testing the code registry."
+files[] = drupal_autoload_test_interface.inc
+files[] = drupal_autoload_test_class.inc
+package = Testing
+version = VERSION
+core = 7.x
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
+project = "drupal"
+datestamp = "1430973154"
+
diff --git a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.module b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.module
new file mode 100644
index 0000000..37aa94e
--- /dev/null
+++ b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.module
@@ -0,0 +1,6 @@
+filesize = 999999;
- $errors = file_validate_size($file, 1, 1);
- $this->assertEqual(count($errors), 0, 'No size limits enforced on uid=1.', 'File');
-
- // Run these tests as a regular user.
- $user = $this->drupalCreateUser();
-
// Create a file with a size of 1000 bytes, and quotas of only 1 byte.
$file = new stdClass();
$file->filesize = 1000;
@@ -506,9 +491,6 @@ class FileValidatorTest extends DrupalWebTestCase {
$this->assertEqual(count($errors), 1, 'Error for the user being over their limit.', 'File');
$errors = file_validate_size($file, 1, 1);
$this->assertEqual(count($errors), 2, 'Errors for both the file and their limit.', 'File');
-
- $user = $original_user;
- drupal_save_session(TRUE);
}
}
@@ -2564,6 +2546,7 @@ class FileNameMungingTest extends FileTestCase {
parent::setUp();
$this->bad_extension = 'php';
$this->name = $this->randomName() . '.' . $this->bad_extension . '.txt';
+ $this->name_with_uc_ext = $this->randomName() . '.' . strtoupper($this->bad_extension) . '.txt';
}
/**
@@ -2601,9 +2584,13 @@ class FileNameMungingTest extends FileTestCase {
* White listed extensions are ignored by file_munge_filename().
*/
function testMungeIgnoreWhitelisted() {
- // Declare our extension as whitelisted.
- $munged_name = file_munge_filename($this->name, $this->bad_extension);
- $this->assertIdentical($munged_name, $this->name, format_string('The new filename (%munged) matches the original (%original) once the extension has been whitelisted.', array('%munged' => $munged_name, '%original' => $this->name)));
+ // Declare our extension as whitelisted. The declared extensions should
+ // be case insensitive so test using one with a different case.
+ $munged_name = file_munge_filename($this->name_with_uc_ext, $this->bad_extension);
+ $this->assertIdentical($munged_name, $this->name_with_uc_ext, format_string('The new filename (%munged) matches the original (%original) once the extension has been whitelisted.', array('%munged' => $munged_name, '%original' => $this->name_with_uc_ext)));
+ // The allowed extensions should also be normalized.
+ $munged_name = file_munge_filename($this->name, strtoupper($this->bad_extension));
+ $this->assertIdentical($munged_name, $this->name, format_string('The new filename (%munged) matches the original (%original) also when the whitelisted extension is in uppercase.', array('%munged' => $munged_name, '%original' => $this->name)));
}
/**
diff --git a/modules/simpletest/tests/file_test.info b/modules/simpletest/tests/file_test.info
index 2e0f4bd..4f907b4 100644
--- a/modules/simpletest/tests/file_test.info
+++ b/modules/simpletest/tests/file_test.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = file_test.module
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/filter_test.info b/modules/simpletest/tests/filter_test.info
index 6cdc7dc..672d2d3 100644
--- a/modules/simpletest/tests/filter_test.info
+++ b/modules/simpletest/tests/filter_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/form.test b/modules/simpletest/tests/form.test
index f90b854..0bf6c8c 100644
--- a/modules/simpletest/tests/form.test
+++ b/modules/simpletest/tests/form.test
@@ -470,6 +470,64 @@ class FormsTestCase extends DrupalWebTestCase {
$this->drupalPost(NULL, array('checkboxes[one]' => TRUE, 'checkboxes[two]' => TRUE), t('Submit'));
$this->assertText('An illegal choice has been detected.', 'Input forgery was detected.');
}
+
+ /**
+ * Tests that submitted values are converted to scalar strings for textfields.
+ */
+ public function testTextfieldStringValue() {
+ // Check multivalued submissions.
+ $multivalue = array('evil' => 'multivalue', 'not so' => 'good');
+ $this->checkFormValue('textfield', $multivalue, '');
+ $this->checkFormValue('password', $multivalue, '');
+ $this->checkFormValue('textarea', $multivalue, '');
+ $this->checkFormValue('machine_name', $multivalue, '');
+ $this->checkFormValue('password_confirm', $multivalue, array('pass1' => '', 'pass2' => ''));
+ // Check integer submissions.
+ $integer = 5;
+ $string = '5';
+ $this->checkFormValue('textfield', $integer, $string);
+ $this->checkFormValue('password', $integer, $string);
+ $this->checkFormValue('textarea', $integer, $string);
+ $this->checkFormValue('machine_name', $integer, $string);
+ $this->checkFormValue('password_confirm', array('pass1' => $integer, 'pass2' => $integer), array('pass1' => $string, 'pass2' => $string));
+ // Check that invalid array keys are ignored for password confirm elements.
+ $this->checkFormValue('password_confirm', array('pass1' => 'test', 'pass2' => 'test', 'extra' => 'invalid'), array('pass1' => 'test', 'pass2' => 'test'));
+ }
+
+ /**
+ * Checks that a given form input value is sanitized to the expected result.
+ *
+ * @param string $element_type
+ * The form element type. Example: textfield.
+ * @param mixed $input_value
+ * The submitted user input value for the form element.
+ * @param mixed $expected_value
+ * The sanitized result value in the form state after calling
+ * form_builder().
+ */
+ protected function checkFormValue($element_type, $input_value, $expected_value) {
+ $form_id = $this->randomName();
+ $form = array();
+ $form_state = form_state_defaults();
+ $form['op'] = array('#type' => 'submit', '#value' => t('Submit'));
+ $form[$element_type] = array(
+ '#type' => $element_type,
+ '#title' => 'test',
+ );
+
+ $form_state['input'][$element_type] = $input_value;
+ $form_state['input']['form_id'] = $form_id;
+ $form_state['method'] = 'post';
+ $form_state['values'] = array();
+ drupal_prepare_form($form_id, $form, $form_state);
+
+ // This is the main function we want to test: it is responsible for
+ // populating user supplied $form_state['input'] to sanitized
+ // $form_state['values'].
+ form_builder($form_id, $form, $form_state);
+
+ $this->assertIdentical($form_state['values'][$element_type], $expected_value, format_string('Form submission for the "@element_type" element type has been correctly sanitized.', array('@element_type' => $element_type)));
+ }
}
/**
diff --git a/modules/simpletest/tests/form_test.info b/modules/simpletest/tests/form_test.info
index 8e75698..da16bf7 100644
--- a/modules/simpletest/tests/form_test.info
+++ b/modules/simpletest/tests/form_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/image.test b/modules/simpletest/tests/image.test
index dc95a6e..8497022 100644
--- a/modules/simpletest/tests/image.test
+++ b/modules/simpletest/tests/image.test
@@ -261,6 +261,7 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase {
*/
function testManipulations() {
// If GD isn't available don't bother testing this.
+ module_load_include('inc', 'system', 'image.gd');
if (!function_exists('image_gd_check_settings') || !image_gd_check_settings()) {
$this->pass(t('Image manipulations for the GD toolkit were skipped because the GD toolkit is not available.'));
return;
@@ -379,7 +380,7 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase {
array_fill(0, 3, 76) + array(3 => 0),
array_fill(0, 3, 149) + array(3 => 0),
array_fill(0, 3, 29) + array(3 => 0),
- array_fill(0, 3, 0) + array(3 => 127)
+ array_fill(0, 3, 225) + array(3 => 127)
),
),
);
@@ -394,11 +395,14 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase {
continue 2;
}
- // Transparent GIFs and the imagefilter function don't work together.
- // There is a todo in image.gd.inc to correct this.
+ // All images should be converted to truecolor when loaded.
+ $image_truecolor = imageistruecolor($image->resource);
+ $this->assertTrue($image_truecolor, format_string('Image %file after load is a truecolor image.', array('%file' => $file)));
+
if ($image->info['extension'] == 'gif') {
if ($op == 'desaturate') {
- $values['corners'][3] = $this->white;
+ // Transparent GIFs and the imagefilter function don't work together.
+ $values['corners'][3][3] = 0;
}
}
@@ -451,7 +455,8 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase {
$directory = file_default_scheme() . '://imagetests';
file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
- image_save($image, $directory . '/' . $op . '.' . $image->info['extension']);
+ $file_path = $directory . '/' . $op . '.' . $image->info['extension'];
+ image_save($image, $file_path);
$this->assertTrue($correct_dimensions_real, format_string('Image %file after %action action has proper dimensions.', array('%file' => $file, '%action' => $op)));
$this->assertTrue($correct_dimensions_object, format_string('Image %file object after %action action is reporting the proper height and width values.', array('%file' => $file, '%action' => $op)));
@@ -460,8 +465,37 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase {
$this->assertTrue($correct_colors, format_string('Image %file object after %action action has the correct color placement.', array('%file' => $file, '%action' => $op)));
}
}
+
+ // Check that saved image reloads without raising PHP errors.
+ $image_reloaded = image_load($file_path);
}
+ }
+ /**
+ * Tests loading an image whose transparent color index is out of range.
+ */
+ function testTransparentColorOutOfRange() {
+ // This image was generated by taking an initial image with a palette size
+ // of 6 colors, and setting the transparent color index to 6 (one higher
+ // than the largest allowed index), as follows:
+ // @code
+ // $image = imagecreatefromgif('modules/simpletest/files/image-test.gif');
+ // imagecolortransparent($image, 6);
+ // imagegif($image, 'modules/simpletest/files/image-test-transparent-out-of-range.gif');
+ // @endcode
+ // This allows us to test that an image with an out-of-range color index
+ // can be loaded correctly.
+ $file = 'image-test-transparent-out-of-range.gif';
+ $image = image_load(drupal_get_path('module', 'simpletest') . '/files/' . $file);
+
+ if (!$image) {
+ $this->fail(format_string('Could not load image %file.', array('%file' => $file)));
+ }
+ else {
+ // All images should be converted to truecolor when loaded.
+ $image_truecolor = imageistruecolor($image->resource);
+ $this->assertTrue($image_truecolor, format_string('Image %file after load is a truecolor image.', array('%file' => $file)));
+ }
}
}
diff --git a/modules/simpletest/tests/image_test.info b/modules/simpletest/tests/image_test.info
index eb49c08..e3293f7 100644
--- a/modules/simpletest/tests/image_test.info
+++ b/modules/simpletest/tests/image_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/menu_test.info b/modules/simpletest/tests/menu_test.info
index 3fd7234..6dc2504 100644
--- a/modules/simpletest/tests/menu_test.info
+++ b/modules/simpletest/tests/menu_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/module_test.info b/modules/simpletest/tests/module_test.info
index 7caa52d..737b3e4 100644
--- a/modules/simpletest/tests/module_test.info
+++ b/modules/simpletest/tests/module_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/password.test b/modules/simpletest/tests/password.test
index 5259d19..7105f3b 100644
--- a/modules/simpletest/tests/password.test
+++ b/modules/simpletest/tests/password.test
@@ -57,4 +57,25 @@ class PasswordHashingTest extends DrupalWebTestCase {
$this->assertFalse(user_needs_new_hash($account), 'Re-hashed password does not need a new hash.');
$this->assertTrue(user_check_password($password, $account), 'Password check succeeds with re-hashed password.');
}
+
+ /**
+ * Verifies that passwords longer than 512 bytes are not hashed.
+ */
+ public function testLongPassword() {
+ $password = str_repeat('x', 512);
+ $result = user_hash_password($password);
+ $this->assertFalse(empty($result), '512 byte long password is allowed.');
+ $password = str_repeat('x', 513);
+ $result = user_hash_password($password);
+ $this->assertFalse($result, '513 byte long password is not allowed.');
+ // Check a string of 3-byte UTF-8 characters.
+ $password = str_repeat('€', 170);
+ $result = user_hash_password($password);
+ $this->assertFalse(empty($result), '510 byte long password is allowed.');
+ $password .= 'xx';
+ $this->assertFalse(empty($result), '512 byte long password is allowed.');
+ $password = str_repeat('€', 171);
+ $result = user_hash_password($password);
+ $this->assertFalse($result, '513 byte long password is not allowed.');
+ }
}
diff --git a/modules/simpletest/tests/path_test.info b/modules/simpletest/tests/path_test.info
index 05f1063..1d5df8d 100644
--- a/modules/simpletest/tests/path_test.info
+++ b/modules/simpletest/tests/path_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/psr_0_test/psr_0_test.info b/modules/simpletest/tests/psr_0_test/psr_0_test.info
index 648bf9d..1ee5529 100644
--- a/modules/simpletest/tests/psr_0_test/psr_0_test.info
+++ b/modules/simpletest/tests/psr_0_test/psr_0_test.info
@@ -5,8 +5,8 @@ core = 7.x
hidden = TRUE
package = Testing
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/psr_4_test/psr_4_test.info b/modules/simpletest/tests/psr_4_test/psr_4_test.info
new file mode 100644
index 0000000..f14c3e4
--- /dev/null
+++ b/modules/simpletest/tests/psr_4_test/psr_4_test.info
@@ -0,0 +1,12 @@
+name = PSR-4 Test cases
+description = Test classes to be discovered by simpletest.
+core = 7.x
+
+hidden = TRUE
+package = Testing
+
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
+project = "drupal"
+datestamp = "1430973154"
+
diff --git a/modules/simpletest/tests/psr_4_test/psr_4_test.module b/modules/simpletest/tests/psr_4_test/psr_4_test.module
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++ b/modules/simpletest/tests/psr_4_test/psr_4_test.module
@@ -0,0 +1 @@
+ 'PSR4 example test: PSR-4 in disabled modules.',
+ 'description' => 'We want to assert that this test case is being discovered.',
+ 'group' => 'SimpleTest',
+ );
+ }
+
+ function testArithmetics() {
+ $this->assert(1 + 1 == 2, '1 + 1 == 2');
+ }
+}
diff --git a/modules/simpletest/tests/psr_4_test/src/Tests/Nested/NestedExampleTest.php b/modules/simpletest/tests/psr_4_test/src/Tests/Nested/NestedExampleTest.php
new file mode 100644
index 0000000..ff3ac29
--- /dev/null
+++ b/modules/simpletest/tests/psr_4_test/src/Tests/Nested/NestedExampleTest.php
@@ -0,0 +1,18 @@
+ 'PSR4 example test: PSR-4 in nested subfolders.',
+ 'description' => 'We want to assert that this PSR-4 test case is being discovered.',
+ 'group' => 'SimpleTest',
+ );
+ }
+
+ function testArithmetics() {
+ $this->assert(1 + 1 == 2, '1 + 1 == 2');
+ }
+}
diff --git a/modules/simpletest/tests/requirements1_test.info b/modules/simpletest/tests/requirements1_test.info
index 6a8c4eb..fccce93 100644
--- a/modules/simpletest/tests/requirements1_test.info
+++ b/modules/simpletest/tests/requirements1_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/requirements2_test.info b/modules/simpletest/tests/requirements2_test.info
index 3a40b0e..da32076 100644
--- a/modules/simpletest/tests/requirements2_test.info
+++ b/modules/simpletest/tests/requirements2_test.info
@@ -7,8 +7,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/session.test b/modules/simpletest/tests/session.test
index 097503b..893d03e 100644
--- a/modules/simpletest/tests/session.test
+++ b/modules/simpletest/tests/session.test
@@ -477,6 +477,56 @@ class SessionHttpsTestCase extends DrupalWebTestCase {
$this->assertResponse(200);
}
+ /**
+ * Tests that empty session IDs do not cause unrelated sessions to load.
+ */
+ public function testEmptySessionId() {
+ global $is_https;
+
+ if ($is_https) {
+ $secure_session_name = session_name();
+ }
+ else {
+ $secure_session_name = 'S' . session_name();
+ }
+
+ // Enable mixed mode for HTTP and HTTPS.
+ variable_set('https', TRUE);
+
+ $admin_user = $this->drupalCreateUser(array('access administration pages'));
+ $standard_user = $this->drupalCreateUser(array('access content'));
+
+ // First log in as the admin user on HTTP.
+ // We cannot use $this->drupalLogin() here because we need to use the
+ // special http.php URLs.
+ $edit = array(
+ 'name' => $admin_user->name,
+ 'pass' => $admin_user->pass_raw
+ );
+ $this->drupalGet('user');
+ $form = $this->xpath('//form[@id="user-login"]');
+ $form[0]['action'] = $this->httpUrl('user');
+ $this->drupalPost(NULL, $edit, t('Log in'));
+
+ $this->curlClose();
+
+ // Now start a session for the standard user on HTTPS.
+ $edit = array(
+ 'name' => $standard_user->name,
+ 'pass' => $standard_user->pass_raw
+ );
+ $this->drupalGet('user');
+ $form = $this->xpath('//form[@id="user-login"]');
+ $form[0]['action'] = $this->httpsUrl('user');
+ $this->drupalPost(NULL, $edit, t('Log in'));
+
+ // Make the secure session cookie blank.
+ curl_setopt($this->curlHandle, CURLOPT_COOKIE, "$secure_session_name=");
+ $this->drupalGet($this->httpsUrl('user'));
+ $this->assertNoText($admin_user->name, 'User is not logged in as admin');
+ $this->assertNoText($standard_user->name, "The user's own name is not displayed because the invalid session cookie has logged them out.");
+ }
+
/**
* Test that there exists a session with two specific session IDs.
*
diff --git a/modules/simpletest/tests/session_test.info b/modules/simpletest/tests/session_test.info
index eeb5984..9c3d0ad 100644
--- a/modules/simpletest/tests/session_test.info
+++ b/modules/simpletest/tests/session_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/system_dependencies_test.info b/modules/simpletest/tests/system_dependencies_test.info
index 7cf67e6..f08622e 100644
--- a/modules/simpletest/tests/system_dependencies_test.info
+++ b/modules/simpletest/tests/system_dependencies_test.info
@@ -6,8 +6,8 @@ core = 7.x
hidden = TRUE
dependencies[] = _missing_dependency
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info b/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info
index 883e667..72207ce 100644
--- a/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info
+++ b/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info
@@ -6,8 +6,8 @@ core = 7.x
hidden = TRUE
dependencies[] = system_incompatible_core_version_test
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/system_incompatible_core_version_test.info b/modules/simpletest/tests/system_incompatible_core_version_test.info
index 86b0f0f..a920804 100644
--- a/modules/simpletest/tests/system_incompatible_core_version_test.info
+++ b/modules/simpletest/tests/system_incompatible_core_version_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 5.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info b/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info
index f1d577a..4e37bfa 100644
--- a/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info
+++ b/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info
@@ -7,8 +7,8 @@ hidden = TRUE
; system_incompatible_module_version_test declares version 1.0
dependencies[] = system_incompatible_module_version_test (>2.0)
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/system_incompatible_module_version_test.info b/modules/simpletest/tests/system_incompatible_module_version_test.info
index 049ffba..a9d17fe 100644
--- a/modules/simpletest/tests/system_incompatible_module_version_test.info
+++ b/modules/simpletest/tests/system_incompatible_module_version_test.info
@@ -5,8 +5,8 @@ version = 1.0
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/system_test.info b/modules/simpletest/tests/system_test.info
index d4473ac..8fc85e3 100644
--- a/modules/simpletest/tests/system_test.info
+++ b/modules/simpletest/tests/system_test.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = system_test.module
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/system_test.module b/modules/simpletest/tests/system_test.module
index 2eda351..8c44329 100644
--- a/modules/simpletest/tests/system_test.module
+++ b/modules/simpletest/tests/system_test.module
@@ -78,6 +78,13 @@ function system_test_menu() {
'type' => MENU_CALLBACK,
);
+ $items['system-test/drupal-set-message'] = array(
+ 'title' => 'Set messages with drupal_set_message()',
+ 'page callback' => 'system_test_drupal_set_message',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+
$items['system-test/main-content-handling'] = array(
'title' => 'Test main content handling',
'page callback' => 'system_test_main_content_fallback',
@@ -106,6 +113,20 @@ function system_test_menu() {
'type' => MENU_CALLBACK,
);
+ $items['system-test/get-destination'] = array(
+ 'title' => 'Test $_GET[\'destination\']',
+ 'page callback' => 'system_test_get_destination',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+
+ $items['system-test/request-destination'] = array(
+ 'title' => 'Test $_REQUEST[\'destination\']',
+ 'page callback' => 'system_test_request_destination',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+
return $items;
}
@@ -420,3 +441,41 @@ function system_test_authorize_init_page($page_title) {
system_authorized_init('system_test_authorize_run', drupal_get_path('module', 'system_test') . '/system_test.module', array(), $page_title);
drupal_goto($authorize_url);
}
+
+/**
+ * Sets two messages and removes the first one before the messages are displayed.
+ */
+function system_test_drupal_set_message() {
+ // Set two messages.
+ drupal_set_message('First message (removed).');
+ drupal_set_message('Second message (not removed).');
+
+ // Remove the first.
+ unset($_SESSION['messages']['status'][0]);
+
+ return '';
+}
+
+/**
+ * Page callback to print out $_GET['destination'] for testing.
+ */
+function system_test_get_destination() {
+ if (isset($_GET['destination'])) {
+ print $_GET['destination'];
+ }
+ // No need to render the whole page, we are just interested in this bit of
+ // information.
+ exit;
+}
+
+/**
+ * Page callback to print out $_REQUEST['destination'] for testing.
+ */
+function system_test_request_destination() {
+ if (isset($_REQUEST['destination'])) {
+ print $_REQUEST['destination'];
+ }
+ // No need to render the whole page, we are just interested in this bit of
+ // information.
+ exit;
+}
diff --git a/modules/simpletest/tests/taxonomy_test.info b/modules/simpletest/tests/taxonomy_test.info
index 7961cc2..419b6e5 100644
--- a/modules/simpletest/tests/taxonomy_test.info
+++ b/modules/simpletest/tests/taxonomy_test.info
@@ -6,8 +6,8 @@ core = 7.x
hidden = TRUE
dependencies[] = taxonomy
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/theme.test b/modules/simpletest/tests/theme.test
index 519a7a9..f5ddfa9 100644
--- a/modules/simpletest/tests/theme.test
+++ b/modules/simpletest/tests/theme.test
@@ -155,6 +155,15 @@ class ThemeTestCase extends DrupalWebTestCase {
$this->assertNotEqual(theme_get_setting('subtheme_override', 'test_basetheme'), theme_get_setting('subtheme_override', 'test_subtheme'), 'Base theme\'s default settings values can be overridden by subtheme.');
$this->assertIdentical(theme_get_setting('basetheme_only', 'test_subtheme'), 'base theme value', 'Base theme\'s default settings values are inherited by subtheme.');
}
+
+ /**
+ * Test the drupal_add_region_content() function.
+ */
+ function testDrupalAddRegionContent() {
+ $this->drupalGet('theme-test/drupal-add-region-content');
+ $this->assertText('Hello');
+ $this->assertText('World');
+ }
}
/**
@@ -425,28 +434,100 @@ class ThemeFastTestCase extends DrupalWebTestCase {
}
/**
- * Unit tests for theme_html_tag().
+ * Tests the markup of core render element types passed to drupal_render().
*/
-class ThemeHtmlTag extends DrupalUnitTestCase {
+class RenderElementTypesTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
- 'name' => 'Theme HTML Tag',
- 'description' => 'Tests theme_html_tag() built-in theme functions.',
+ 'name' => 'Render element types',
+ 'description' => 'Tests the markup of core render element types passed to drupal_render().',
'group' => 'Theme',
);
}
/**
- * Test function theme_html_tag()
+ * Asserts that an array of elements is rendered properly.
+ *
+ * @param array $elements
+ * An array of associative arrays describing render elements and their
+ * expected markup. Each item in $elements must contain the following:
+ * - 'name': This human readable description will be displayed on the test
+ * results page.
+ * - 'value': This is the render element to test.
+ * - 'expected': This is the expected markup for the element in 'value'.
+ */
+ function assertElements($elements) {
+ foreach($elements as $element) {
+ $this->assertIdentical(drupal_render($element['value']), $element['expected'], '"' . $element['name'] . '" input rendered correctly by drupal_render().');
+ }
+ }
+
+ /**
+ * Tests system #type 'container'.
+ */
+ function testContainer() {
+ $elements = array(
+ // Basic container with no attributes.
+ array(
+ 'name' => "#type 'container' with no HTML attributes",
+ 'value' => array(
+ '#type' => 'container',
+ 'child' => array(
+ '#markup' => 'foo',
+ ),
+ ),
+ 'expected' => 'foo
',
+ ),
+ // Container with a class.
+ array(
+ 'name' => "#type 'container' with a class HTML attribute",
+ 'value' => array(
+ '#type' => 'container',
+ 'child' => array(
+ '#markup' => 'foo',
+ ),
+ '#attributes' => array(
+ 'class' => 'bar',
+ ),
+ ),
+ 'expected' => 'foo
',
+ ),
+ );
+
+ $this->assertElements($elements);
+ }
+
+ /**
+ * Tests system #type 'html_tag'.
*/
- function testThemeHtmlTag() {
- // Test auto-closure meta tag generation
- $tag['element'] = array('#tag' => 'meta', '#attributes' => array('name' => 'description', 'content' => 'Drupal test'));
- $this->assertEqual(''."\n", theme_html_tag($tag), 'Test auto-closure meta tag generation.');
+ function testHtmlTag() {
+ $elements = array(
+ // Test auto-closure meta tag generation.
+ array(
+ 'name' => "#type 'html_tag' auto-closure meta tag generation",
+ 'value' => array(
+ '#type' => 'html_tag',
+ '#tag' => 'meta',
+ '#attributes' => array(
+ 'name' => 'description',
+ 'content' => 'Drupal test',
+ ),
+ ),
+ 'expected' => '' . "\n",
+ ),
+ // Test title tag generation.
+ array(
+ 'name' => "#type 'html_tag' title tag generation",
+ 'value' => array(
+ '#type' => 'html_tag',
+ '#tag' => 'title',
+ '#value' => 'title test',
+ ),
+ 'expected' => 'title test' . "\n",
+ ),
+ );
- // Test title tag generation
- $tag['element'] = array('#tag' => 'title', '#value' => 'title test');
- $this->assertEqual('title test'."\n", theme_html_tag($tag), 'Test title tag generation.');
+ $this->assertElements($elements);
}
}
@@ -500,3 +581,68 @@ class ThemeRegistryTestCase extends DrupalWebTestCase {
$this->assertTrue($registry['theme_test_template_test_2'], 'Offset was returned correctly from the theme registry');
}
}
+
+/**
+ * Tests for theme debug markup.
+ */
+class ThemeDebugMarkupTestCase extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Theme debug markup',
+ 'description' => 'Tests theme debug markup output.',
+ 'group' => 'Theme',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('theme_test', 'node');
+ theme_enable(array('test_theme'));
+ }
+
+ /**
+ * Tests debug markup added to template output.
+ */
+ function testDebugOutput() {
+ variable_set('theme_default', 'test_theme');
+ // Enable the debug output.
+ variable_set('theme_debug', TRUE);
+
+ $registry = theme_get_registry();
+ $extension = '.tpl.php';
+ // Populate array of templates.
+ $templates = drupal_find_theme_templates($registry, $extension, drupal_get_path('theme', 'test_theme'));
+ $templates += drupal_find_theme_templates($registry, $extension, drupal_get_path('module', 'node'));
+
+ // Create a node and test different features of the debug markup.
+ $node = $this->drupalCreateNode();
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('', 'Theme debug markup found in theme output when debug is enabled.');
+ $this->assertRaw("CALL: theme('node')", 'Theme call information found.');
+ $this->assertRaw('x node--1' . $extension . PHP_EOL . ' * node--page' . $extension . PHP_EOL . ' * node' . $extension, 'Suggested template files found in order and node ID specific template shown as current template.');
+ $template_filename = $templates['node__1']['path'] . '/' . $templates['node__1']['template'] . $extension;
+ $this->assertRaw("BEGIN OUTPUT from '$template_filename'", 'Full path to current template file found.');
+
+ // Create another node and make sure the template suggestions shown in the
+ // debug markup are correct.
+ $node2 = $this->drupalCreateNode();
+ $this->drupalGet('node/' . $node2->nid);
+ $this->assertRaw('* node--2' . $extension . PHP_EOL . ' * node--page' . $extension . PHP_EOL . ' x node' . $extension, 'Suggested template files found in order and base template shown as current template.');
+
+ // Create another node and make sure the template suggestions shown in the
+ // debug markup are correct.
+ $node3 = $this->drupalCreateNode();
+ $build = array('#theme' => 'node__foo__bar');
+ $build += node_view($node3);
+ $output = drupal_render($build);
+ $this->assertTrue(strpos($output, "CALL: theme('node__foo__bar')") !== FALSE, 'Theme call information found.');
+ $this->assertTrue(strpos($output, '* node--foo--bar' . $extension . PHP_EOL . ' * node--foo' . $extension . PHP_EOL . ' * node--3' . $extension . PHP_EOL . ' * node--page' . $extension . PHP_EOL . ' x node' . $extension) !== FALSE, 'Suggested template files found in order and base template shown as current template.');
+
+ // Disable theme debug.
+ variable_set('theme_debug', FALSE);
+
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertNoRaw('', 'Theme debug markup not found in theme output when debug is disabled.');
+ }
+
+}
diff --git a/modules/simpletest/tests/theme_test.info b/modules/simpletest/tests/theme_test.info
index eb3bf87..2e7eaa4 100644
--- a/modules/simpletest/tests/theme_test.info
+++ b/modules/simpletest/tests/theme_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/theme_test.module b/modules/simpletest/tests/theme_test.module
index 61a12bb..948d817 100644
--- a/modules/simpletest/tests/theme_test.module
+++ b/modules/simpletest/tests/theme_test.module
@@ -53,6 +53,11 @@ function theme_test_menu() {
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
+ $items['theme-test/drupal-add-region-content'] = array(
+ 'page callback' => '_theme_test_drupal_add_region_content',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
return $items;
}
@@ -126,6 +131,14 @@ function _theme_test_suggestion() {
return theme(array('theme_test__suggestion', 'theme_test'), array());
}
+/**
+ * Page callback, calls drupal_add_region_content.
+ */
+function _theme_test_drupal_add_region_content() {
+ drupal_add_region_content('content', 'World');
+ return 'Hello';
+}
+
/**
* Theme function for testing theme('theme_test_foo').
*/
diff --git a/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info b/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info
index c2bb2dc..475c691 100644
--- a/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info
+++ b/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info
@@ -6,8 +6,8 @@ hidden = TRUE
settings[basetheme_only] = base theme value
settings[subtheme_override] = base theme value
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info b/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info
index fa14699..6f952c5 100644
--- a/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info
+++ b/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info
@@ -6,8 +6,8 @@ hidden = TRUE
settings[subtheme_override] = subtheme value
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/themes/test_theme/templates/node--1.tpl.php b/modules/simpletest/tests/themes/test_theme/templates/node--1.tpl.php
new file mode 100644
index 0000000..87aa794
--- /dev/null
+++ b/modules/simpletest/tests/themes/test_theme/templates/node--1.tpl.php
@@ -0,0 +1,2 @@
+
+Node Content Dummy
diff --git a/modules/simpletest/tests/themes/test_theme/test_theme.info b/modules/simpletest/tests/themes/test_theme/test_theme.info
index 4f459b4..2bd8b66 100644
--- a/modules/simpletest/tests/themes/test_theme/test_theme.info
+++ b/modules/simpletest/tests/themes/test_theme/test_theme.info
@@ -17,8 +17,8 @@ stylesheets[all][] = system.base.css
settings[theme_test_setting] = default value
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/update_script_test.info b/modules/simpletest/tests/update_script_test.info
index 27a49fb..caf035e 100644
--- a/modules/simpletest/tests/update_script_test.info
+++ b/modules/simpletest/tests/update_script_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/update_test_1.info b/modules/simpletest/tests/update_test_1.info
index f7725ca..b032585 100644
--- a/modules/simpletest/tests/update_test_1.info
+++ b/modules/simpletest/tests/update_test_1.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/update_test_2.info b/modules/simpletest/tests/update_test_2.info
index f7725ca..b032585 100644
--- a/modules/simpletest/tests/update_test_2.info
+++ b/modules/simpletest/tests/update_test_2.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/update_test_3.info b/modules/simpletest/tests/update_test_3.info
index f7725ca..b032585 100644
--- a/modules/simpletest/tests/update_test_3.info
+++ b/modules/simpletest/tests/update_test_3.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/url_alter_test.info b/modules/simpletest/tests/url_alter_test.info
index b236386..626bdf0 100644
--- a/modules/simpletest/tests/url_alter_test.info
+++ b/modules/simpletest/tests/url_alter_test.info
@@ -5,8 +5,8 @@ package = Testing
version = VERSION
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/simpletest/tests/xmlrpc_test.info b/modules/simpletest/tests/xmlrpc_test.info
index eadcd28..ff6dd59 100644
--- a/modules/simpletest/tests/xmlrpc_test.info
+++ b/modules/simpletest/tests/xmlrpc_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/statistics/statistics.admin.inc b/modules/statistics/statistics.admin.inc
index 71e64aa..f061081 100644
--- a/modules/statistics/statistics.admin.inc
+++ b/modules/statistics/statistics.admin.inc
@@ -59,7 +59,7 @@ function statistics_recent_hits() {
* statistics settings form, but is dependent on cron running.
*
* @return
- * A render array containing information about the the top pages.
+ * A render array containing information about the top pages.
*/
function statistics_top_pages() {
$header = array(
@@ -137,7 +137,8 @@ function statistics_top_visitors() {
->groupBy('u.name')
->groupBy('bl.iid')
->limit(30)
- ->orderByHeader($header);
+ ->orderByHeader($header)
+ ->orderBy('a.hostname');
$uniques_query = db_select('accesslog')->distinct();
$uniques_query->fields('accesslog', array('uid', 'hostname'));
diff --git a/modules/statistics/statistics.info b/modules/statistics/statistics.info
index 8310158..f95b85f 100644
--- a/modules/statistics/statistics.info
+++ b/modules/statistics/statistics.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = statistics.test
configure = admin/config/system/statistics
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/statistics/statistics.test b/modules/statistics/statistics.test
index 0498bb7..7e038d6 100644
--- a/modules/statistics/statistics.test
+++ b/modules/statistics/statistics.test
@@ -414,7 +414,7 @@ class StatisticsAdminTestCase extends DrupalWebTestCase {
$timestamp = time();
$this->drupalPost(NULL, NULL, t('Cancel account'));
// Confirm account cancellation request.
- $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login));
+ $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid));
$this->assertFalse(user_load($account->uid, TRUE), 'User is not found in the database.');
$this->drupalGet('admin/reports/visitors');
diff --git a/modules/syslog/syslog.info b/modules/syslog/syslog.info
index feb8a66..08c1683 100644
--- a/modules/syslog/syslog.info
+++ b/modules/syslog/syslog.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = syslog.test
configure = admin/config/development/logging
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/system/image.gd.inc b/modules/system/image.gd.inc
index d9035e4..913b0de 100644
--- a/modules/system/image.gd.inc
+++ b/modules/system/image.gd.inc
@@ -229,7 +229,24 @@ function image_gd_desaturate(stdClass $image) {
function image_gd_load(stdClass $image) {
$extension = str_replace('jpg', 'jpeg', $image->info['extension']);
$function = 'imagecreatefrom' . $extension;
- return (function_exists($function) && $image->resource = $function($image->source));
+ if (function_exists($function) && $image->resource = $function($image->source)) {
+ if (imageistruecolor($image->resource)) {
+ return TRUE;
+ }
+ else {
+ // Convert indexed images to truecolor, copying the image to a new
+ // truecolor resource, so that filters work correctly and don't result
+ // in unnecessary dither.
+ $resource = image_gd_create_tmp($image, $image->info['width'], $image->info['height']);
+ if ($resource) {
+ imagecopy($resource, $image->resource, 0, 0, 0, 0, imagesx($resource), imagesy($resource));
+ imagedestroy($image->resource);
+ $image->resource = $resource;
+ }
+ }
+ return (bool) $image->resource;
+ }
+ return FALSE;
}
/**
@@ -297,17 +314,31 @@ function image_gd_create_tmp(stdClass $image, $width, $height) {
$res = imagecreatetruecolor($width, $height);
if ($image->info['extension'] == 'gif') {
- // Grab transparent color index from image resource.
+ // Find out if a transparent color is set, will return -1 if no
+ // transparent color has been defined in the image.
$transparent = imagecolortransparent($image->resource);
if ($transparent >= 0) {
- // The original must have a transparent color, allocate to the new image.
- $transparent_color = imagecolorsforindex($image->resource, $transparent);
- $transparent = imagecolorallocate($res, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
-
- // Flood with our new transparent color.
- imagefill($res, 0, 0, $transparent);
- imagecolortransparent($res, $transparent);
+ // Find out the number of colors in the image palette. It will be 0 for
+ // truecolor images.
+ $palette_size = imagecolorstotal($image->resource);
+ if ($palette_size == 0 || $transparent < $palette_size) {
+ // Set the transparent color in the new resource, either if it is a
+ // truecolor image or if the transparent color is part of the palette.
+ // Since the index of the transparency color is a property of the
+ // image rather than of the palette, it is possible that an image
+ // could be created with this index set outside the palette size (see
+ // http://stackoverflow.com/a/3898007).
+ $transparent_color = imagecolorsforindex($image->resource, $transparent);
+ $transparent = imagecolorallocate($res, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
+
+ // Flood with our new transparent color.
+ imagefill($res, 0, 0, $transparent);
+ imagecolortransparent($res, $transparent);
+ }
+ else {
+ imagefill($res, 0, 0, imagecolorallocate($res, 255, 255, 255));
+ }
}
}
elseif ($image->info['extension'] == 'png') {
diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc
index b6f6789..0f525c6 100644
--- a/modules/system/system.admin.inc
+++ b/modules/system/system.admin.inc
@@ -640,13 +640,13 @@ function system_theme_settings_validate($form, &$form_state) {
// If the user provided a path for a logo or favicon file, make sure a file
// exists at that path.
- if ($form_state['values']['logo_path']) {
+ if (!empty($form_state['values']['logo_path'])) {
$path = _system_theme_settings_validate_path($form_state['values']['logo_path']);
if (!$path) {
form_set_error('logo_path', t('The custom logo path is invalid.'));
}
}
- if ($form_state['values']['favicon_path']) {
+ if (!empty($form_state['values']['favicon_path'])) {
$path = _system_theme_settings_validate_path($form_state['values']['favicon_path']);
if (!$path) {
form_set_error('favicon_path', t('The custom favicon path is invalid.'));
@@ -703,14 +703,16 @@ function system_theme_settings_submit($form, &$form_state) {
// If the user uploaded a new logo or favicon, save it to a permanent location
// and use it in place of the default theme-provided file.
- if ($file = $values['logo_upload']) {
+ if (!empty($values['logo_upload'])) {
+ $file = $values['logo_upload'];
unset($values['logo_upload']);
$filename = file_unmanaged_copy($file->uri);
$values['default_logo'] = 0;
$values['logo_path'] = $filename;
$values['toggle_logo'] = 1;
}
- if ($file = $values['favicon_upload']) {
+ if (!empty($values['favicon_upload'])) {
+ $file = $values['favicon_upload'];
unset($values['favicon_upload']);
$filename = file_unmanaged_copy($file->uri);
$values['default_favicon'] = 0;
@@ -2644,8 +2646,8 @@ function theme_system_modules_fieldset($variables) {
}
$row[] = array('data' => $description, 'class' => array('description'));
// Display links (such as help or permissions) in their own columns.
- foreach (array('help', 'permissions', 'configure') as $key) {
- $row[] = array('data' => drupal_render($module['links'][$key]), 'class' => array($key));
+ foreach (array('help', 'permissions', 'configure') as $link_type) {
+ $row[] = array('data' => drupal_render($module['links'][$link_type]), 'class' => array($link_type));
}
$rows[] = $row;
}
diff --git a/modules/system/system.api.php b/modules/system/system.api.php
index 22175b1..d6cbc76 100644
--- a/modules/system/system.api.php
+++ b/modules/system/system.api.php
@@ -247,7 +247,7 @@ function hook_entity_info() {
'custom settings' => FALSE,
),
'search_result' => array(
- 'label' => t('Search result'),
+ 'label' => t('Search result highlighting input'),
'custom settings' => FALSE,
),
);
@@ -606,8 +606,8 @@ function hook_cron() {
* @return
* An associative array where the key is the queue name and the value is
* again an associative array. Possible keys are:
- * - 'worker callback': The name of the function to call. It will be called
- * with one argument, the item created via DrupalQueue::createItem().
+ * - 'worker callback': The name of an implementation of
+ * callback_queue_worker().
* - 'time': (optional) How much time Drupal should spend on calling this
* worker in seconds. Defaults to 15.
* - 'skip on cron': (optional) Set to TRUE to avoid being processed during
@@ -1890,9 +1890,8 @@ function hook_init() {
/**
* Define image toolkits provided by this module.
*
- * The file which includes each toolkit's functions must be declared as part of
- * the files array in the module .info file so that the registry will find and
- * parse it.
+ * The file which includes each toolkit's functions must be included in this
+ * hook.
*
* The toolkit's functions must be named image_toolkitname_operation().
* where the operation may be:
@@ -3694,8 +3693,9 @@ function hook_registry_files_alter(&$files, $modules) {
*
* Any tasks you define here will be run, in order, after the installer has
* finished the site configuration step but before it has moved on to the
- * final import of languages and the end of the installation. You can have any
- * number of custom tasks to perform during this phase.
+ * final import of languages and the end of the installation. This is invoked
+ * by install_tasks(). You can have any number of custom tasks to perform
+ * during this phase.
*
* Each task you define here corresponds to a callback function which you must
* separately define and which is called when your task is run. This function
@@ -3788,6 +3788,8 @@ function hook_registry_files_alter(&$files, $modules) {
*
* @see install_state_defaults()
* @see batch_set()
+ * @see hook_install_tasks_alter()
+ * @see install_tasks()
*/
function hook_install_tasks(&$install_state) {
// Here, we define a variable to allow tasks to indicate that a particular,
@@ -3890,6 +3892,8 @@ function hook_html_head_alter(&$head_elements) {
/**
* Alter the full list of installation tasks.
*
+ * This hook is invoked on the install profile in install_tasks().
+ *
* @param $tasks
* An array of all available installation tasks, including those provided by
* Drupal core. You can modify this array to change or replace any part of
@@ -3897,6 +3901,9 @@ function hook_html_head_alter(&$head_elements) {
* is selected.
* @param $install_state
* An array of information about the current installation state.
+ *
+ * @see hook_install_tasks()
+ * @see install_tasks()
*/
function hook_install_tasks_alter(&$tasks, $install_state) {
// Replace the "Choose language" installation task provided by Drupal core
@@ -4783,6 +4790,28 @@ function hook_filetransfer_info_alter(&$filetransfer_info) {
* @{
*/
+/**
+ * Work on a single queue item.
+ *
+ * Callback for hook_cron_queue_info().
+ *
+ * @param $queue_item_data
+ * The data that was passed to DrupalQueueInterface::createItem() when the
+ * item was queued.
+ *
+ * @throws Exception
+ * The worker callback may throw an exception to indicate there was a problem.
+ * The cron process will log the exception, and leave the item in the queue to
+ * be processed again later.
+ *
+ * @see drupal_cron_run()
+ */
+function callback_queue_worker($queue_item_data) {
+ $node = node_load($queue_item_data);
+ $node->title = 'Updated title';
+ node_save($node);
+}
+
/**
* Return the URI for an entity.
*
diff --git a/modules/system/system.base-rtl.css b/modules/system/system.base-rtl.css
index 075cafe..79d6302 100644
--- a/modules/system/system.base-rtl.css
+++ b/modules/system/system.base-rtl.css
@@ -9,10 +9,10 @@
*/
/* Animated throbber */
html.js input.form-autocomplete {
- background-position: 0% 2px;
+ background-position: 0% center;
}
html.js input.throbbing {
- background-position: 0% -18px;
+ background-position: 0% center;
}
/**
diff --git a/modules/system/system.base.css b/modules/system/system.base.css
index 412b18a..7e9220d 100644
--- a/modules/system/system.base.css
+++ b/modules/system/system.base.css
@@ -31,12 +31,13 @@
}
/* Animated throbber */
html.js input.form-autocomplete {
- background-image: url(../../misc/throbber.gif);
- background-position: 100% 2px; /* LTR */
+ background-image: url(../../misc/throbber-inactive.png);
+ background-position: 100% center; /* LTR */
background-repeat: no-repeat;
}
html.js input.throbbing {
- background-position: 100% -18px; /* LTR */
+ background-image: url(../../misc/throbber-active.gif);
+ background-position: 100% center; /* LTR */
}
/**
@@ -164,7 +165,7 @@ table.sticky-header {
display: inline-block;
}
.ajax-progress .throbber {
- background: transparent url(../../misc/throbber.gif) no-repeat 0px -18px;
+ background: transparent url(../../misc/throbber-active.gif) no-repeat 0px center;
float: left; /* LTR */
height: 15px;
margin: 2px;
diff --git a/modules/system/system.info b/modules/system/system.info
index aff552e..3ba01c0 100644
--- a/modules/system/system.info
+++ b/modules/system/system.info
@@ -12,8 +12,8 @@ files[] = system.test
required = TRUE
configure = admin/config/system
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/system/system.install b/modules/system/system.install
index 43c7383..64c989a 100644
--- a/modules/system/system.install
+++ b/modules/system/system.install
@@ -2854,7 +2854,14 @@ function system_update_7061(&$sandbox) {
// We will convert filepaths to URI using the default scheme
// and stripping off the existing file directory path.
$file['uri'] = $scheme . preg_replace('!^' . preg_quote($basename) . '!', '', $file['filepath']);
- $file['uri'] = file_stream_wrapper_uri_normalize($file['uri']);
+ // Normalize the URI but don't call file_stream_wrapper_uri_normalize()
+ // directly, since that is a higher-level API function which invokes
+ // hooks while validating the scheme, and those will not work during
+ // the upgrade. Instead, use a simpler version that just assumes the
+ // scheme from above is already valid.
+ if (($file_uri_scheme = file_uri_scheme($file['uri'])) && ($file_uri_target = file_uri_target($file['uri']))) {
+ $file['uri'] = $file_uri_scheme . '://' . $file_uri_target;
+ }
unset($file['filepath']);
// Insert into the file_managed table.
// Each fid should only be stored once in file_managed.
diff --git a/modules/system/system.module b/modules/system/system.module
index 18d8a88..8fc517f 100644
--- a/modules/system/system.module
+++ b/modules/system/system.module
@@ -374,6 +374,9 @@ function system_element_info() {
'#element_validate' => array('form_validate_machine_name'),
'#theme' => 'textfield',
'#theme_wrappers' => array('form_element'),
+ // Use the same value callback as for textfields; this ensures that we only
+ // get string values.
+ '#value_callback' => 'form_type_textfield_value',
);
$types['password'] = array(
'#input' => TRUE,
@@ -382,6 +385,9 @@ function system_element_info() {
'#process' => array('ajax_process_form'),
'#theme' => 'password',
'#theme_wrappers' => array('form_element'),
+ // Use the same value callback as for textfields; this ensures that we only
+ // get string values.
+ '#value_callback' => 'form_type_textfield_value',
);
$types['password_confirm'] = array(
'#input' => TRUE,
@@ -2024,7 +2030,6 @@ function system_user_timezone(&$form, &$form_state) {
'#description' => t('Select the desired local time and time zone. Dates and times throughout this site will be displayed using this time zone.'),
);
if (!isset($account->timezone) && $account->uid == $user->uid && empty($form_state['input']['timezone'])) {
- $form['timezone']['#description'] = t('Your time zone setting will be automatically detected if possible. Confirm the selection and click save.');
$form['timezone']['timezone']['#attributes'] = array('class' => array('timezone-detect'));
drupal_add_js('misc/timezone.js');
}
@@ -2399,6 +2404,10 @@ function _system_rebuild_module_data() {
continue;
}
+ // Add the info file modification time, so it becomes available for
+ // contributed modules to use for ordering module lists.
+ $module->info['mtime'] = filemtime(dirname($module->uri) . '/' . $module->name . '.info');
+
// Merge in defaults and save.
$modules[$key]->info = $module->info + $defaults;
@@ -2537,6 +2546,10 @@ function _system_rebuild_theme_data() {
$themes[$key]->filename = $theme->uri;
$themes[$key]->info = drupal_parse_info_file($theme->uri) + $defaults;
+ // Add the info file modification time, so it becomes available for
+ // contributed modules to use for ordering theme lists.
+ $themes[$key]->info['mtime'] = filemtime($theme->uri);
+
// Invoke hook_system_info_alter() to give installed modules a chance to
// modify the data in the .info files if necessary.
$type = 'theme';
@@ -3386,7 +3399,7 @@ function system_timezone($abbreviation = '', $offset = -1, $is_daylight_saving_t
* @ingroup themeable
*/
function theme_system_powered_by() {
- return '' . t('Powered by Drupal', array('@poweredby' => 'http://drupal.org')) . '';
+ return '' . t('Powered by Drupal', array('@poweredby' => 'https://www.drupal.org')) . '';
}
/**
diff --git a/modules/system/system.queue.inc b/modules/system/system.queue.inc
index 901c4d6..6eeaae1 100644
--- a/modules/system/system.queue.inc
+++ b/modules/system/system.queue.inc
@@ -231,7 +231,7 @@ class SystemQueue implements DrupalReliableQueueInterface {
// until an item is successfully claimed or we are reasonably sure there
// are no unclaimed items left.
while (TRUE) {
- $item = db_query_range('SELECT data, item_id FROM {queue} q WHERE expire = 0 AND name = :name ORDER BY created ASC', 0, 1, array(':name' => $this->name))->fetchObject();
+ $item = db_query_range('SELECT data, item_id FROM {queue} q WHERE expire = 0 AND name = :name ORDER BY created, item_id ASC', 0, 1, array(':name' => $this->name))->fetchObject();
if ($item) {
// Try to update the item. Only one thread can succeed in UPDATEing the
// same row. We cannot rely on REQUEST_TIME because items might be
diff --git a/modules/system/system.test b/modules/system/system.test
index cae5cc7..d4c98f0 100644
--- a/modules/system/system.test
+++ b/modules/system/system.test
@@ -556,6 +556,34 @@ class ModuleDependencyTestCase extends ModuleTestCase {
$this->drupalPost(NULL, NULL, t('Uninstall'));
$this->assertText(t('The selected modules have been uninstalled.'), 'Modules status has been updated.');
}
+
+ /**
+ * Tests whether the correct module metadata is returned.
+ */
+ function testModuleMetaData() {
+ // Generate the list of available modules.
+ $modules = system_rebuild_module_data();
+ // Check that the mtime field exists for the system module.
+ $this->assertTrue(!empty($modules['system']->info['mtime']), 'The system.info file modification time field is present.');
+ // Use 0 if mtime isn't present, to avoid an array index notice.
+ $test_mtime = !empty($modules['system']->info['mtime']) ? $modules['system']->info['mtime'] : 0;
+ // Ensure the mtime field contains a number that is greater than zero.
+ $this->assertTrue(is_numeric($test_mtime) && ($test_mtime > 0), 'The system.info file modification time field contains a timestamp.');
+ }
+
+ /**
+ * Tests whether the correct theme metadata is returned.
+ */
+ function testThemeMetaData() {
+ // Generate the list of available themes.
+ $themes = system_rebuild_theme_data();
+ // Check that the mtime field exists for the bartik theme.
+ $this->assertTrue(!empty($themes['bartik']->info['mtime']), 'The bartik.info file modification time field is present.');
+ // Use 0 if mtime isn't present, to avoid an array index notice.
+ $test_mtime = !empty($themes['bartik']->info['mtime']) ? $themes['bartik']->info['mtime'] : 0;
+ // Ensure the mtime field contains a number that is greater than zero.
+ $this->assertTrue(is_numeric($test_mtime) && ($test_mtime > 0), 'The bartik.info file modification time field contains a timestamp.');
+ }
}
/**
@@ -1040,6 +1068,11 @@ class PageNotFoundTestCase extends DrupalWebTestCase {
);
$node = $this->drupalCreateNode($edit);
+ // As node IDs must be integers, make sure requests for non-integer IDs
+ // return a page not found error.
+ $this->drupalGet('node/invalid');
+ $this->assertResponse(404);
+
// Use a custom 404 page.
$this->drupalPost('admin/config/system/site-information', array('site_404' => 'node/' . $node->nid), t('Save configuration'));
@@ -2797,3 +2830,75 @@ class SystemValidTokenTest extends DrupalUnitTestCase {
return TRUE;
}
}
+
+/**
+ * Tests drupal_set_message() and related functions.
+ */
+class DrupalSetMessageTest extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Messages',
+ 'description' => 'Tests that messages can be displayed using drupal_set_message().',
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('system_test');
+ }
+
+ /**
+ * Tests setting messages and removing one before it is displayed.
+ */
+ function testSetRemoveMessages() {
+ // The page at system-test/drupal-set-message sets two messages and then
+ // removes the first before it is displayed.
+ $this->drupalGet('system-test/drupal-set-message');
+ $this->assertNoText('First message (removed).');
+ $this->assertText('Second message (not removed).');
+ }
+}
+
+/**
+ * Tests confirm form destinations.
+ */
+class ConfirmFormTest extends DrupalWebTestCase {
+ protected $admin_user;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Confirm form',
+ 'description' => 'Tests that the confirm form does not use external destinations.',
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $this->admin_user = $this->drupalCreateUser(array('administer users'));
+ $this->drupalLogin($this->admin_user);
+ }
+
+ /**
+ * Tests that the confirm form does not use external destinations.
+ */
+ function testConfirmForm() {
+ $this->drupalGet('user/1/cancel');
+ $this->assertCancelLinkUrl(url('user/1'));
+ $this->drupalGet('user/1/cancel', array('query' => array('destination' => 'node')));
+ $this->assertCancelLinkUrl(url('node'));
+ $this->drupalGet('user/1/cancel', array('query' => array('destination' => 'http://example.com')));
+ $this->assertCancelLinkUrl(url('user/1'));
+ }
+
+ /**
+ * Asserts that a cancel link is present pointing to the provided URL.
+ */
+ function assertCancelLinkUrl($url, $message = '', $group = 'Other') {
+ $links = $this->xpath('//a[normalize-space(text())=:label and @href=:url]', array(':label' => t('Cancel'), ':url' => $url));
+ $message = ($message ? $message : format_string('Cancel link with url %url found.', array('%url' => $url)));
+ return $this->assertTrue(isset($links[0]), $message, $group);
+ }
+}
diff --git a/modules/system/tests/cron_queue_test.info b/modules/system/tests/cron_queue_test.info
index 43cd629..faaef6e 100644
--- a/modules/system/tests/cron_queue_test.info
+++ b/modules/system/tests/cron_queue_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/taxonomy/taxonomy.api.php b/modules/taxonomy/taxonomy.api.php
index b9c23db..f3c5022 100644
--- a/modules/taxonomy/taxonomy.api.php
+++ b/modules/taxonomy/taxonomy.api.php
@@ -212,7 +212,7 @@ function hook_taxonomy_term_view($term, $view_mode, $langcode) {
* documentation respectively for details.
*
* @param $build
- * A renderable array representing the node content.
+ * A renderable array representing the term.
*
* @see hook_entity_view_alter()
*/
diff --git a/modules/taxonomy/taxonomy.info b/modules/taxonomy/taxonomy.info
index 13ddbc0..c689e25 100644
--- a/modules/taxonomy/taxonomy.info
+++ b/modules/taxonomy/taxonomy.info
@@ -8,8 +8,8 @@ files[] = taxonomy.module
files[] = taxonomy.test
configure = admin/structure/taxonomy
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/taxonomy/taxonomy.module b/modules/taxonomy/taxonomy.module
index 4191146..e147c1c 100644
--- a/modules/taxonomy/taxonomy.module
+++ b/modules/taxonomy/taxonomy.module
@@ -776,15 +776,26 @@ function taxonomy_term_show($term) {
* An array in the format expected by drupal_render().
*/
function taxonomy_term_view_multiple($terms, $view_mode = 'teaser', $weight = 0, $langcode = NULL) {
- field_attach_prepare_view('taxonomy_term', $terms, $view_mode, $langcode);
- entity_prepare_view('taxonomy_term', $terms, $langcode);
$build = array();
+ $entities_by_view_mode = entity_view_mode_prepare('taxonomy_term', $terms, $view_mode, $langcode);
+ foreach ($entities_by_view_mode as $entity_view_mode => $entities) {
+ field_attach_prepare_view('taxonomy_term', $entities, $entity_view_mode, $langcode);
+ entity_prepare_view('taxonomy_term', $entities, $langcode);
+
+ foreach ($entities as $entity) {
+ $build['taxonomy_terms'][$entity->tid] = taxonomy_term_view($entity, $entity_view_mode, $langcode);
+ }
+ }
+
foreach ($terms as $term) {
- $build['taxonomy_terms'][$term->tid] = taxonomy_term_view($term, $view_mode, $langcode);
$build['taxonomy_terms'][$term->tid]['#weight'] = $weight;
$weight++;
}
+ // Sort here, to preserve the input order of the entities that were passed to
+ // this function.
+ uasort($build['taxonomy_terms'], 'element_sort');
$build['taxonomy_terms']['#sorted'] = TRUE;
+
return $build;
}
@@ -817,12 +828,7 @@ function taxonomy_term_build_content($term, $view_mode = 'full', $langcode = NUL
$term->content = array();
// Allow modules to change the view mode.
- $context = array(
- 'entity_type' => 'taxonomy_term',
- 'entity' => $term,
- 'langcode' => $langcode,
- );
- drupal_alter('entity_view_mode', $view_mode, $context);
+ $view_mode = key(entity_view_mode_prepare('taxonomy_term', array($term->tid => $term), $view_mode, $langcode));
// Add the term description if the term has one and it is visible.
$type = 'taxonomy_term';
diff --git a/modules/toolbar/toolbar.info b/modules/toolbar/toolbar.info
index 1e341b4..782091c 100644
--- a/modules/toolbar/toolbar.info
+++ b/modules/toolbar/toolbar.info
@@ -4,8 +4,8 @@ core = 7.x
package = Core
version = VERSION
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/tracker/tracker.info b/modules/tracker/tracker.info
index 2c5eff5..f41cbf5 100644
--- a/modules/tracker/tracker.info
+++ b/modules/tracker/tracker.info
@@ -6,8 +6,8 @@ version = VERSION
core = 7.x
files[] = tracker.test
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/tracker/tracker.module b/modules/tracker/tracker.module
index 8694222..ab3e748 100644
--- a/modules/tracker/tracker.module
+++ b/modules/tracker/tracker.module
@@ -263,7 +263,7 @@ function _tracker_add($nid, $uid, $changed) {
))
->execute();
- // Create or update the user-level data.
+ // Create or update the user-level data, first for the user posting.
db_merge('tracker_user')
->key(array(
'nid' => $nid,
@@ -274,6 +274,14 @@ function _tracker_add($nid, $uid, $changed) {
'published' => $node->status,
))
->execute();
+ // Update the times for all the other users tracking the post.
+ db_update('tracker_user')
+ ->condition('nid', $nid)
+ ->fields(array(
+ 'changed' => $changed,
+ 'published' => $node->status,
+ ))
+ ->execute();
}
/**
diff --git a/modules/tracker/tracker.test b/modules/tracker/tracker.test
index 66ebb84..8a48ea8 100644
--- a/modules/tracker/tracker.test
+++ b/modules/tracker/tracker.test
@@ -182,6 +182,72 @@ class TrackerTest extends DrupalWebTestCase {
$this->assertText('1 new', 'New comments are counted on the tracker listing pages.');
}
+ /**
+ * Tests for ordering on a users tracker listing when comments are posted.
+ */
+ function testTrackerOrderingNewComments() {
+ $this->drupalLogin($this->user);
+
+ $node_one = $this->drupalCreateNode(array(
+ 'title' => $this->randomName(8),
+ ));
+
+ $node_two = $this->drupalCreateNode(array(
+ 'title' => $this->randomName(8),
+ ));
+
+ // Now get other_user to track these pieces of content.
+ $this->drupalLogin($this->other_user);
+
+ // Add a comment to the first page.
+ $comment = array(
+ 'subject' => $this->randomName(),
+ 'comment_body[' . LANGUAGE_NONE . '][0][value]' => $this->randomName(20),
+ );
+ $this->drupalPost('comment/reply/' . $node_one->nid, $comment, t('Save'));
+
+ // If the comment is posted in the same second as the last one then Drupal
+ // can't tell the difference, so we wait one second here.
+ sleep(1);
+
+ // Add a comment to the second page.
+ $comment = array(
+ 'subject' => $this->randomName(),
+ 'comment_body[' . LANGUAGE_NONE . '][0][value]' => $this->randomName(20),
+ );
+ $this->drupalPost('comment/reply/' . $node_two->nid, $comment, t('Save'));
+
+ // We should at this point have in our tracker for other_user:
+ // 1. node_two
+ // 2. node_one
+ // Because that's the reverse order of the posted comments.
+
+ // Now we're going to post a comment to node_one which should jump it to the
+ // top of the list.
+
+ $this->drupalLogin($this->user);
+ // If the comment is posted in the same second as the last one then Drupal
+ // can't tell the difference, so we wait one second here.
+ sleep(1);
+
+ // Add a comment to the second page.
+ $comment = array(
+ 'subject' => $this->randomName(),
+ 'comment_body[' . LANGUAGE_NONE . '][0][value]' => $this->randomName(20),
+ );
+ $this->drupalPost('comment/reply/' . $node_one->nid, $comment, t('Save'));
+
+ // Switch back to the other_user and assert that the order has swapped.
+ $this->drupalLogin($this->other_user);
+ $this->drupalGet('user/' . $this->other_user->uid . '/track');
+ // This is a cheeky way of asserting that the nodes are in the right order
+ // on the tracker page.
+ // It's almost certainly too brittle.
+ $pattern = '/' . preg_quote($node_one->title) . '.+' . preg_quote($node_two->title) . '/s';
+ $this->verbose($pattern);
+ $this->assertPattern($pattern, 'Most recently commented on node appears at the top of tracker');
+ }
+
/**
* Tests that existing nodes are indexed by cron.
*/
diff --git a/modules/translation/tests/translation_test.info b/modules/translation/tests/translation_test.info
index 2123d56..3445bd1 100644
--- a/modules/translation/tests/translation_test.info
+++ b/modules/translation/tests/translation_test.info
@@ -5,8 +5,8 @@ package = Testing
version = VERSION
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/translation/translation.info b/modules/translation/translation.info
index e532090..a27903c 100644
--- a/modules/translation/translation.info
+++ b/modules/translation/translation.info
@@ -6,8 +6,8 @@ version = VERSION
core = 7.x
files[] = translation.test
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/translation/translation.module b/modules/translation/translation.module
index 3312357..580d000 100644
--- a/modules/translation/translation.module
+++ b/modules/translation/translation.module
@@ -336,9 +336,13 @@ function translation_node_insert($node) {
'tnid' => $tnid,
'translate' => 0,
))
- ->condition('nid', $node->translation_source->nid)
+ ->condition('nid', $tnid)
->execute();
+
+ // Flush the (untranslated) source node from the load cache.
+ entity_get_controller('node')->resetCache(array($tnid));
}
+
db_update('node')
->fields(array(
'tnid' => $tnid,
@@ -368,13 +372,23 @@ function translation_node_update($node) {
))
->condition('nid', $node->nid)
->execute();
+
if (!empty($node->translation['retranslate'])) {
// This is the source node, asking to mark all translations outdated.
- db_update('node')
- ->fields(array('translate' => 1))
+ $translations = db_select('node', 'n')
+ ->fields('n', array('nid'))
->condition('nid', $node->nid, '<>')
->condition('tnid', $node->tnid)
+ ->execute()
+ ->fetchCol();
+
+ db_update('node')
+ ->fields(array('translate' => 1))
+ ->condition('nid', $translations, 'IN')
->execute();
+
+ // Flush the modified translation nodes from the load cache.
+ entity_get_controller('node')->resetCache($translations);
}
}
}
@@ -414,17 +428,22 @@ function translation_node_delete($node) {
* A node object.
*/
function translation_remove_from_set($node) {
- if (isset($node->tnid)) {
+ if (isset($node->tnid) && $node->tnid) {
$query = db_update('node')
->fields(array(
'tnid' => 0,
'translate' => 0,
));
- if (db_query('SELECT COUNT(*) FROM {node} WHERE tnid = :tnid', array(':tnid' => $node->tnid))->fetchField() == 1) {
+
+ // Determine which nodes to apply the update to.
+ $set_nids = db_query('SELECT nid FROM {node} WHERE tnid = :tnid', array(':tnid' => $node->tnid))->fetchCol();
+ if (count($set_nids) == 1) {
// There is only one node left in the set: remove the set altogether.
$query
->condition('tnid', $node->tnid)
->execute();
+
+ $flush_set = TRUE;
}
else {
$query
@@ -439,8 +458,14 @@ function translation_remove_from_set($node) {
->fields(array('tnid' => $new_tnid))
->condition('tnid', $node->tnid)
->execute();
+
+ $flush_set = TRUE;
}
}
+
+ // Flush the modified nodes from the load cache.
+ $nids = !empty($flush_set) ? $set_nids : array($node->nid);
+ entity_get_controller('node')->resetCache($nids);
}
}
diff --git a/modules/trigger/tests/trigger_test.info b/modules/trigger/tests/trigger_test.info
index 250c78f..7c8b9e2 100644
--- a/modules/trigger/tests/trigger_test.info
+++ b/modules/trigger/tests/trigger_test.info
@@ -4,8 +4,8 @@ package = Testing
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/trigger/trigger.info b/modules/trigger/trigger.info
index 2a5f54c..aa112c8 100644
--- a/modules/trigger/trigger.info
+++ b/modules/trigger/trigger.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = trigger.test
configure = admin/structure/trigger
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/update/tests/aaa_update_test.info b/modules/update/tests/aaa_update_test.info
index ac17038..3e841ad 100644
--- a/modules/update/tests/aaa_update_test.info
+++ b/modules/update/tests/aaa_update_test.info
@@ -4,8 +4,8 @@ package = Testing
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/update/tests/bbb_update_test.info b/modules/update/tests/bbb_update_test.info
index 9730b8d..c5fdc70 100644
--- a/modules/update/tests/bbb_update_test.info
+++ b/modules/update/tests/bbb_update_test.info
@@ -4,8 +4,8 @@ package = Testing
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/update/tests/ccc_update_test.info b/modules/update/tests/ccc_update_test.info
index 77bacd1..4fc09eb 100644
--- a/modules/update/tests/ccc_update_test.info
+++ b/modules/update/tests/ccc_update_test.info
@@ -4,8 +4,8 @@ package = Testing
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info b/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info
index 3325a32..6cbf67d 100644
--- a/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info
+++ b/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info
@@ -3,8 +3,8 @@ description = Test theme which acts as a base theme for other test subthemes.
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info b/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info
index 19e5c89..fe17cd2 100644
--- a/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info
+++ b/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info
@@ -4,8 +4,8 @@ core = 7.x
base theme = update_test_basetheme
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/update/tests/update_test.info b/modules/update/tests/update_test.info
index 574e82c..e8a5aaf 100644
--- a/modules/update/tests/update_test.info
+++ b/modules/update/tests/update_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/update/update.compare.inc b/modules/update/update.compare.inc
index 6e0c5fe..072a0da 100644
--- a/modules/update/update.compare.inc
+++ b/modules/update/update.compare.inc
@@ -417,14 +417,15 @@ function update_calculate_project_data($available) {
* version (e.g., 5.x-1.5-beta1, 5.x-1.5-beta2, and 5.x-1.5). Development
* snapshots for a given major version are always listed last.
*
- * @param $project
- * An array containing information about a specific project.
+ * @param $unused
+ * Input is not being used, but remains in function for API compatibility
+ * reasons.
* @param $project_data
* An array containing information about a specific project.
* @param $available
* Data about available project releases of a specific project.
*/
-function update_calculate_project_update_status($project, &$project_data, $available) {
+function update_calculate_project_update_status($unused, &$project_data, $available) {
foreach (array('title', 'link') as $attribute) {
if (!isset($project_data[$attribute]) && isset($available[$attribute])) {
$project_data[$attribute] = $available[$attribute];
diff --git a/modules/update/update.info b/modules/update/update.info
index 1f00015..08aef93 100644
--- a/modules/update/update.info
+++ b/modules/update/update.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = update.test
configure = admin/reports/updates/settings
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/update/update.module b/modules/update/update.module
index d1f0d85..a59c7d7 100644
--- a/modules/update/update.module
+++ b/modules/update/update.module
@@ -278,12 +278,15 @@ function update_theme() {
),
'update_report' => array(
'variables' => array('data' => NULL),
+ 'file' => 'update.report.inc',
),
'update_version' => array(
'variables' => array('version' => NULL, 'tag' => NULL, 'class' => array()),
+ 'file' => 'update.report.inc',
),
'update_status_label' => array(
'variables' => array('status' => NULL),
+ 'file' => 'update.report.inc',
),
);
}
diff --git a/modules/user/tests/user_form_test.info b/modules/user/tests/user_form_test.info
index 07049d3..68c2f5e 100644
--- a/modules/user/tests/user_form_test.info
+++ b/modules/user/tests/user_form_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/user/user.api.php b/modules/user/user.api.php
index 3afc88a..edc61bd 100644
--- a/modules/user/user.api.php
+++ b/modules/user/user.api.php
@@ -327,14 +327,6 @@ function hook_user_logout($account) {
* The module should format its custom additions for display and add them to the
* $account->content array.
*
- * Note that when this hook is invoked, the changes have not yet been written to
- * the database, because a database transaction is still in progress. The
- * transaction is not finalized until the save operation is entirely completed
- * and user_save() goes out of scope. You should not rely on data in the
- * database at this time as it is not updated yet. You should also note that any
- * write/update database queries executed from this hook are also not committed
- * immediately. Check user_save() and db_transaction() for more info.
- *
* @param $account
* The user object on which the operation is being performed.
* @param $view_mode
@@ -386,7 +378,7 @@ function hook_user_view_alter(&$build) {
}
/**
- * Inform other modules that a user role is about to be saved.
+ * Act on a user role being inserted or updated.
*
* Modules implementing this hook can act on the user role object before
* it has been saved to the database.
@@ -405,7 +397,7 @@ function hook_user_role_presave($role) {
}
/**
- * Inform other modules that a user role has been added.
+ * Respond to creation of a new user role.
*
* Modules implementing this hook can act on the user role object when saved to
* the database. It's recommended that you implement this hook if your module
@@ -426,7 +418,7 @@ function hook_user_role_insert($role) {
}
/**
- * Inform other modules that a user role has been updated.
+ * Respond to updates to a user role.
*
* Modules implementing this hook can act on the user role object when updated.
* It's recommended that you implement this hook if your module adds additional
@@ -447,7 +439,7 @@ function hook_user_role_update($role) {
}
/**
- * Inform other modules that a user role has been deleted.
+ * Respond to user role deletion.
*
* This hook allows you act when a user role has been deleted.
* If your module stores references to roles, it's recommended that you
diff --git a/modules/user/user.info b/modules/user/user.info
index 3f72643..0740f01 100644
--- a/modules/user/user.info
+++ b/modules/user/user.info
@@ -9,8 +9,8 @@ required = TRUE
configure = admin/config/people
stylesheets[all][] = user.css
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/modules/user/user.install b/modules/user/user.install
index 4e1a3c2..b573e72 100644
--- a/modules/user/user.install
+++ b/modules/user/user.install
@@ -81,7 +81,7 @@ function user_schema() {
),
'foreign keys' => array(
'role' => array(
- 'table' => 'roles',
+ 'table' => 'role',
'columns' => array('rid' => 'rid'),
),
),
@@ -278,7 +278,7 @@ function user_schema() {
'columns' => array('uid' => 'uid'),
),
'role' => array(
- 'table' => 'roles',
+ 'table' => 'role',
'columns' => array('rid' => 'rid'),
),
),
@@ -356,11 +356,13 @@ function user_update_dependencies() {
'filter' => 7000,
);
- // user_update_7012() uses the file API, which relies on the {file_managed}
- // table, so it must run after system_update_7034(), which creates that
- // table.
+ // user_update_7012() uses the file API and inserts records into the
+ // {file_managed} table, so it therefore must run after system_update_7061(),
+ // which inserts files with specific IDs into the table and therefore relies
+ // on the table being empty (otherwise it would accidentally overwrite
+ // existing records).
$dependencies['user'][7012] = array(
- 'system' => 7034,
+ 'system' => 7061,
);
// user_update_7013() uses the file usage API, which relies on the
diff --git a/modules/user/user.module b/modules/user/user.module
index b239799..9637a71 100644
--- a/modules/user/user.module
+++ b/modules/user/user.module
@@ -32,7 +32,7 @@ define('USER_REGISTER_VISITORS', 1);
define('USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL', 2);
/**
- * Implement hook_help().
+ * Implements hook_help().
*/
function user_help($path, $arg) {
global $user;
@@ -501,12 +501,17 @@ function user_save($account, $edit = array(), $category = 'account') {
file_usage_delete($account->original->picture, 'user', 'user', $account->uid);
file_delete($account->original->picture);
}
+ // Save the picture object, if it is set. drupal_write_record() expects
+ // $account->picture to be a FID.
+ $picture = empty($account->picture) ? NULL : $account->picture;
$account->picture = empty($account->picture->fid) ? 0 : $account->picture->fid;
// Do not allow 'uid' to be changed.
$account->uid = $account->original->uid;
// Save changes to the user table.
$success = drupal_write_record('users', $account, 'uid');
+ // Restore the picture object.
+ $account->picture = $picture;
if ($success === FALSE) {
// The query failed - better to abort the save than risk further
// data loss.
@@ -589,16 +594,16 @@ function user_save($account, $edit = array(), $category = 'account') {
user_module_invoke('insert', $edit, $account, $category);
module_invoke_all('entity_insert', $account, 'user');
- // Save user roles.
- if (count($account->roles) > 1) {
+ // Save user roles. Skip built-in roles, and ones that were already saved
+ // to the database during hook calls.
+ $rids_to_skip = array_merge(array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID), db_query('SELECT rid FROM {users_roles} WHERE uid = :uid', array(':uid' => $account->uid))->fetchCol());
+ if ($rids_to_save = array_diff(array_keys($account->roles), $rids_to_skip)) {
$query = db_insert('users_roles')->fields(array('uid', 'rid'));
- foreach (array_keys($account->roles) as $rid) {
- if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
- $query->values(array(
- 'uid' => $account->uid,
- 'rid' => $rid,
- ));
- }
+ foreach ($rids_to_save as $rid) {
+ $query->values(array(
+ 'uid' => $account->uid,
+ 'rid' => $rid,
+ ));
}
$query->execute();
}
@@ -843,6 +848,26 @@ function user_is_blocked($name) {
->execute()->fetchObject();
}
+/**
+ * Checks if a user has a role.
+ *
+ * @param int $rid
+ * A role ID.
+ *
+ * @param object|null $account
+ * (optional) A user account. Defaults to the current user.
+ *
+ * @return bool
+ * TRUE if the user has the role, or FALSE if not.
+ */
+function user_has_role($rid, $account = NULL) {
+ if (!$account) {
+ $account = $GLOBALS['user'];
+ }
+
+ return isset($account->roles[$rid]);
+}
+
/**
* Implements hook_permission().
*/
@@ -2330,7 +2355,7 @@ function user_external_login_register($name, $module) {
*/
function user_pass_reset_url($account) {
$timestamp = REQUEST_TIME;
- return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE));
+ return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE));
}
/**
@@ -2352,7 +2377,7 @@ function user_pass_reset_url($account) {
*/
function user_cancel_url($account) {
$timestamp = REQUEST_TIME;
- return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE));
+ return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE));
}
/**
@@ -2372,12 +2397,33 @@ function user_cancel_url($account) {
* A UNIX timestamp, typically REQUEST_TIME.
* @param int $login
* The UNIX timestamp of the user's last login.
+ * @param int $uid
+ * The user ID of the user account.
*
* @return
* A string that is safe for use in URLs and SQL statements.
*/
-function user_pass_rehash($password, $timestamp, $login) {
- return drupal_hmac_base64($timestamp . $login, drupal_get_hash_salt() . $password);
+function user_pass_rehash($password, $timestamp, $login, $uid) {
+ // Backwards compatibility: Try to determine a $uid if one was not passed.
+ // (Since $uid is a required parameter to this function, a PHP warning will
+ // be generated if it's not provided, which is an indication that the calling
+ // code should be updated. But the code below will try to generate a correct
+ // hash in the meantime.)
+ if (!isset($uid)) {
+ $uids = db_query_range('SELECT uid FROM {users} WHERE pass = :password AND login = :login AND uid > 0', 0, 2, array(':password' => $password, ':login' => $login))->fetchCol();
+ // If exactly one user account matches the provided password and login
+ // timestamp, proceed with that $uid.
+ if (count($uids) == 1) {
+ $uid = reset($uids);
+ }
+ // Otherwise there is no safe hash to return, so return a random string
+ // that will never be treated as a valid token.
+ else {
+ return drupal_random_key();
+ }
+ }
+
+ return drupal_hmac_base64($timestamp . $login . $uid, drupal_get_hash_salt() . $password);
}
/**
@@ -2633,12 +2679,7 @@ function user_build_content($account, $view_mode = 'full', $langcode = NULL) {
$account->content = array();
// Allow modules to change the view mode.
- $context = array(
- 'entity_type' => 'user',
- 'entity' => $account,
- 'langcode' => $langcode,
- );
- drupal_alter('entity_view_mode', $view_mode, $context);
+ $view_mode = key(entity_view_mode_prepare('user', array($account->uid => $account), $view_mode, $langcode));
// Build fields content.
field_attach_prepare_view('user', array($account->uid => $account), $view_mode, $langcode);
@@ -3773,8 +3814,8 @@ function user_register_form($form, &$form_state) {
// inside the submit function interferes with form processing and breaks
// hook_form_alter().
$form['administer_users'] = array(
- '#type' => 'value',
- '#value' => $admin,
+ '#type' => 'value',
+ '#value' => $admin,
);
// If we aren't admin but already logged on, go to the user page instead.
diff --git a/modules/user/user.pages.inc b/modules/user/user.pages.inc
index 8ec2348..f21bd13 100644
--- a/modules/user/user.pages.inc
+++ b/modules/user/user.pages.inc
@@ -126,7 +126,7 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a
drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'));
drupal_goto('user/password');
}
- elseif ($account->uid && $timestamp >= $account->login && $timestamp <= $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) {
+ elseif ($account->uid && $timestamp >= $account->login && $timestamp <= $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)) {
// First stage is a confirmation form, then login
if ($action == 'login') {
// Set the new user.
@@ -359,7 +359,6 @@ function user_cancel_confirm_form($form, &$form_state, $account) {
$form['_account'] = array('#type' => 'value', '#value' => $account);
// Display account cancellation method selection, if allowed.
- $default_method = variable_get('user_cancel_method', 'user_cancel_block');
$admin_access = user_access('administer users');
$can_select_method = $admin_access || user_access('select account cancellation method');
$form['user_cancel_method'] = array(
@@ -523,7 +522,7 @@ function user_cancel_confirm($account, $timestamp = 0, $hashed_pass = '') {
// Basic validation of arguments.
if (isset($account->data['user_cancel_method']) && !empty($timestamp) && !empty($hashed_pass)) {
// Validate expiration and hashed password/login.
- if ($timestamp <= $current && $current - $timestamp < $timeout && $account->uid && $timestamp >= $account->login && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) {
+ if ($timestamp <= $current && $current - $timestamp < $timeout && $account->uid && $timestamp >= $account->login && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)) {
$edit = array(
'user_cancel_notify' => isset($account->data['user_cancel_notify']) ? $account->data['user_cancel_notify'] : variable_get('user_mail_status_canceled_notify', FALSE),
);
diff --git a/modules/user/user.test b/modules/user/user.test
index e2086d4..07be4c2 100644
--- a/modules/user/user.test
+++ b/modules/user/user.test
@@ -498,7 +498,7 @@ class UserPasswordResetTestCase extends DrupalWebTestCase {
// To attempt an expired password reset, create a password reset link as if
// its request time was 60 seconds older than the allowed limit of timeout.
$bogus_timestamp = REQUEST_TIME - variable_get('user_password_reset_timeout', 86400) - 60;
- $this->drupalGet("user/reset/$account->uid/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login));
+ $this->drupalGet("user/reset/$account->uid/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login, $account->uid));
$this->assertText(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'Expired password reset request rejected.');
}
@@ -519,6 +519,74 @@ class UserPasswordResetTestCase extends DrupalWebTestCase {
$this->assertFieldByName('name', $edit['name'], 'User name found.');
}
+ /**
+ * Make sure that users cannot forge password reset URLs of other users.
+ */
+ function testResetImpersonation() {
+ // Make sure user 1 has a valid password, so it does not interfere with the
+ // test user accounts that are created below.
+ $account = user_load(1);
+ user_save($account, array('pass' => user_password()));
+
+ // Create two identical user accounts except for the user name. They must
+ // have the same empty password, so we can't use $this->drupalCreateUser().
+ $edit = array();
+ $edit['name'] = $this->randomName();
+ $edit['mail'] = $edit['name'] . '@example.com';
+ $edit['status'] = 1;
+
+ $user1 = user_save(drupal_anonymous_user(), $edit);
+
+ $edit['name'] = $this->randomName();
+ $user2 = user_save(drupal_anonymous_user(), $edit);
+
+ // The password reset URL must not be valid for the second user when only
+ // the user ID is changed in the URL.
+ $reset_url = user_pass_reset_url($user1);
+ $attack_reset_url = str_replace("user/reset/$user1->uid", "user/reset/$user2->uid", $reset_url);
+ $this->drupalGet($attack_reset_url);
+ $this->assertNoText($user2->name, 'The invalid password reset page does not show the user name.');
+ $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.');
+ $this->assertText('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.');
+
+ // When legacy code calls user_pass_rehash() without providing the $uid
+ // parameter, neither password reset URL should be valid since it is
+ // impossible for the system to determine which user account the token was
+ // intended for.
+ $timestamp = REQUEST_TIME;
+ // Pass an explicit NULL for the $uid parameter of user_pass_rehash()
+ // rather than not passing it at all, to avoid triggering PHP warnings in
+ // the test.
+ $reset_url_token = user_pass_rehash($user1->pass, $timestamp, $user1->login, NULL);
+ $reset_url = url("user/reset/$user1->uid/$timestamp/$reset_url_token", array('absolute' => TRUE));
+ $this->drupalGet($reset_url);
+ $this->assertNoText($user1->name, 'The invalid password reset page does not show the user name.');
+ $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.');
+ $this->assertText('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.');
+ $attack_reset_url = str_replace("user/reset/$user1->uid", "user/reset/$user2->uid", $reset_url);
+ $this->drupalGet($attack_reset_url);
+ $this->assertNoText($user2->name, 'The invalid password reset page does not show the user name.');
+ $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.');
+ $this->assertText('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.');
+
+ // To verify that user_pass_rehash() never returns a valid result in the
+ // above situation (even if legacy code also called it to attempt to
+ // validate the token, rather than just to generate the URL), check that a
+ // second call with the same parameters produces a different result.
+ $new_reset_url_token = user_pass_rehash($user1->pass, $timestamp, $user1->login, NULL);
+ $this->assertNotEqual($reset_url_token, $new_reset_url_token);
+
+ // However, when the duplicate account is removed, the password reset URL
+ // should be valid.
+ user_delete($user2->uid);
+ $reset_url_token = user_pass_rehash($user1->pass, $timestamp, $user1->login, NULL);
+ $reset_url = url("user/reset/$user1->uid/$timestamp/$reset_url_token", array('absolute' => TRUE));
+ $this->drupalGet($reset_url);
+ $this->assertText($user1->name, 'The valid password reset page shows the user name.');
+ $this->assertUrl($reset_url, array(), 'The user remains on the password reset login page.');
+ $this->assertNoText('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.');
+ }
+
}
/**
@@ -558,7 +626,7 @@ class UserCancelTestCase extends DrupalWebTestCase {
// Attempt bogus account cancellation request confirmation.
$timestamp = $account->login;
- $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login));
+ $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid));
$this->assertResponse(403, 'Bogus cancelling request rejected.');
$account = user_load($account->uid);
$this->assertTrue($account->status == 1, 'User account was not canceled.');
@@ -631,14 +699,14 @@ class UserCancelTestCase extends DrupalWebTestCase {
// Attempt bogus account cancellation request confirmation.
$bogus_timestamp = $timestamp + 60;
- $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login));
+ $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login, $account->uid));
$this->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'Bogus cancelling request rejected.');
$account = user_load($account->uid);
$this->assertTrue($account->status == 1, 'User account was not canceled.');
// Attempt expired account cancellation request confirmation.
$bogus_timestamp = $timestamp - 86400 - 60;
- $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login));
+ $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login, $account->uid));
$this->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'Expired cancel account request rejected.');
$accounts = user_load_multiple(array($account->uid), array('status' => 1));
$this->assertTrue(reset($accounts), 'User account was not canceled.');
@@ -675,7 +743,7 @@ class UserCancelTestCase extends DrupalWebTestCase {
$this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), 'Account cancellation request mailed message displayed.');
// Confirm account cancellation request.
- $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login));
+ $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid));
$account = user_load($account->uid, TRUE);
$this->assertTrue($account->status == 0, 'User has been blocked.');
@@ -713,7 +781,7 @@ class UserCancelTestCase extends DrupalWebTestCase {
$this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), 'Account cancellation request mailed message displayed.');
// Confirm account cancellation request.
- $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login));
+ $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid));
$account = user_load($account->uid, TRUE);
$this->assertTrue($account->status == 0, 'User has been blocked.');
@@ -763,7 +831,7 @@ class UserCancelTestCase extends DrupalWebTestCase {
$this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), 'Account cancellation request mailed message displayed.');
// Confirm account cancellation request.
- $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login));
+ $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid));
$this->assertFalse(user_load($account->uid, TRUE), 'User is not found in the database.');
// Confirm that user's content has been attributed to anonymous user.
@@ -827,7 +895,7 @@ class UserCancelTestCase extends DrupalWebTestCase {
$this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), 'Account cancellation request mailed message displayed.');
// Confirm account cancellation request.
- $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login));
+ $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid));
$this->assertFalse(user_load($account->uid, TRUE), 'User is not found in the database.');
// Confirm that user's content has been deleted.
@@ -1127,6 +1195,17 @@ class UserPictureTestCase extends DrupalWebTestCase {
$pic_path2 = $this->saveUserPicture($image);
$this->assertNotEqual($pic_path, $pic_path2, 'Filename of second picture is different.');
+
+ // Check if user picture has a valid file ID after saving the user.
+ $account = user_load($this->user->uid, TRUE);
+ $this->assertTrue(is_object($account->picture), 'User picture object is valid after user load.');
+ $this->assertNotNull($account->picture->fid, 'User picture object has a FID after user load.');
+ $this->assertTrue(is_file($account->picture->uri), 'File is located in proper directory after user load.');
+ user_save($account);
+ // Verify that the user save does not destroy the user picture object.
+ $this->assertTrue(is_object($account->picture), 'User picture object is valid after user save.');
+ $this->assertNotNull($account->picture->fid, 'User picture object has a FID after user save.');
+ $this->assertTrue(is_file($account->picture->uri), 'File is located in proper directory after user save.');
}
}
diff --git a/profiles/minimal/minimal.info b/profiles/minimal/minimal.info
index 8125179..912fe1e 100644
--- a/profiles/minimal/minimal.info
+++ b/profiles/minimal/minimal.info
@@ -5,8 +5,8 @@ core = 7.x
dependencies[] = block
dependencies[] = dblog
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/profiles/standard/standard.info b/profiles/standard/standard.info
index feb02b3..347c069 100644
--- a/profiles/standard/standard.info
+++ b/profiles/standard/standard.info
@@ -24,8 +24,8 @@ dependencies[] = field_ui
dependencies[] = file
dependencies[] = rdf
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info b/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info
index 39a1554..1889a48 100644
--- a/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info
+++ b/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info
@@ -6,8 +6,8 @@ core = 7.x
hidden = TRUE
files[] = drupal_system_listing_compatible_test.test
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info b/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info
index 10492ce..7fc93a2 100644
--- a/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info
+++ b/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info
@@ -8,8 +8,8 @@ version = VERSION
core = 6.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/profiles/testing/testing.info b/profiles/testing/testing.info
index 1b5b5b8..d8f0e8a 100644
--- a/profiles/testing/testing.info
+++ b/profiles/testing/testing.info
@@ -4,8 +4,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/robots.txt b/robots.txt
index 6f20eaf..ff9e286 100644
--- a/robots.txt
+++ b/robots.txt
@@ -12,9 +12,6 @@
#
# For more information about the robots.txt standard, see:
# http://www.robotstxt.org/robotstxt.html
-#
-# For syntax checking, see:
-# http://www.frobee.com/robots-txt-check
User-agent: *
Crawl-delay: 10
diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh
index 189d7f2..9078168 100755
--- a/scripts/run-tests.sh
+++ b/scripts/run-tests.sh
@@ -419,9 +419,20 @@ function simpletest_script_get_test_list() {
else {
if ($args['class']) {
// Check for valid class names.
- foreach ($args['test_names'] as $class_name) {
- if (in_array($class_name, $all_tests)) {
- $test_list[] = $class_name;
+ $test_list = array();
+ foreach ($args['test_names'] as $test_class) {
+ if (class_exists($test_class)) {
+ $test_list[] = $test_class;
+ }
+ else {
+ $groups = simpletest_test_get_all();
+ $all_classes = array();
+ foreach ($groups as $group) {
+ $all_classes = array_merge($all_classes, array_keys($group));
+ }
+ simpletest_script_print_error('Test class not found: ' . $test_class);
+ simpletest_script_print_alternatives($test_class, $all_classes, 6);
+ exit(1);
}
}
}
@@ -444,9 +455,12 @@ function simpletest_script_get_test_list() {
// Check for valid group names and get all valid classes in group.
foreach ($args['test_names'] as $group_name) {
if (isset($groups[$group_name])) {
- foreach ($groups[$group_name] as $class_name => $info) {
- $test_list[] = $class_name;
- }
+ $test_list = array_merge($test_list, array_keys($groups[$group_name]));
+ }
+ else {
+ simpletest_script_print_error('Test group not found: ' . $group_name);
+ simpletest_script_print_alternatives($group_name, array_keys($groups));
+ exit(1);
}
}
}
@@ -674,3 +688,37 @@ function simpletest_script_color_code($status) {
}
return 0; // Default formatting.
}
+
+/**
+ * Prints alternative test names.
+ *
+ * Searches the provided array of string values for close matches based on the
+ * Levenshtein algorithm.
+ *
+ * @see http://php.net/manual/en/function.levenshtein.php
+ *
+ * @param string $string
+ * A string to test.
+ * @param array $array
+ * A list of strings to search.
+ * @param int $degree
+ * The matching strictness. Higher values return fewer matches. A value of
+ * 4 means that the function will return strings from $array if the candidate
+ * string in $array would be identical to $string by changing 1/4 or fewer of
+ * its characters.
+ */
+function simpletest_script_print_alternatives($string, $array, $degree = 4) {
+ $alternatives = array();
+ foreach ($array as $item) {
+ $lev = levenshtein($string, $item);
+ if ($lev <= strlen($item) / $degree || FALSE !== strpos($string, $item)) {
+ $alternatives[] = $item;
+ }
+ }
+ if (!empty($alternatives)) {
+ simpletest_script_print(" Did you mean?\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
+ foreach ($alternatives as $alternative) {
+ simpletest_script_print(" - $alternative\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
+ }
+ }
+}
diff --git a/themes/bartik/bartik.info b/themes/bartik/bartik.info
index c394ebd..d56742b 100644
--- a/themes/bartik/bartik.info
+++ b/themes/bartik/bartik.info
@@ -34,8 +34,8 @@ regions[footer] = Footer
settings[shortcut_module_link] = 0
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/themes/bartik/css/style-rtl.css b/themes/bartik/css/style-rtl.css
index 3bb02ca..991edfc 100644
--- a/themes/bartik/css/style-rtl.css
+++ b/themes/bartik/css/style-rtl.css
@@ -225,10 +225,10 @@ ul.action-links li a {
/* Animated throbber */
html.js input.form-autocomplete {
- background-position: 1% 4px;
+ background-position: 1% center;
}
html.js input.throbbing {
- background-position: 1% -16px;
+ background-position: 1% center;
}
/* Comment form */
diff --git a/themes/bartik/css/style.css b/themes/bartik/css/style.css
index c40755b..8426e56 100644
--- a/themes/bartik/css/style.css
+++ b/themes/bartik/css/style.css
@@ -704,7 +704,6 @@ ul.links {
}
.comment .submitted .comment-permalink {
font-size: 0.786em;
- text-transform: lowercase;
}
.comment .content {
font-size: 0.929em;
@@ -1326,14 +1325,6 @@ input.form-button-disabled:active,
color: #717171;
}
-/* Animated throbber */
-html.js input.form-autocomplete {
- background-position: 100% 4px; /* LTR */
-}
-html.js input.throbbing {
- background-position: 100% -16px; /* LTR */
-}
-
/* Comment form */
.comment-form label {
float: left; /* LTR */
diff --git a/themes/garland/garland.info b/themes/garland/garland.info
index 42533b4..4cbe921 100644
--- a/themes/garland/garland.info
+++ b/themes/garland/garland.info
@@ -7,8 +7,8 @@ stylesheets[all][] = style.css
stylesheets[print][] = print.css
settings[garland_width] = fluid
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/themes/seven/seven.info b/themes/seven/seven.info
index a0db650..9f9b6fd 100644
--- a/themes/seven/seven.info
+++ b/themes/seven/seven.info
@@ -13,8 +13,8 @@ regions[page_bottom] = Page bottom
regions[sidebar_first] = First sidebar
regions_hidden[] = sidebar_first
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"
diff --git a/themes/seven/style.css b/themes/seven/style.css
index 56a6094..c52d661 100644
--- a/themes/seven/style.css
+++ b/themes/seven/style.css
@@ -709,12 +709,7 @@ select.form-select:focus {
color: #000;
border-color: #ace;
}
-html.js input.form-autocomplete {
- background-position: 100% 4px;
-}
-html.js input.throbbing {
- background-position: 100% -16px;
-}
+
ul.action-links {
margin: 1em 0;
padding: 0 20px 0 20px; /* LTR */
diff --git a/themes/stark/stark.info b/themes/stark/stark.info
index f762e44..70a0680 100644
--- a/themes/stark/stark.info
+++ b/themes/stark/stark.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
stylesheets[all][] = layout.css
-; Information added by Drupal.org packaging script on 2014-10-15
-version = "7.32"
+; Information added by Drupal.org packaging script on 2015-05-07
+version = "7.37"
project = "drupal"
-datestamp = "1413387510"
+datestamp = "1430973154"