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' => '
    1. ', + 'allowed_html' => '
        1. ', '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"