diff -Naur drupal-7.41/.editorconfig drupal-7.66/.editorconfig --- drupal-7.41/.editorconfig 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/.editorconfig 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,14 @@ +# Drupal editor configuration normalization +# @see http://editorconfig.org/ + +# This is the top-most .editorconfig file; do not search in parent directories. +root = true + +# All files. +[*] +end_of_line = LF +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff -Naur drupal-7.41/.htaccess drupal-7.66/.htaccess --- drupal-7.41/.htaccess 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/.htaccess 2019-04-17 22:20:46.000000000 +0200 @@ -3,8 +3,13 @@ # # Protect files and directories from prying eyes. - - Order allow,deny + + + Require all denied + + + Order allow,deny + # Don't show directory listings for URLs which map to a directory. @@ -80,7 +85,7 @@ # If you do not have mod_rewrite installed, you should remove these # directories from your webroot or otherwise protect them from being # downloaded. - RewriteRule "(^|/)\." - [F] + RewriteRule "/\.|^\.(?!well-known/)" - [F] # If your site can be accessed both with and without the 'www.' prefix, you # can use one of the following settings to redirect users to your preferred diff -Naur drupal-7.41/CHANGELOG.txt drupal-7.66/CHANGELOG.txt --- drupal-7.41/CHANGELOG.txt 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/CHANGELOG.txt 2019-04-17 22:20:46.000000000 +0200 @@ -1,3 +1,244 @@ +Drupal 7.xx, xxxx-xx-xx (development version) +----------------------- + +Drupal 7.66, 2019-04-17 +----------------------- +- Fixed security issues: + - SA-CORE-2019-006 + +Drupal 7.65, 2019-03-20 +----------------------- +- Fixed security issues: + - SA-CORE-2019-004 + +Drupal 7.64, 2019-02-06 +----------------------- +- [regression] Unset the 'host' header in drupal_http_request() during redirect +- Fixed: 7.x does not have Phar protection and Phar tests are failing on Drupal 7 +- Fixed: Notice: Undefined index: display_field in file_field_widget_value() (line 582 of /module/file/file.field.inc) +- Performance improvement: Registry rebuild should not parse the same file twice in the same request +- Fixed _registry_update() to clear caches after transaction is committed + +Drupal 7.63, 2019-01-16 +----------------------- +- Fixed a fatal error for some Drush users introduced by SA-CORE-2019-002. + +Drupal 7.62, 2019-01-15 +----------------------- +- Fixed security issues: + - SA-CORE-2019-001 + - SA-CORE-2019-002 + +Drupal 7.61, 2018-11-07 +----------------------- +- File upload validation functions and hook_file_validate() implementations are + now always passed the correct file URI. +- The default form cache expiration of 6 hours is now configurable (API + addition: https://www.drupal.org/node/2857751). +- Allowed callers of drupal_http_request() to optionally specify an explicit + Host header. +- Allowed the + character to appear in usernames. +- PHP 7.2: Fixed Archive_Tar incompatibility. +- PHP 7.2: Removed deprecated function each(). +- PHP 7.2: Avoid count() calls on uncountable variables. +- PHP 7.2: Removed deprecated create_function() call. +- PHP 7.2: Make sure variables are arrays in theme_links(). +- Fixed theme-settings.php not being loaded on cached forms +- Fixed problem with IE11 & Chrome(PointerEvents enabled) & some Firefox scroll to the top of the page after dragging the bottom item with jquery 1.5 <-> 1.11 + +Drupal 7.60, 2018-10-18 +------------------------ +- Fixed security issues. See SA-CORE-2018-006. + +Drupal 7.59, 2018-04-25 +----------------------- +- Fixed security issues (remote code execution). See SA-CORE-2018-004. + +Drupal 7.58, 2018-03-28 +----------------------- +- Fixed security issues (remote code execution). See SA-CORE-2018-002. + +Drupal 7.57, 2018-02-21 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2018-001. + +Drupal 7.56, 2017-06-21 +----------------------- +- Fixed security issues (access bypass). See SA-CORE-2017-003. + +Drupal 7.55, 2017-06-07 +----------------------- +- Fixed incompatibility with PHP versions 7.0.19 and 7.1.5 due to duplicate + DATE_RFC7231 definition. +- Made Drupal core pass all automated tests on PHP 7.1. +- Allowed services such as Let's Encrypt to work with Drupal on Apache, by + making Drupal's .htaccess file allow access to the .well-known directory + defined by RFC 5785. +- Made new Drupal sites work correctly on Apache 2.4 when the mod_access_compat + Apache module is disabled. +- Fixed Drupal's URL-generating functions to always encode '[' and ']' so that + the URLs will pass HTML5 validation. +- Various additional bug fixes. +- Various API documentation improvements. +- Additional automated test coverage. + +Drupal 7.54, 2017-02-01 +----------------------- +- Modules are now able to define theme engines (API addition: + https://www.drupal.org/node/2826480). +- Logging of searches can now be disabled (new option in the administrative + interface). +- Added menu tree render structure to (pre-)process hooks for theme_menu_tree() + (API addition: https://www.drupal.org/node/2827134). +- Added new function for determining whether an HTTPS request is being served + (API addition: https://www.drupal.org/node/2824590). +- Fixed incorrect default value for short and medium date formats on the date + type configuration page. +- File validation error message is now removed after subsequent upload of valid + file. +- Numerous bug fixes. +- Numerous API documentation improvements. +- Additional performance improvements. +- Additional automated test coverage. + +Drupal 7.53, 2016-12-07 +----------------------- +- Fixed drag and drop support on newer Chrome/IE 11+ versions after 7.51 update + when jQuery is updated to 1.7-1.11.0. + +Drupal 7.52, 2016-11-16 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2016-005. + +Drupal 7.51, 2016-10-05 +----------------------- +- The Update module now also checks for updates to a disabled theme that is + used as an admin theme. +- Exceptions thrown in dblog_watchdog() are now caught and ignored. +- Clarified the warning that appears when modules are missing or have moved. +- Log messages are now XSS filtered on display. +- Draggable tables now work on touch screen devices. +- Added a setting for allowing double underscores in CSS identifiers + (https://www.drupal.org/node/2810369). +- If a user navigates away from a page while an Ajax request is running they + will no longer get an error message saying "An Ajax HTTP request terminated + abnormally". +- The system_region_list() API function now takes an optional third parameter + which allows region name translations to be skipped when they are not needed + (API addition: https://www.drupal.org/node/2810365). +- Numerous performance improvements. +- Numerous bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. + +Drupal 7.50, 2016-07-07 +----------------------- +- Added a new "administer fields" permission for trusted users, which is + required in addition to other permissions to use the field UI + (https://www.drupal.org/node/2483307). +- Added clickjacking protection to Drupal core by setting the X-Frame-Options + header to SAMEORIGIN by default (https://www.drupal.org/node/2735873). +- Added support for full UTF-8 (emojis, Asian symbols, mathematical symbols) on + MySQL and other database drivers when the site and database are configured to + allow it (https://www.drupal.org/node/2761183). +- Improved performance by avoiding a re-scan of directories when a file is + missing; instead, trigger a PHP warning (minor API change: + https://www.drupal.org/node/2581445). +- Made it possible to use any PHP callable in Ajax form callbacks, form API + form-building functions, and form API wrapper callbacks (API addition: + https://www.drupal.org/node/2761169). +- Fixed that following a password reset link while logged in leaves users unable + to change their password (minor user interface change: + https://www.drupal.org/node/2759023). +- Implemented various fixes for automated test failures on PHP 5.4+ and PHP 7. + Drupal core automated tests now pass in these environments. +- Improved support for PHP 7 by fixing various problems. +- Fixed various bugs with PHP 5.5+ imagerotate(), including when incorrect + color indices are passed in. +- Fixed a regression introduced in Drupal 7.43 that allowed files uploaded by + anonymous users to be lost after form validation errors, and that also caused + regressions with certain contributed modules. +- Fixed a regression introduced in Drupal 7.36 which caused the default value + of hidden textarea fields to be ignored. +- Fixed robots.txt to allow search engines to access CSS, JavaScript and image + files. +- Changed wording on the Update Manager settings page to clarify that the + option to check for disabled module updates also applies to uninstalled + modules (administrative-facing translatable string change). +- Changed the help text when editing menu links and configuring URL redirect + actions so that it does not reference "Drupal" or the drupal.org website + (administrative-facing translatable string change). +- Fixed the locale safety check that is used to ensure that translations are + safe to allow for tokens in the href/src attributes of translated strings. +- Fixed that URL generation only works on port 80 when using domain based + language negotation. +- Made method="get" forms work inside the administrative overlay. The fix adds + a new hidden field to these forms when they appear inside the overlay (minor + data structure change). +- Increased maxlength of menu link title input fields in the node form and + menu link form from 128 to 255 characters. +- Removed meaningless post-check=0 and pre-check=0 cache control headers from + Drupal HTTP responses. +- Added a .editorconfig file to auto-configure editors that support it. +- Added --directory option to run-tests.sh for easier test discovery of all + tests within a project. +- Made run-tests.sh exit with a failure code when there are test fails or + problems running the script. +- Fixed that cookies from previous tests are still present when a new test + starts in DrupalWebTestCase. +- Improved performance of queries on the {authmap} database table. +- Fixed handling of missing files and functions inside the registry. +- Fixed Ajax handling for tableselect form elements that use checkboxes. +- Fixed a bug which caused ip_address() to return nothing when the client IP + address and proxy IP address are the same. +- Added a new option to format_xml_elements() to allow for already encoded + values. +- Changed the {history} table's node ID field to be an unsigned integer, to + match the same field in the {node} table and to prevent errors with very + large node IDs. +- Added an explicit page callback to the "admin/people/create" menu item in the + User module (minor data structure change). Previously this automatically + inherited the page callback from the parent "admin/people" menu item, which + broke contributed modules that override the "admin/people" page. +- Numerous small bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. + +Drupal 7.44, 2016-06-15 +----------------------- +- Fixed security issues (privilege escalation). See SA-CORE-2016-002. + +Drupal 7.43, 2016-02-24 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2016-001. + +Drupal 7.42, 2016-02-03 +----------------------- +- Stopped invoking hook_flush_caches() on every cron run, since some modules + use that hook for expensive operations that are only needed on cache clears. +- Changed the default .htaccess and web.config to block Composer-related files. +- Added static caching to module_load_include() to improve performance. +- Fixed double-encoding bugs in select field widgets provided by the Options + module. The fix deprecates the 'strip_tags' property on option widgets and + replaces it with a new 'strip_tags_and_unescape' property (minor data + structure change). +- Improved MySQL 5.7 support by changing the MySQL database driver to stop + using the ANSI SQL mode alias, which has different meanings for different + MySQL versions. +- Fixed a regression introduced in Drupal 7.39 which prevented autocomplete + functionality from working on servers that are not configured to + automatically recognize index.php. +- Updated the Archive_Tar PEAR package to the latest 1.4.0 release, to fix bugs + with tar file handling on various operating systems. +- Fixed fatal errors on node preview when a field is displayed in the node + teaser but hidden in the full node view. The fix removes a + field_attach_prepare_view() call from the node_preview() function since it is + redundant with one in the node preview theme layer. +- Improved the description of the "Trimmed" format option on text fields + (translatable string change, and minor UI and data structure change). +- Numerous small bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. Drupal 7.41, 2015-10-21 ----------------------- diff -Naur drupal-7.41/MAINTAINERS.txt drupal-7.66/MAINTAINERS.txt --- drupal-7.41/MAINTAINERS.txt 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/MAINTAINERS.txt 2019-04-17 22:20:46.000000000 +0200 @@ -12,7 +12,10 @@ - Dries Buytaert 'dries' https://www.drupal.org/u/dries - Angela Byron 'webchick' https://www.drupal.org/u/webchick +- Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx - David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein +- Stefan Ruijsenaars 'stefan.r' https://www.drupal.org/u/stefanr-0 +- (provisional) Pol Dellaiera 'Pol' https://www.drupal.org/u/pol Component maintainers @@ -42,10 +45,9 @@ - Derek Wright 'dww' https://www.drupal.org/u/dww Database system -- Larry Garfield 'Crell' https://www.drupal.org/u/crell +- ? - MySQL driver - - Larry Garfield 'Crell' https://www.drupal.org/u/crell - David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss - PostgreSQL driver @@ -143,7 +145,6 @@ Node Access - Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman - Ken Rickard 'agentrickard' https://www.drupal.org/u/agentrickard -- Jess Myrbo 'xjm' https://www.drupal.org/u/xjm Security team @@ -266,7 +267,6 @@ - ? Taxonomy module -- Jess Myrbo 'xjm' https://www.drupal.org/u/xjm - Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch - Benjamin Doherty 'bangpound' https://www.drupal.org/u/bangpound diff -Naur drupal-7.41/includes/ajax.inc drupal-7.66/includes/ajax.inc --- drupal-7.41/includes/ajax.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/ajax.inc 2019-04-17 22:20:46.000000000 +0200 @@ -394,7 +394,7 @@ if (!empty($form_state['triggering_element'])) { $callback = $form_state['triggering_element']['#ajax']['callback']; } - if (!empty($callback) && function_exists($callback)) { + if (!empty($callback) && is_callable($callback)) { $result = $callback($form, $form_state); if (!(is_array($result) && isset($result['#type']) && $result['#type'] == 'ajax')) { diff -Naur drupal-7.41/includes/bootstrap.inc drupal-7.66/includes/bootstrap.inc --- drupal-7.41/includes/bootstrap.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/bootstrap.inc 2019-04-17 22:20:46.000000000 +0200 @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '7.41'); +define('VERSION', '7.66'); /** * Core API compatibility. @@ -254,8 +254,13 @@ * http://tools.ietf.org/html/rfc7231#section-7.1.1.1 * * Example: Sun, 06 Nov 1994 08:49:37 GMT + * + * This constant was introduced in PHP 7.0.19 and PHP 7.1.5 but needs to be + * defined by Drupal for earlier PHP versions. */ -define('DATE_RFC7231', 'D, d M Y H:i:s \G\M\T'); +if (!defined('DATE_RFC7231')) { + 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. @@ -699,6 +704,19 @@ // Set sane locale settings, to ensure consistent string, dates, times and // numbers handling. setlocale(LC_ALL, 'C'); + + // PHP's built-in phar:// stream wrapper is not sufficiently secure. Override + // it with a more secure one, which requires PHP 5.3.3. For lower versions, + // unregister the built-in one without replacing it. Sites needing phar + // support for lower PHP versions must implement hook_stream_wrappers() to + // register their desired implementation. + if (in_array('phar', stream_get_wrappers(), TRUE)) { + stream_wrapper_unregister('phar'); + if (version_compare(PHP_VERSION, '5.3.3', '>=')) { + include_once DRUPAL_ROOT . '/includes/file.phar.inc'; + file_register_phar_wrapper(); + } + } } /** @@ -719,6 +737,16 @@ } /** + * Checks whether an HTTPS request is being served. + * + * @return bool + * TRUE if the request is HTTPS, FALSE otherwise. + */ +function drupal_is_https() { + return isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on'; +} + +/** * Sets the base URL, cookie domain, and session name from configuration. */ function drupal_settings_initialize() { @@ -731,7 +759,7 @@ if (file_exists(DRUPAL_ROOT . '/' . conf_path() . '/settings.php')) { include_once DRUPAL_ROOT . '/' . conf_path() . '/settings.php'; } - $is_https = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on'; + $is_https = drupal_is_https(); if (isset($base_url)) { // Parse fixed base URL from settings.php. @@ -828,14 +856,21 @@ * @param $filename * The filename of the item if it is to be set explicitly rather * than by consulting the database. + * @param bool $trigger_error + * Whether to trigger an error when a file is missing or has unexpectedly + * moved. This defaults to TRUE, but can be set to FALSE by calling code that + * merely wants to check whether an item exists in the filesystem. * * @return * The filename of the requested item or NULL if the item is not found. */ -function drupal_get_filename($type, $name, $filename = NULL) { +function drupal_get_filename($type, $name, $filename = NULL, $trigger_error = TRUE) { + // The $files static variable will hold the locations of all requested files. + // We can be sure that any file listed in this static variable actually + // exists as all additions have gone through a file_exists() check. // The location of files will not change during the request, so do not use // drupal_static(). - static $files = array(), $dirs = array(); + static $files = array(); // Profiles are a special case: they have a fixed location and naming. if ($type == 'profile') { @@ -847,64 +882,296 @@ } if (!empty($filename) && file_exists($filename)) { + // Prime the static cache with the provided filename. $files[$type][$name] = $filename; } elseif (isset($files[$type][$name])) { - // nothing + // This item had already been found earlier in the request, either through + // priming of the static cache (for example, in system_list()), through a + // lookup in the {system} table, or through a file scan (cached or not). Do + // nothing. } - // Verify that we have an active database connection, before querying - // the database. This is required because this function is called both - // before we have a database connection (i.e. during installation) and - // when a database connection fails. else { + // Look for the filename listed in the {system} table. Verify that we have + // an active database connection before doing so, since this function is + // called both before we have a database connection (i.e. during + // installation) and when a database connection fails. + $database_unavailable = TRUE; try { if (function_exists('db_query')) { $file = db_query("SELECT filename FROM {system} WHERE name = :name AND type = :type", array(':name' => $name, ':type' => $type))->fetchField(); if ($file !== FALSE && file_exists(DRUPAL_ROOT . '/' . $file)) { $files[$type][$name] = $file; } + $database_unavailable = FALSE; } } catch (Exception $e) { // The database table may not exist because Drupal is not yet installed, - // or the database might be down. We have a fallback for this case so we - // hide the error completely. + // the database might be down, or we may have done a non-database cache + // flush while $conf['page_cache_without_database'] = TRUE and + // $conf['page_cache_invoke_hooks'] = TRUE. We have a fallback for these + // cases so we hide the error completely. } - // Fallback to searching the filesystem if the database could not find the - // file or the file returned by the database is not found. + // Fall back to searching the filesystem if the database could not find the + // file or the file does not exist at the path returned by the database. if (!isset($files[$type][$name])) { - // We have a consistent directory naming: modules, themes... - $dir = $type . 's'; - if ($type == 'theme_engine') { - $dir = 'themes/engines'; - $extension = 'engine'; - } - elseif ($type == 'theme') { - $extension = 'info'; - } - else { - $extension = $type; - } + $files[$type][$name] = _drupal_get_filename_fallback($type, $name, $trigger_error, $database_unavailable); + } + } - if (!isset($dirs[$dir][$extension])) { - $dirs[$dir][$extension] = TRUE; - if (!function_exists('drupal_system_listing')) { - require_once DRUPAL_ROOT . '/includes/common.inc'; - } - // Scan the appropriate directories for all files with the requested - // extension, not just the file we are currently looking for. This - // prevents unnecessary scans from being repeated when this function is - // called more than once in the same page request. - $matches = drupal_system_listing("/^" . DRUPAL_PHP_FUNCTION_PATTERN . "\.$extension$/", $dir, 'name', 0); - foreach ($matches as $matched_name => $file) { - $files[$type][$matched_name] = $file->uri; + if (isset($files[$type][$name])) { + return $files[$type][$name]; + } +} + +/** + * Performs a cached file system scan as a fallback when searching for a file. + * + * This function looks for the requested file by triggering a file scan, + * caching the new location if the file has moved and caching the miss + * if the file is missing. If a file had been marked as missing in a previous + * file scan, or if it has been marked as moved and is still in the last known + * location, no new file scan will be performed. + * + * @param string $type + * The type of the item (theme, theme_engine, module, profile). + * @param string $name + * The name of the item for which the filename is requested. + * @param bool $trigger_error + * Whether to trigger an error when a file is missing or has unexpectedly + * moved. + * @param bool $database_unavailable + * Whether this function is being called because the Drupal database could + * not be queried for the file's location. + * + * @return + * The filename of the requested item or NULL if the item is not found. + * + * @see drupal_get_filename() + */ +function _drupal_get_filename_fallback($type, $name, $trigger_error, $database_unavailable) { + $file_scans = &_drupal_file_scan_cache(); + $filename = NULL; + + // If the cache indicates that the item is missing, or we can verify that the + // item exists in the location the cache says it exists in, use that. + if (isset($file_scans[$type][$name]) && ($file_scans[$type][$name] === FALSE || file_exists($file_scans[$type][$name]))) { + $filename = $file_scans[$type][$name]; + } + // Otherwise, perform a new file scan to find the item. + else { + $filename = _drupal_get_filename_perform_file_scan($type, $name); + // Update the static cache, and mark the persistent cache for updating at + // the end of the page request. See drupal_file_scan_write_cache(). + $file_scans[$type][$name] = $filename; + $file_scans['#write_cache'] = TRUE; + } + + // If requested, trigger a user-level warning about the missing or + // unexpectedly moved file. If the database was unavailable, do not trigger a + // warning in the latter case, though, since if the {system} table could not + // be queried there is no way to know if the location found here was + // "unexpected" or not. + if ($trigger_error) { + $error_type = $filename === FALSE ? 'missing' : 'moved'; + if ($error_type == 'missing' || !$database_unavailable) { + _drupal_get_filename_fallback_trigger_error($type, $name, $error_type); + } + } + + // The cache stores FALSE for files that aren't found (to be able to + // distinguish them from files that have not yet been searched for), but + // drupal_get_filename() expects NULL for these instead, so convert to NULL + // before returning. + if ($filename === FALSE) { + $filename = NULL; + } + return $filename; +} + +/** + * Returns the current list of cached file system scan results. + * + * @return + * An associative array tracking the most recent file scan results for all + * files that have had scans performed. The keys are the type and name of the + * item that was searched for, and the values can be either: + * - Boolean FALSE if the item was not found in the file system. + * - A string pointing to the location where the item was found. + */ +function &_drupal_file_scan_cache() { + $file_scans = &drupal_static(__FUNCTION__, array()); + + // The file scan results are stored in a persistent cache (in addition to the + // static cache) but because this function can be called before the + // persistent cache is available, we must merge any items that were found + // earlier in the page request into the results from the persistent cache. + if (!isset($file_scans['#cache_merge_done'])) { + try { + if (function_exists('cache_get')) { + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + if (!empty($cache->data)) { + // File scan results from the current request should take precedence + // over the results from the persistent cache, since they are newer. + $file_scans = drupal_array_merge_deep($cache->data, $file_scans); } + // Set a flag to indicate that the persistent cache does not need to be + // merged again. + $file_scans['#cache_merge_done'] = TRUE; } } + catch (Exception $e) { + // Hide the error. + } } - if (isset($files[$type][$name])) { - return $files[$type][$name]; + return $file_scans; +} + +/** + * Performs a file system scan to search for a system resource. + * + * @param $type + * The type of the item (theme, theme_engine, module, profile). + * @param $name + * The name of the item for which the filename is requested. + * + * @return + * The filename of the requested item or FALSE if the item is not found. + * + * @see drupal_get_filename() + * @see _drupal_get_filename_fallback() + */ +function _drupal_get_filename_perform_file_scan($type, $name) { + // The location of files will not change during the request, so do not use + // drupal_static(). + static $dirs = array(), $files = array(); + + // We have a consistent directory naming: modules, themes... + $dir = $type . 's'; + if ($type == 'theme_engine') { + $dir = 'themes/engines'; + $extension = 'engine'; + } + elseif ($type == 'theme') { + $extension = 'info'; + } + else { + $extension = $type; + } + + // Check if we had already scanned this directory/extension combination. + if (!isset($dirs[$dir][$extension])) { + // Log that we have now scanned this directory/extension combination + // into a static variable so as to prevent unnecessary file scans. + $dirs[$dir][$extension] = TRUE; + if (!function_exists('drupal_system_listing')) { + require_once DRUPAL_ROOT . '/includes/common.inc'; + } + // Scan the appropriate directories for all files with the requested + // extension, not just the file we are currently looking for. This + // prevents unnecessary scans from being repeated when this function is + // called more than once in the same page request. + $matches = drupal_system_listing("/^" . DRUPAL_PHP_FUNCTION_PATTERN . "\.$extension$/", $dir, 'name', 0); + foreach ($matches as $matched_name => $file) { + // Log the locations found in the file scan into a static variable. + $files[$type][$matched_name] = $file->uri; + } + } + + // Return the results of the file system scan, or FALSE to indicate the file + // was not found. + return isset($files[$type][$name]) ? $files[$type][$name] : FALSE; +} + +/** + * Triggers a user-level warning for missing or unexpectedly moved files. + * + * @param $type + * The type of the item (theme, theme_engine, module, profile). + * @param $name + * The name of the item for which the filename is requested. + * @param $error_type + * The type of the error ('missing' or 'moved'). + * + * @see drupal_get_filename() + * @see _drupal_get_filename_fallback() + */ +function _drupal_get_filename_fallback_trigger_error($type, $name, $error_type) { + // Hide messages due to known bugs that will appear on a lot of sites. + // @todo Remove this in https://www.drupal.org/node/2383823 + if (empty($name)) { + return; + } + + // Make sure we only show any missing or moved file errors only once per + // request. + static $errors_triggered = array(); + if (empty($errors_triggered[$type][$name][$error_type])) { + // Use _drupal_trigger_error_with_delayed_logging() here since these are + // triggered during low-level operations that cannot necessarily be + // interrupted by a watchdog() call. + if ($error_type == 'missing') { + _drupal_trigger_error_with_delayed_logging(format_string('The following @type is missing from the file system: %name. For information about how to fix this, see the documentation page.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING); + } + elseif ($error_type == 'moved') { + _drupal_trigger_error_with_delayed_logging(format_string('The following @type has moved within the file system: %name. In order to fix this, clear caches or put the @type back in its original location. For more information, see the documentation page.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING); + } + $errors_triggered[$type][$name][$error_type] = TRUE; + } +} + +/** + * Invokes trigger_error() with logging delayed until the end of the request. + * + * This is an alternative to PHP's trigger_error() function which can be used + * during low-level Drupal core operations that need to avoid being interrupted + * by a watchdog() call. + * + * Normally, Drupal's error handler calls watchdog() in response to a + * trigger_error() call. However, this invokes hook_watchdog() which can run + * arbitrary code. If the trigger_error() happens in the middle of an + * operation such as a rebuild operation which should not be interrupted by + * arbitrary code, that could potentially break or trigger the rebuild again. + * This function protects against that by delaying the watchdog() call until + * the end of the current page request. + * + * This is an internal function which should only be called by low-level Drupal + * core functions. It may be removed in a future Drupal 7 release. + * + * @param string $error_msg + * The error message to trigger. As with trigger_error() itself, this is + * limited to 1024 bytes; additional characters beyond that will be removed. + * @param int $error_type + * (optional) The type of error. This should be one of the E_USER family of + * constants. As with trigger_error() itself, this defaults to E_USER_NOTICE + * if not provided. + * + * @see _drupal_log_error() + */ +function _drupal_trigger_error_with_delayed_logging($error_msg, $error_type = E_USER_NOTICE) { + $delay_logging = &drupal_static(__FUNCTION__, FALSE); + $delay_logging = TRUE; + trigger_error($error_msg, $error_type); + $delay_logging = FALSE; +} + +/** + * Writes the file scan cache to the persistent cache. + * + * This cache stores all files marked as missing or moved after a file scan + * to prevent unnecessary file scans in subsequent requests. This cache is + * cleared in system_list_reset() (i.e. after a module/theme rebuild). + */ +function drupal_file_scan_write_cache() { + // Only write to the persistent cache if requested, and if we know that any + // data previously in the cache was successfully loaded and merged in by + // _drupal_file_scan_cache(). + $file_scans = &_drupal_file_scan_cache(); + if (isset($file_scans['#write_cache']) && isset($file_scans['#cache_merge_done'])) { + unset($file_scans['#write_cache']); + cache_set('_drupal_file_scan_cache', $file_scans, 'cache_bootstrap'); } } @@ -1261,7 +1528,7 @@ $default_headers = array( 'Expires' => 'Sun, 19 Nov 1978 05:00:00 GMT', - 'Cache-Control' => 'no-cache, must-revalidate, post-check=0, pre-check=0', + 'Cache-Control' => 'no-cache, must-revalidate', // Prevent browsers from sniffing a response and picking a MIME type // different from the declared content-type, since that can lead to // XSS and other vulnerabilities. @@ -1439,6 +1706,23 @@ * available to code that needs localization. See st() and get_t() for * alternatives. * + * @section sec_context String context + * Matching source strings are normally only translated once, and the same + * translation is used everywhere that has a matching string. However, in some + * cases, a certain English source string needs to have multiple translations. + * One example of this is the string "May", which could be used as either a + * full month name or a 3-letter abbreviated month. In other languages where + * the month name for May has more than 3 letters, you would need to provide + * two different translations (one for the full name and one abbreviated), and + * the correct form would need to be chosen, depending on how "May" is being + * used. To facilitate this, the "May" string should be provided with two + * different contexts in the $options parameter when calling t(). For example: + * @code + * t('May', array(), array('context' => 'Long month name') + * t('May', array(), array('context' => 'Abbreviated month name') + * @endcode + * See https://localize.drupal.org/node/2109 for more information. + * * @param $string * A string containing the English string to translate. * @param $args @@ -1449,8 +1733,9 @@ * An associative array of additional options, with the following elements: * - 'langcode' (defaults to the current language): The language code to * translate to a language other than what is used to display the page. - * - 'context' (defaults to the empty context): The context the source string - * belongs to. + * - 'context' (defaults to the empty context): A string giving the context + * that the source string belongs to. See @ref sec_context above for more + * information. * * @return * The translated string. @@ -2360,6 +2645,10 @@ timer_start('page'); // Initialize the configuration, including variables from settings.php. drupal_settings_initialize(); + + // Sanitize unsafe keys from the request. + require_once DRUPAL_ROOT . '/includes/request-sanitizer.inc'; + DrupalRequestSanitizer::sanitize(); } /** @@ -2502,6 +2791,11 @@ unset($_GET['destination']); unset($_REQUEST['destination']); } + // Use the DrupalRequestSanitizer to ensure that the destination's query + // parameters are not dangerous. + if (isset($_GET['destination'])) { + DrupalRequestSanitizer::cleanDestination(); + } // 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'])) { @@ -2786,10 +3080,14 @@ } /** - * Returns the default language used on the site + * Returns the default language, as an object, or one of its properties. * * @param $property - * Optional property of the language object to return + * (optional) The property of the language object to return. + * + * @return + * Either the language object for the default language used on the site, + * or the property of that object named in the $property parameter. */ function language_default($property = NULL) { $language = variable_get('language_default', (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => '')); @@ -2941,8 +3239,15 @@ // Eliminate all trusted IPs. $untrusted = array_diff($forwarded, $reverse_proxy_addresses); - // The right-most IP is the most specific we can trust. - $ip_address = array_pop($untrusted); + if (!empty($untrusted)) { + // The right-most IP is the most specific we can trust. + $ip_address = array_pop($untrusted); + } + else { + // All IP addresses in the forwarded array are configured proxy IPs + // (and thus trusted). We take the leftmost IP. + $ip_address = array_shift($forwarded); + } } } } @@ -3183,7 +3488,7 @@ $cache_key = $type[0] . $name; if (isset($lookup_cache[$cache_key])) { if ($lookup_cache[$cache_key]) { - require_once DRUPAL_ROOT . '/' . $lookup_cache[$cache_key]; + include_once DRUPAL_ROOT . '/' . $lookup_cache[$cache_key]; } return (bool) $lookup_cache[$cache_key]; } @@ -3208,7 +3513,7 @@ $lookup_cache[$cache_key] = $file; if ($file) { - require_once DRUPAL_ROOT . '/' . $file; + include_once DRUPAL_ROOT . '/' . $file; return TRUE; } else { @@ -3493,8 +3798,12 @@ chdir(DRUPAL_ROOT); try { - while (list($key, $callback) = each($callbacks)) { + // Manually iterate over the array instead of using a foreach loop. + // A foreach operates on a copy of the array, so any shutdown functions that + // were added from other shutdown functions would never be called. + while ($callback = current($callbacks)) { call_user_func_array($callback['callback'], $callback['arguments']); + next($callbacks); } } catch (Exception $exception) { diff -Naur drupal-7.41/includes/cache.inc drupal-7.66/includes/cache.inc --- drupal-7.41/includes/cache.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/cache.inc 2019-04-17 22:20:46.000000000 +0200 @@ -122,7 +122,12 @@ * the administrator panel. * - cache_path: Stores the system paths that have an alias. * @param $expire - * (optional) One of the following values: + * (optional) Controls the maximum lifetime of this cache entry. Note that + * caches might be subject to clearing at any time, so this setting does not + * guarantee a minimum lifetime. With this in mind, the cache should not be + * used for data that must be kept during a cache clear, like sessions. + * + * Use 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 @@ -262,7 +267,12 @@ * 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 - * (optional) One of the following values: + * (optional) Controls the maximum lifetime of this cache entry. Note that + * caches might be subject to clearing at any time, so this setting does not + * guarantee a minimum lifetime. With this in mind, the cache should not be + * used for data that must be kept during a cache clear, like sessions. + * + * Use 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 -Naur drupal-7.41/includes/common.inc drupal-7.66/includes/common.inc --- drupal-7.41/includes/common.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/common.inc 2019-04-17 22:20:46.000000000 +0200 @@ -487,7 +487,7 @@ $params = array(); foreach ($query as $key => $value) { - $key = ($parent ? $parent . '[' . rawurlencode($key) . ']' : rawurlencode($key)); + $key = $parent ? $parent . rawurlencode('[' . $key . ']') : rawurlencode($key); // Recurse into children. if (is_array($value)) { @@ -611,8 +611,9 @@ } // The 'q' parameter contains the path of the current page if clean URLs are // disabled. It overrides the 'path' of the URL when present, even if clean - // URLs are enabled, due to how Apache rewriting rules work. - if (isset($options['query']['q'])) { + // URLs are enabled, due to how Apache rewriting rules work. The path + // parameter must be a string. + if (isset($options['query']['q']) && is_string($options['query']['q'])) { $options['path'] = $options['query']['q']; unset($options['query']['q']); } @@ -688,6 +689,13 @@ $options['fragment'] = $destination['fragment']; } + // In some cases modules call drupal_goto(current_path()). We need to ensure + // that such a redirect is not to an external URL. + if ($path === current_path() && empty($options['external']) && url_is_external($path)) { + // Force url() to generate a non-external URL. + $options['external'] = FALSE; + } + drupal_alter('drupal_goto', $path, $options, $http_response_code); // The 'Location' HTTP header must be absolute. @@ -753,7 +761,8 @@ * - headers: An array containing request headers to send as name/value pairs. * - method: A string containing the request method. Defaults to 'GET'. * - data: A string containing the request body, formatted as - * 'param=value¶m=value&...'. Defaults to NULL. + * 'param=value¶m=value&...'; to generate this, use http_build_query(). + * Defaults to NULL. * - max_redirects: An integer representing how many times a redirect * may be followed. Defaults to 3. * - timeout: A float representing the maximum number of seconds the function @@ -778,6 +787,8 @@ * HTTP header names are case-insensitive (RFC 2616, section 4.2), so for * easy access the array keys are returned in lower case. * - data: A string containing the response body that was received. + * + * @see http_build_query() */ function drupal_http_request($url, array $options = array()) { // Allow an alternate HTTP client library to replace Drupal's default @@ -856,8 +867,10 @@ // Make the socket connection to a proxy server. $socket = 'tcp://' . $proxy_server . ':' . variable_get('proxy_port', 8080); // The Host header still needs to match the real request. - $options['headers']['Host'] = $uri['host']; - $options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : ''; + if (!isset($options['headers']['Host'])) { + $options['headers']['Host'] = $uri['host']; + $options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : ''; + } break; case 'http': @@ -867,14 +880,18 @@ // RFC 2616: "non-standard ports MUST, default ports MAY be included". // We don't add the standard port to prevent from breaking rewrite rules // checking the host that do not take into account the port number. - $options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : ''); + if (!isset($options['headers']['Host'])) { + $options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : ''); + } break; case 'https': // Note: Only works when PHP is compiled with OpenSSL support. $port = isset($uri['port']) ? $uri['port'] : 443; $socket = 'ssl://' . $uri['host'] . ':' . $port; - $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : ''); + if (!isset($options['headers']['Host'])) { + $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : ''); + } break; default: @@ -1077,6 +1094,11 @@ elseif ($options['max_redirects']) { // Redirect to the new location. $options['max_redirects']--; + + // We need to unset the 'Host' header + // as we are redirecting to a new location. + unset($options['headers']['Host']); + $result = drupal_http_request($location, $options); $result->redirect_code = $code; } @@ -1760,9 +1782,15 @@ * - 'key': element name * - 'value': element contents * - 'attributes': associative array of element attributes + * - 'encoded': TRUE if 'value' is already encoded * * In both cases, 'value' can be a simple string, or it can be another array * with the same format as $array itself for nesting. + * + * If 'encoded' is TRUE it is up to the caller to ensure that 'value' is either + * entity-encoded or CDATA-escaped. Using this option is not recommended when + * working with untrusted user input, since failing to escape the data + * correctly has security implications. */ function format_xml_elements($array) { $output = ''; @@ -1775,7 +1803,7 @@ } if (isset($value['value']) && $value['value'] != '') { - $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : check_plain($value['value'])) . '\n"; + $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : (!empty($value['encoded']) ? $value['value'] : check_plain($value['value']))) . '\n"; } else { $output .= " />\n"; @@ -2220,20 +2248,11 @@ 'prefix' => '' ); - // A duplicate of the code from url_is_external() to avoid needing another - // function call, since performance inside url() is critical. + // Determine whether this is an external link, but ensure that the current + // path is always treated as internal by default (to prevent external link + // injection vulnerabilities). if (!isset($options['external'])) { - // Return an external link if $path contains an allowed absolute URL. Avoid - // calling drupal_strip_dangerous_protocols() if there is any slash (/), - // hash (#) or question_mark (?) before the colon (:) occurrence - if any - - // as this would clearly mean it is not a URL. If the path starts with 2 - // slashes then it is always considered an external URL without an explicit - // protocol part. - $colonpos = strpos($path, ':'); - $options['external'] = (strpos($path, '//') === 0) - || ($colonpos !== FALSE - && !preg_match('![/?#]!', substr($path, 0, $colonpos)) - && drupal_strip_dangerous_protocols($path) == $path); + $options['external'] = $path === $_GET['q'] ? FALSE : url_is_external($path); } // Preserve the original path before altering or aliasing. @@ -2303,7 +2322,10 @@ $language = isset($options['language']) && isset($options['language']->language) ? $options['language']->language : ''; $alias = drupal_get_path_alias($original_path, $language); if ($alias != $original_path) { - $path = $alias; + // Strip leading slashes from internal path aliases to prevent them + // becoming external URLs without protocol. /example.com should not be + // turned into //example.com. + $path = ltrim($alias, '/'); } } @@ -2353,12 +2375,18 @@ */ function url_is_external($path) { $colonpos = strpos($path, ':'); - // Avoid calling drupal_strip_dangerous_protocols() if there is any slash (/), - // hash (#) or question_mark (?) before the colon (:) occurrence - if any - as - // this would clearly mean it is not a URL. If the path starts with 2 slashes - // then it is always considered an external URL without an explicit protocol - // part. + // Some browsers treat \ as / so normalize to forward slashes. + $path = str_replace('\\', '/', $path); + // If the path starts with 2 slashes then it is always considered an external + // URL without an explicit protocol part. return (strpos($path, '//') === 0) + // Leading control characters may be ignored or mishandled by browsers, so + // assume such a path may lead to an external location. The \p{C} character + // class matches all UTF-8 control, unassigned, and private characters. + || (preg_match('/^\p{C}/u', $path) !== 0) + // Avoid calling drupal_strip_dangerous_protocols() if there is any slash + // (/), hash (#) or question_mark (?) before the colon (:) occurrence - if + // any - as this would clearly mean it is not a URL. || ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path); @@ -2643,6 +2671,15 @@ global $language; drupal_add_http_header('Content-Language', $language->language); + // By default, do not allow the site to be rendered in an iframe on another + // domain, but provide a variable to override this. If the code running for + // this page request already set the X-Frame-Options header earlier, don't + // overwrite it here. + $frame_options = variable_get('x_frame_options', 'SAMEORIGIN'); + if ($frame_options && is_null(drupal_get_http_header('X-Frame-Options'))) { + drupal_add_http_header('X-Frame-Options', $frame_options); + } + // Menu status constants are integers; page content is a string or array. if (is_int($page_callback_result)) { // @todo: Break these up into separate functions? @@ -2757,6 +2794,7 @@ _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE); drupal_cache_system_paths(); module_implements_write_cache(); + drupal_file_scan_write_cache(); system_run_automated_cron(); } @@ -3024,6 +3062,13 @@ */ function drupal_add_css($data = NULL, $options = NULL) { $css = &drupal_static(__FUNCTION__, array()); + $count = &drupal_static(__FUNCTION__ . '_count', 0); + + // If the $css variable has been reset with drupal_static_reset(), there is + // no longer any CSS being tracked, so set the counter back to 0 also. + if (count($css) === 0) { + $count = 0; + } // Construct the options, taking the defaults into consideration. if (isset($options)) { @@ -3059,7 +3104,8 @@ } // Always add a tiny value to the weight, to conserve the insertion order. - $options['weight'] += count($css) / 1000; + $options['weight'] += $count / 1000; + $count++; // Add the data to the CSS array depending on the type. switch ($options['type']) { @@ -3872,6 +3918,21 @@ * The cleaned identifier. */ function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '')) { + // Use the advanced drupal_static() pattern, since this is called very often. + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['allow_css_double_underscores'] = &drupal_static(__FUNCTION__ . ':allow_css_double_underscores'); + } + $allow_css_double_underscores = &$drupal_static_fast['allow_css_double_underscores']; + if (!isset($allow_css_double_underscores)) { + $allow_css_double_underscores = variable_get('allow_css_double_underscores', FALSE); + } + + // Preserve BEM-style double-underscores depending on custom setting. + if ($allow_css_double_underscores) { + $filter['__'] = '__'; + } + // By default, we filter using Drupal's coding standards. $identifier = strtr($identifier, $filter); @@ -3943,7 +4004,11 @@ // be merged with content already on the base page. The HTML IDs must be // unique for the fully merged content. Therefore, initialize $seen_ids to // take into account IDs that are already in use on the base page. - $seen_ids_init = &drupal_static(__FUNCTION__ . ':init'); + static $drupal_static_fast; + if (!isset($drupal_static_fast['seen_ids_init'])) { + $drupal_static_fast['seen_ids_init'] = &drupal_static(__FUNCTION__ . ':init'); + } + $seen_ids_init = &$drupal_static_fast['seen_ids_init']; if (!isset($seen_ids_init)) { // Ideally, Drupal would provide an API to persist state information about // prior page requests in the database, and we'd be able to add this @@ -3988,7 +4053,10 @@ } } } - $seen_ids = &drupal_static(__FUNCTION__, $seen_ids_init); + if (!isset($drupal_static_fast['seen_ids'])) { + $drupal_static_fast['seen_ids'] = &drupal_static(__FUNCTION__, $seen_ids_init); + } + $seen_ids = &$drupal_static_fast['seen_ids']; $id = strtr(drupal_strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); diff -Naur drupal-7.41/includes/database/database.inc drupal-7.66/includes/database/database.inc --- drupal-7.41/includes/database/database.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/database/database.inc 2019-04-17 22:20:46.000000000 +0200 @@ -296,6 +296,20 @@ */ protected $prefixReplace = array(); + /** + * List of escaped database, table, and field names, keyed by unescaped names. + * + * @var array + */ + protected $escapedNames = array(); + + /** + * List of escaped aliases names, keyed by unescaped aliases. + * + * @var array + */ + protected $escapedAliases = array(); + function __construct($dsn, $username, $password, $driver_options = array()) { // Initialize and prepare the connection prefix. $this->setPrefix(isset($this->connectionOptions['prefix']) ? $this->connectionOptions['prefix'] : ''); @@ -919,11 +933,14 @@ * For some database drivers, it may also wrap the table name in * database-specific escape characters. * - * @return + * @return string * The sanitized table name string. */ public function escapeTable($table) { - return preg_replace('/[^A-Za-z0-9_.]+/', '', $table); + if (!isset($this->escapedNames[$table])) { + $this->escapedNames[$table] = preg_replace('/[^A-Za-z0-9_.]+/', '', $table); + } + return $this->escapedNames[$table]; } /** @@ -933,11 +950,14 @@ * For some database drivers, it may also wrap the field name in * database-specific escape characters. * - * @return + * @return string * The sanitized field name string. */ public function escapeField($field) { - return preg_replace('/[^A-Za-z0-9_.]+/', '', $field); + if (!isset($this->escapedNames[$field])) { + $this->escapedNames[$field] = preg_replace('/[^A-Za-z0-9_.]+/', '', $field); + } + return $this->escapedNames[$field]; } /** @@ -948,11 +968,14 @@ * DatabaseConnection::escapeTable(), this doesn't allow the period (".") * because that is not allowed in aliases. * - * @return + * @return string * The sanitized field name string. */ public function escapeAlias($field) { - return preg_replace('/[^A-Za-z0-9_]+/', '', $field); + if (!isset($this->escapedAliases[$field])) { + $this->escapedAliases[$field] = preg_replace('/[^A-Za-z0-9_]+/', '', $field); + } + return $this->escapedAliases[$field]; } /** @@ -1313,6 +1336,39 @@ * also larger than the $existing_id if one was passed in. */ abstract public function nextId($existing_id = 0); + + /** + * Checks whether utf8mb4 support is configurable in settings.php. + * + * @return bool + */ + public function utf8mb4IsConfigurable() { + // Since 4 byte UTF-8 is not supported by default, there is nothing to + // configure. + return FALSE; + } + + /** + * Checks whether utf8mb4 support is currently active. + * + * @return bool + */ + public function utf8mb4IsActive() { + // Since 4 byte UTF-8 is not supported by default, there is nothing to + // activate. + return FALSE; + } + + /** + * Checks whether utf8mb4 support is available on the current database system. + * + * @return bool + */ + public function utf8mb4IsSupported() { + // By default we assume that the database backend may not support 4 byte + // UTF-8. + return FALSE; + } } /** diff -Naur drupal-7.41/includes/database/mysql/database.inc drupal-7.66/includes/database/mysql/database.inc --- drupal-7.41/includes/database/mysql/database.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/database/mysql/database.inc 2019-04-17 22:20:46.000000000 +0200 @@ -28,6 +28,12 @@ $this->connectionOptions = $connection_options; + $charset = 'utf8'; + // Check if the charset is overridden to utf8mb4 in settings.php. + if ($this->utf8mb4IsActive()) { + $charset = 'utf8mb4'; + } + // The DSN should use either a socket or a host/port. if (isset($connection_options['unix_socket'])) { $dsn = 'mysql:unix_socket=' . $connection_options['unix_socket']; @@ -39,7 +45,7 @@ // Character set is added to dsn to ensure PDO uses the proper character // set when escaping. This has security implications. See // https://www.drupal.org/node/1201452 for further discussion. - $dsn .= ';charset=utf8'; + $dsn .= ';charset=' . $charset; $dsn .= ';dbname=' . $connection_options['database']; // Allow PDO options to be overridden. $connection_options += array( @@ -63,10 +69,10 @@ // certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci' // for UTF-8. if (!empty($connection_options['collation'])) { - $this->exec('SET NAMES utf8 COLLATE ' . $connection_options['collation']); + $this->exec('SET NAMES ' . $charset . ' COLLATE ' . $connection_options['collation']); } else { - $this->exec('SET NAMES utf8'); + $this->exec('SET NAMES ' . $charset); } // Set MySQL init_commands if not already defined. Default Drupal's MySQL @@ -81,7 +87,7 @@ 'init_commands' => array(), ); $connection_options['init_commands'] += array( - 'sql_mode' => "SET sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'", + 'sql_mode' => "SET sql_mode = 'REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'", ); // Execute initial commands. foreach ($connection_options['init_commands'] as $sql) { @@ -206,6 +212,42 @@ } } } + + public function utf8mb4IsConfigurable() { + return TRUE; + } + + public function utf8mb4IsActive() { + return isset($this->connectionOptions['charset']) && $this->connectionOptions['charset'] === 'utf8mb4'; + } + + public function utf8mb4IsSupported() { + // Ensure that the MySQL driver supports utf8mb4 encoding. + $version = $this->getAttribute(PDO::ATTR_CLIENT_VERSION); + if (strpos($version, 'mysqlnd') !== FALSE) { + // The mysqlnd driver supports utf8mb4 starting at version 5.0.9. + $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version); + if (version_compare($version, '5.0.9', '<')) { + return FALSE; + } + } + else { + // The libmysqlclient driver supports utf8mb4 starting at version 5.5.3. + if (version_compare($version, '5.5.3', '<')) { + return FALSE; + } + } + + // Ensure that the MySQL server supports large prefixes and utf8mb4. + try { + $this->query("CREATE TABLE {drupal_utf8mb4_test} (id VARCHAR(255), PRIMARY KEY(id(255))) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci ROW_FORMAT=DYNAMIC ENGINE=INNODB"); + } + catch (Exception $e) { + return FALSE; + } + $this->query("DROP TABLE {drupal_utf8mb4_test}"); + return TRUE; + } } diff -Naur drupal-7.41/includes/database/mysql/schema.inc drupal-7.66/includes/database/mysql/schema.inc --- drupal-7.41/includes/database/mysql/schema.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/database/mysql/schema.inc 2019-04-17 22:20:46.000000000 +0200 @@ -39,8 +39,8 @@ $info['table'] = substr($table, ++$pos); } else { - $db_info = Database::getConnectionInfo(); - $info['database'] = $db_info[$this->connection->getTarget()]['database']; + $db_info = $this->connection->getConnectionOptions(); + $info['database'] = $db_info['database']; $info['table'] = $table; } return $info; @@ -81,7 +81,8 @@ // Provide defaults if needed. $table += array( 'mysql_engine' => 'InnoDB', - 'mysql_character_set' => 'utf8', + // Allow the default charset to be overridden in settings.php. + 'mysql_character_set' => $this->connection->utf8mb4IsActive() ? 'utf8mb4' : 'utf8', ); $sql = "CREATE TABLE {" . $name . "} (\n"; @@ -109,6 +110,13 @@ $sql .= ' COLLATE ' . $info['collation']; } + // The row format needs to be either DYNAMIC or COMPRESSED in order to allow + // for the innodb_large_prefix setting to take effect, see + // https://dev.mysql.com/doc/refman/5.6/en/create-table.html + if ($this->connection->utf8mb4IsActive()) { + $sql .= ' ROW_FORMAT=DYNAMIC'; + } + // Add table comment. if (!empty($table['description'])) { $sql .= ' COMMENT ' . $this->prepareComment($table['description'], self::COMMENT_MAX_TABLE); diff -Naur drupal-7.41/includes/database/pgsql/database.inc drupal-7.66/includes/database/pgsql/database.inc --- drupal-7.41/includes/database/pgsql/database.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/database/pgsql/database.inc 2019-04-17 22:20:46.000000000 +0200 @@ -11,7 +11,7 @@ */ /** - * The name by which to obtain a lock for retrive the next insert id. + * The name by which to obtain a lock for retrieving the next insert id. */ define('POSTGRESQL_NEXTID_LOCK', 1000); @@ -55,7 +55,7 @@ $connection_options['pdo'] += array( // Prepared statements are most effective for performance when queries // are recycled (used several times). However, if they are not re-used, - // prepared statements become ineffecient. Since most of Drupal's + // prepared statements become inefficient. Since most of Drupal's // prepared queries are not re-used, it should be faster to emulate // the preparation than to actually ready statements for re-use. If in // doubt, reset to FALSE and measure performance. @@ -175,14 +175,14 @@ } /** - * Retrive a the next id in a sequence. + * Retrieve the next id in a sequence. * * PostgreSQL has built in sequences. We'll use these instead of inserting * and updating a sequences table. */ public function nextId($existing = 0) { - // Retrive the name of the sequence. This information cannot be cached + // Retrieve the name of the sequence. This information cannot be cached // because the prefix may change, for example, like it does in simpletests. $sequence_name = $this->makeSequenceName('sequences', 'value'); @@ -194,7 +194,7 @@ } // PostgreSQL advisory locks are simply locks to be used by an - // application such as Drupal. This will prevent other Drupal proccesses + // application such as Drupal. This will prevent other Drupal processes // from altering the sequence while we are. $this->query("SELECT pg_advisory_lock(" . POSTGRESQL_NEXTID_LOCK . ")"); @@ -209,13 +209,21 @@ // Reset the sequence to a higher value than the existing id. $this->query("ALTER SEQUENCE " . $sequence_name . " RESTART WITH " . ($existing + 1)); - // Retrive the next id. We know this will be as high as we want it. + // Retrieve the next id. We know this will be as high as we want it. $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField(); $this->query("SELECT pg_advisory_unlock(" . POSTGRESQL_NEXTID_LOCK . ")"); return $id; } + + public function utf8mb4IsActive() { + return TRUE; + } + + public function utf8mb4IsSupported() { + return TRUE; + } } /** diff -Naur drupal-7.41/includes/database/pgsql/install.inc drupal-7.66/includes/database/pgsql/install.inc --- drupal-7.41/includes/database/pgsql/install.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/database/pgsql/install.inc 2019-04-17 22:20:46.000000000 +0200 @@ -165,7 +165,7 @@ LANGUAGE \'sql\'' ); - // Using || to concatenate in Drupal is not recommeneded because there are + // Using || to concatenate in Drupal is not recommended because there are // database drivers for Drupal that do not support the syntax, however // they do support CONCAT(item1, item2) which we can replicate in // PostgreSQL. PostgreSQL requires the function to be defined for each diff -Naur drupal-7.41/includes/database/pgsql/select.inc drupal-7.66/includes/database/pgsql/select.inc --- drupal-7.41/includes/database/pgsql/select.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/database/pgsql/select.inc 2019-04-17 22:20:46.000000000 +0200 @@ -80,7 +80,7 @@ } // If a table loads all fields, it can not be added again. It would - // result in an ambigious alias error because that field would be loaded + // result in an ambiguous alias error because that field would be loaded // twice: Once through table_alias.* and once directly. If the field // actually belongs to a different table, it must be added manually. foreach ($this->tables as $table) { @@ -90,7 +90,7 @@ } // If $field contains an characters which are not allowed in a field name - // it is considered an expression, these can't be handeld automatically + // it is considered an expression, these can't be handled automatically // either. if ($this->connection->escapeField($field) != $field) { return $return; diff -Naur drupal-7.41/includes/database/query.inc drupal-7.66/includes/database/query.inc --- drupal-7.41/includes/database/query.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/database/query.inc 2019-04-17 22:20:46.000000000 +0200 @@ -845,8 +845,8 @@ /** * Executes the DELETE query. * - * @return - * The return value is dependent on the database connection. + * @return int + * The number of rows affected by the delete query. */ public function execute() { $values = array(); @@ -1242,7 +1242,7 @@ * MergeQuery::updateFields() and MergeQuery::insertFields() needs to be called * instead. MergeQuery::fields() can also be called which calls both of these * methods as the common case is to use the same column-value pairs for both - * INSERT and UPDATE. However, this is not mandatory. Another convinient + * INSERT and UPDATE. However, this is not mandatory. Another convenient * wrapper is MergeQuery::key() which adds the same column-value pairs to the * condition and the INSERT query part. * diff -Naur drupal-7.41/includes/database/schema.inc drupal-7.66/includes/database/schema.inc --- drupal-7.41/includes/database/schema.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/database/schema.inc 2019-04-17 22:20:46.000000000 +0200 @@ -164,6 +164,9 @@ * @see drupal_install_schema() */ +/** + * Base class for database schema definitions. + */ abstract class DatabaseSchema implements QueryPlaceholderInterface { protected $connection; @@ -291,7 +294,7 @@ protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) { $info = $this->connection->getConnectionOptions(); - // Retrive the table name and schema + // Retrieve the table name and schema $table_info = $this->getPrefixInfo($table_name, $add_prefix); $condition = new DatabaseCondition('AND'); diff -Naur drupal-7.41/includes/database/select.inc drupal-7.66/includes/database/select.inc --- drupal-7.41/includes/database/select.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/database/select.inc 2019-04-17 22:20:46.000000000 +0200 @@ -1231,6 +1231,21 @@ // Modules may alter all queries or only those having a particular tag. if (isset($this->alterTags)) { + // Many contrib modules assume that query tags used for access-checking + // purposes follow the pattern $entity_type . '_access'. But this is + // not the case for taxonomy terms, since core used to add term_access + // instead of taxonomy_term_access to its queries. Provide backwards + // compatibility by adding both tags here instead of attempting to fix + // all contrib modules in a coordinated effort. + // TODO: + // - Extract this mechanism into a hook as part of a public (non-security) + // issue. + // - Emit E_USER_DEPRECATED if term_access is used. + // https://www.drupal.org/node/2575081 + $term_access_tags = array('term_access' => 1, 'taxonomy_term_access' => 1); + if (array_intersect_key($this->alterTags, $term_access_tags)) { + $this->alterTags += $term_access_tags; + } $hooks = array('query'); foreach ($this->alterTags as $tag => $value) { $hooks[] = 'query_' . $tag; diff -Naur drupal-7.41/includes/database/sqlite/database.inc drupal-7.66/includes/database/sqlite/database.inc --- drupal-7.41/includes/database/sqlite/database.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/database/sqlite/database.inc 2019-04-17 22:20:46.000000000 +0200 @@ -378,6 +378,14 @@ } } + public function utf8mb4IsActive() { + return TRUE; + } + + public function utf8mb4IsSupported() { + return TRUE; + } + } /** diff -Naur drupal-7.41/includes/database/sqlite/query.inc drupal-7.66/includes/database/sqlite/query.inc --- drupal-7.41/includes/database/sqlite/query.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/database/sqlite/query.inc 2019-04-17 22:20:46.000000000 +0200 @@ -99,16 +99,15 @@ /** * SQLite specific implementation of DeleteQuery. - * - * When the WHERE is omitted from a DELETE statement and the table being deleted - * has no triggers, SQLite uses an optimization to erase the entire table content - * without having to visit each row of the table individually. - * - * Prior to SQLite 3.6.5, SQLite does not return the actual number of rows deleted - * by that optimized "truncate" optimization. */ class DeleteQuery_sqlite extends DeleteQuery { public function execute() { + // When the WHERE is omitted from a DELETE statement and the table being + // deleted has no triggers, SQLite uses an optimization to erase the entire + // table content without having to visit each row of the table individually. + // Prior to SQLite 3.6.5, SQLite does not return the actual number of rows + // deleted by that optimized "truncate" optimization. But we want to return + // the number of rows affected, so we calculate it directly. if (!count($this->condition)) { $total_rows = $this->connection->query('SELECT COUNT(*) FROM {' . $this->connection->escapeTable($this->table) . '}')->fetchField(); parent::execute(); diff -Naur drupal-7.41/includes/database/sqlite/schema.inc drupal-7.66/includes/database/sqlite/schema.inc --- drupal-7.41/includes/database/sqlite/schema.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/database/sqlite/schema.inc 2019-04-17 22:20:46.000000000 +0200 @@ -244,7 +244,7 @@ // database. So the syntax '...RENAME TO database.table' would fail. // So we must determine the full table name here rather than surrounding // the table with curly braces incase the db_prefix contains a reference - // to a database outside of our existsing database. + // to a database outside of our existing database. $info = $this->getPrefixInfo($new_name); $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $info['table']); diff -Naur drupal-7.41/includes/date.inc drupal-7.66/includes/date.inc --- drupal-7.41/includes/date.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/date.inc 2019-04-17 22:20:46.000000000 +0200 @@ -14,11 +14,6 @@ // Short date formats. $formats[] = array( 'type' => 'short', - 'format' => 'Y-m-d H:i', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'short', 'format' => 'm/d/Y - H:i', 'locales' => array('en-us'), ); @@ -39,6 +34,11 @@ ); $formats[] = array( 'type' => 'short', + 'format' => 'Y-m-d H:i', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'short', 'format' => 'm/d/Y - g:ia', 'locales' => array(), ); @@ -86,11 +86,6 @@ // Medium date formats. $formats[] = array( 'type' => 'medium', - 'format' => 'D, Y-m-d H:i', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'medium', 'format' => 'D, m/d/Y - H:i', 'locales' => array('en-us'), ); @@ -106,6 +101,11 @@ ); $formats[] = array( 'type' => 'medium', + 'format' => 'D, Y-m-d H:i', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'medium', 'format' => 'F j, Y - H:i', 'locales' => array(), ); diff -Naur drupal-7.41/includes/entity.inc drupal-7.66/includes/entity.inc --- drupal-7.41/includes/entity.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/entity.inc 2019-04-17 22:20:46.000000000 +0200 @@ -446,7 +446,7 @@ * * This class allows finding entities based on entity properties (for example, * node->changed), field values, and generic entity meta data (bundle, - * entity type, entity id, and revision ID). It is not possible to query across + * entity type, entity ID, and revision ID). It is not possible to query across * multiple entity types. For example, there is no facility to find published * nodes written by users created in the last hour, as this would require * querying both node->status and user->created. @@ -688,14 +688,36 @@ * @param $field * Either a field name or a field array. * @param $column - * The column that should hold the value to be matched. + * The column that should hold the value to be matched, defined in the + * hook_field_schema() of this field. If this is omitted then all of the + * other parameters are ignored, except $field, and this call will just be + * adding a condition that says that the field has a value, rather than + * testing the value itself. * @param $value - * The value to test the column value against. + * The value to test the column value against. In most cases, this is a + * scalar. For more complex options, it is an array. The meaning of each + * element in the array is dependent on $operator. * @param $operator - * The operator to be used to test the given value. + * The operator to be used to test the given value. The possible values are: + * - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These + * operators expect $value to be a literal of the same type as the + * column. + * - 'IN', 'NOT IN': These operators expect $value to be an array of + * literals of the same type as the column. + * - 'BETWEEN': This operator expects $value to be an array of two literals + * of the same type as the column. + * The operator can be omitted, and will default to 'IN' if the value is an + * array, or to '=' otherwise. * @param $delta_group * An arbitrary identifier: conditions in the same group must have the same - * $delta_group. + * $delta_group. For example, let's presume a multivalue field which has + * two columns, 'color' and 'shape', and for entity ID 1, there are two + * values: red/square and blue/circle. Entity ID 1 does not have values + * corresponding to 'red circle'; however if you pass 'red' and 'circle' as + * conditions, it will appear in the results -- by default queries will run + * against any combination of deltas. By passing the conditions with the + * same $delta_group it will ensure that only values attached to the same + * delta are matched, and entity 1 would then be excluded from the results. * @param $language_group * An arbitrary identifier: conditions in the same group must have the same * $language_group. @@ -770,9 +792,11 @@ * @param $field * Either a field name or a field array. * @param $column - * A column defined in the hook_field_schema() of this field. If this is - * omitted then the query will find only entities that have data in this - * field, using the entity and property conditions if there are any. + * The column that should hold the value to be matched, defined in the + * hook_field_schema() of this field. If this is omitted then all of the + * other parameters are ignored, except $field, and this call will just be + * adding a condition that says that the field has a value, rather than + * testing the value itself. * @param $value * The value to test the column value against. In most cases, this is a * scalar. For more complex options, it is an array. The meaning of each @@ -791,10 +815,10 @@ * @param $delta_group * An arbitrary identifier: conditions in the same group must have the same * $delta_group. For example, let's presume a multivalue field which has - * two columns, 'color' and 'shape', and for entity id 1, there are two + * two columns, 'color' and 'shape', and for entity ID 1, there are two * values: red/square and blue/circle. Entity ID 1 does not have values * corresponding to 'red circle', however if you pass 'red' and 'circle' as - * conditions, it will appear in the results - by default queries will run + * conditions, it will appear in the results -- by default queries will run * against any combination of deltas. By passing the conditions with the * same $delta_group it will ensure that only values attached to the same * delta are matched, and entity 1 would then be excluded from the results. diff -Naur drupal-7.41/includes/errors.inc drupal-7.66/includes/errors.inc --- drupal-7.41/includes/errors.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/errors.inc 2019-04-17 22:20:46.000000000 +0200 @@ -66,7 +66,7 @@ _drupal_log_error(array( '%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error', // The standard PHP error handler considers that the error messages - // are HTML. We mimick this behavior here. + // are HTML. We mimic this behavior here. '!message' => filter_xss_admin($message), '%function' => $caller['function'], '%file' => $caller['file'], @@ -114,7 +114,7 @@ return array( '%type' => get_class($exception), // The standard PHP exception handler considers that the exception message - // is plain-text. We mimick this behavior here. + // is plain-text. We mimic this behavior here. '!message' => check_plain($message), '%function' => $caller['function'], '%file' => $caller['file'], @@ -199,7 +199,16 @@ $number++; } - watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']); + // Log the error immediately, unless this is a non-fatal error which has been + // triggered via drupal_trigger_error_with_delayed_logging(); in that case + // trigger it in a shutdown function. Fatal errors are always triggered + // immediately since for a fatal error the page request will end here anyway. + if (!$fatal && drupal_static('_drupal_trigger_error_with_delayed_logging')) { + drupal_register_shutdown_function('watchdog', 'php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']); + } + else { + watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']); + } if ($fatal) { drupal_add_http_header('Status', '500 Service unavailable (with message)'); @@ -224,7 +233,7 @@ } else { // Display the message if the current error reporting level allows this type - // of message to be displayed, and unconditionnaly in update.php. + // of message to be displayed, and unconditionally in update.php. if (error_displayable($error)) { $class = 'error'; diff -Naur drupal-7.41/includes/file.inc drupal-7.66/includes/file.inc --- drupal-7.41/includes/file.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/file.inc 2019-04-17 22:20:46.000000000 +0200 @@ -273,7 +273,9 @@ * The normalized URI. */ function file_stream_wrapper_uri_normalize($uri) { - $scheme = file_uri_scheme($uri); + // Inline file_uri_scheme() function call for performance reasons. + $position = strpos($uri, '://'); + $scheme = $position ? substr($uri, 0, $position) : FALSE; if ($scheme && file_stream_wrapper_valid_scheme($scheme)) { $target = file_uri_target($uri); @@ -533,7 +535,18 @@ EOF; if ($private) { - $lines = "Deny from all\n\n" . $lines; + $lines = << + Require all denied + + +# Deny all requests from Apache 2.0-2.2. + + Deny from all + +EOF + . "\n\n" . $lines; } return $lines; @@ -887,7 +900,6 @@ */ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { $original_source = $source; - $original_destination = $destination; // Assert that the source file actually exists. if (!file_exists($source)) { @@ -981,8 +993,15 @@ * @return * The destination filepath, or FALSE if the file already exists * and FILE_EXISTS_ERROR is specified. + * + * @throws RuntimeException + * Thrown if the filename contains invalid UTF-8. */ function file_destination($destination, $replace) { + $basename = drupal_basename($destination); + if (!drupal_validate_utf8($basename)) { + throw new RuntimeException(sprintf("Invalid filename '%s'", $basename)); + } if (file_exists($destination)) { switch ($replace) { case FILE_EXISTS_REPLACE: @@ -990,7 +1009,6 @@ break; case FILE_EXISTS_RENAME: - $basename = drupal_basename($destination); $directory = drupal_dirname($destination); $destination = file_create_filename($basename, $directory); break; @@ -1206,11 +1224,20 @@ * @return * File path consisting of $directory and a unique filename based off * of $basename. + * + * @throws RuntimeException + * Thrown if the $basename is not valid UTF-8 or another error occurs + * stripping control characters. */ function file_create_filename($basename, $directory) { + $original = $basename; // Strip control characters (ASCII value < 32). Though these are allowed in // some filesystems, not many applications handle them well. $basename = preg_replace('/[\x00-\x1F]/u', '_', $basename); + if (preg_last_error() !== PREG_NO_ERROR) { + throw new RuntimeException(sprintf("Invalid filename '%s'", $original)); + } + if (substr(PHP_OS, 0, 3) == 'WIN') { // These characters are not allowed in Windows filenames $basename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $basename); @@ -1522,9 +1549,9 @@ // rename filename.php.foo and filename.php to filename.php.foo.txt and // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads' // evaluates to TRUE. - if (!variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) { + if (!variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|phar|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) { $file->filemime = 'text/plain'; - $file->uri .= '.txt'; + // The destination filename will also later be used to create the URI. $file->filename .= '.txt'; // The .txt extension may not be in the allowed list of extensions. We have // to add it here or else the file upload will fail. @@ -1551,7 +1578,13 @@ if (substr($destination, -1) != '/') { $destination .= '/'; } - $file->destination = file_destination($destination . $file->filename, $replace); + try { + $file->destination = file_destination($destination . $file->filename, $replace); + } + catch (RuntimeException $e) { + drupal_set_message(t('The file %source could not be uploaded because the name is invalid.', array('%source' => $form_field_name)), 'error'); + return FALSE; + } // If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and // there's an existing file so we need to bail. if ($file->destination === FALSE) { @@ -1602,6 +1635,20 @@ // If we made it this far it's safe to record this file in the database. if ($file = file_save($file)) { + // Track non-public files in the session if they were uploaded by an + // anonymous user. This allows modules such as the File module to only + // grant view access to the specific anonymous user who uploaded the file. + // See file_file_download(). + // The 'file_public_schema' variable is used to allow other publicly + // accessible file schemes to be treated the same as the public:// scheme + // provided by Drupal core and to avoid adding unnecessary data to the + // session (and the resulting bypass of the page cache) in those cases. For + // security reasons, only schemes that are completely publicly accessible, + // with no download restrictions, should be added to this variable. See + // file_managed_file_value(). + if (!$user->uid && !in_array($destination_scheme, variable_get('file_public_schema', array('public')))) { + $_SESSION['anonymous_allowed_file_ids'][$file->fid] = $file->fid; + } // Add file to the cache. $upload_cache[$form_field_name] = $file; return $file; @@ -2022,7 +2069,7 @@ * * @see file_transfer() * @see file_download_access() - * @see hook_file_downlaod() + * @see hook_file_download() */ function file_download_headers($uri) { // Let other modules provide headers and control access to the file. @@ -2104,9 +2151,33 @@ * 'filename', and 'name' members corresponding to the matching files. */ function file_scan_directory($dir, $mask, $options = array(), $depth = 0) { + // Default nomask option. + $nomask = '/(\.\.?|CVS)$/'; + + // Overrides the $nomask variable accordingly if $options['nomask'] is set. + // + // Allow directories specified in settings.php to be ignored. You can use this + // to not check for files in common special-purpose directories. For example, + // node_modules and bower_components. Ignoring irrelevant directories is a + // performance boost. + if (!isset($options['nomask'])) { + $ignore_directories = variable_get( + 'file_scan_ignore_directories', + array() + ); + + foreach ($ignore_directories as $index => $ignore_directory) { + $ignore_directories[$index] = preg_quote($ignore_directory, '/'); + } + + if (!empty($ignore_directories)) { + $nomask = '/^(\.\.?)|CVS|' . implode('|', $ignore_directories) . '$/'; + } + } + // Merge in defaults. $options += array( - 'nomask' => '/(\.\.?|CVS)$/', + 'nomask' => $nomask, 'callback' => 0, 'recurse' => TRUE, 'key' => 'uri', @@ -2551,7 +2622,6 @@ * An associative array of headers, as expected by file_transfer(). */ function file_get_content_headers($file) { - $name = mime_header_encode($file->filename); $type = mime_header_encode($file->filemime); return array( diff -Naur drupal-7.41/includes/file.phar.inc drupal-7.66/includes/file.phar.inc --- drupal-7.41/includes/file.phar.inc 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/includes/file.phar.inc 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,41 @@ +withAssertion(new PharExtensionInterceptor()) + ); + } + catch (\LogicException $e) { + // Continue if the PharStreamWrapperManager is already initialized. + // For example, this occurs following a drupal_static_reset(), such + // as during tests. + }; + + // To prevent file_stream_wrapper_valid_scheme() treating "phar" as a valid + // scheme, this is registered with PHP only, not with hook_stream_wrappers() + // or the internal storage of file_get_stream_wrappers(). + stream_wrapper_register('phar', '\\TYPO3\\PharStreamWrapper\\PharStreamWrapper'); +} diff -Naur drupal-7.41/includes/form.inc drupal-7.66/includes/form.inc --- drupal-7.41/includes/form.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/form.inc 2019-04-17 22:20:46.000000000 +0200 @@ -105,7 +105,8 @@ * generate the same form (or very similar forms) using different $form_ids * can implement hook_forms(), which maps different $form_id values to the * proper form constructor function. Examples may be found in node_forms(), - * and search_forms(). + * and search_forms(). hook_forms() can also be used to define forms in + * classes. * @param ... * Any additional arguments are passed on to the functions called by * drupal_get_form(), including the unique form constructor function. For @@ -554,8 +555,10 @@ * Stores a form in the cache. */ function form_set_cache($form_build_id, $form, $form_state) { - // 6 hours cache life time for forms should be plenty. - $expire = 21600; + // The default cache_form expiration is 6 hours. On busy sites, the cache_form + // table can become very large. A shorter cache lifetime can help to keep the + // table's size under control. + $expire = variable_get('form_cache_expiration', 21600); // Ensure that the form build_id embedded in the form structure is the same as // the one passed in as a parameter. This is an additional safety measure to @@ -809,7 +812,7 @@ } if (isset($form_definition['callback'])) { $callback = $form_definition['callback']; - $form_state['build_info']['base_form_id'] = $callback; + $form_state['build_info']['base_form_id'] = isset($form_definition['base_form_id']) ? $form_definition['base_form_id'] : $callback; } // In case $form_state['wrapper_callback'] is not defined already, we also // allow hook_forms() to define one. @@ -830,7 +833,7 @@ // the actual form builder function ($callback) expects. This allows for // pre-populating a form with common elements for certain forms, such as // back/next/save buttons in multi-step form wizards. See drupal_build_form(). - if (isset($form_state['wrapper_callback']) && function_exists($form_state['wrapper_callback'])) { + if (isset($form_state['wrapper_callback']) && is_callable($form_state['wrapper_callback'])) { $form = call_user_func_array($form_state['wrapper_callback'], $args); // Put the prepopulated $form into $args. $args[0] = $form; @@ -1175,7 +1178,7 @@ // If the session token was set by drupal_prepare_form(), ensure that it // matches the current user's session. This is duplicate to code in // form_builder() but left to protect any custom form handling code. - if (isset($form['#token'])) { + if (!empty($form['#token'])) { if (!drupal_valid_token($form_state['values']['form_token'], $form['#token']) || !empty($form_state['invalid_token'])) { _drupal_invalid_token_set_form_error(); // Stop here and don't run any further validation handlers, because they @@ -1437,10 +1440,12 @@ // length if it's a string, and the item count if it's an array. // An unchecked checkbox has a #value of integer 0, different than string // '0', which could be a valid value. - $is_empty_multiple = (!count($elements['#value'])); + $is_countable = is_array($elements['#value']) || $elements['#value'] instanceof Countable; + $is_empty_multiple = $is_countable && count($elements['#value']) == 0; $is_empty_string = (is_string($elements['#value']) && drupal_strlen(trim($elements['#value'])) == 0); $is_empty_value = ($elements['#value'] === 0); - if ($is_empty_multiple || $is_empty_string || $is_empty_value) { + $is_empty_null = is_null($elements['#value']); + if ($is_empty_multiple || $is_empty_string || $is_empty_value || $is_empty_null) { // Although discouraged, a #title is not mandatory for form elements. In // case there is no #title, we cannot set a form error message. // Instead of setting no #title, form constructors are encouraged to set @@ -1836,7 +1841,7 @@ // If the session token was set by drupal_prepare_form(), ensure that it // matches the current user's session. $form_state['invalid_token'] = FALSE; - if (isset($element['#token'])) { + if (!empty($element['#token'])) { if (empty($form_state['input']['form_token']) || !drupal_valid_token($form_state['input']['form_token'], $element['#token'])) { // Set an early form error to block certain input processing since that // opens the door for CSRF vulnerabilities. @@ -2571,7 +2576,7 @@ * for this element. Return nothing to use the default. */ function form_type_textarea_value($element, $input = FALSE) { - if ($input !== FALSE) { + if ($input !== FALSE && $input !== NULL) { // This should be a string, but allow other scalars since they might be // valid input in programmatic form submissions. return is_scalar($input) ? (string) $input : ''; @@ -3028,7 +3033,7 @@ function password_confirm_validate($element, &$element_state) { $pass1 = trim($element['pass1']['#value']); $pass2 = trim($element['pass2']['#value']); - if (!empty($pass1) || !empty($pass2)) { + if (strlen($pass1) > 0 || strlen($pass2) > 0) { if (strcmp($pass1, $pass2)) { form_error($element, t('The specified passwords do not match.')); } @@ -3385,9 +3390,12 @@ /** * Returns HTML to wrap child elements in a container. * - * Used for grouped form items. Can also be used as a #theme_wrapper for any + * Used for grouped form items. Can also be used as a theme wrapper for any * renderable element, to surround it with a
and add attributes such as - * classes or an HTML id. + * classes or an HTML ID. + * + * See the @link forms_api_reference.html Form API reference @endlink for more + * information on the #theme_wrappers render array property. * * @param $variables * An associative array containing: @@ -3542,6 +3550,7 @@ '#return_value' => $key, '#default_value' => isset($value[$key]) ? $key : NULL, '#attributes' => $element['#attributes'], + '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, ); } else { @@ -3979,7 +3988,12 @@ // browser interpreting the path plus search string as an actual file. $current_clean_url = isset($GLOBALS['conf']['clean_url']) ? $GLOBALS['conf']['clean_url'] : NULL; $GLOBALS['conf']['clean_url'] = 0; - $element['#autocomplete_input']['#url_value'] = url($element['#autocomplete_path'], array('absolute' => TRUE)); + // Force the script path to 'index.php', in case the server is not + // configured to find it automatically. Normally it is the responsibility + // of the site to do this themselves using hook_url_outbound_alter() (see + // url()) but since this code is forcing non-clean URLs on sites that don't + // normally use them, it is done here instead. + $element['#autocomplete_input']['#url_value'] = url($element['#autocomplete_path'], array('absolute' => TRUE, 'script' => 'index.php')); $GLOBALS['conf']['clean_url'] = $current_clean_url; } return $element; diff -Naur drupal-7.41/includes/install.core.inc drupal-7.66/includes/install.core.inc --- drupal-7.41/includes/install.core.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/install.core.inc 2019-04-17 22:20:46.000000000 +0200 @@ -809,6 +809,13 @@ variable_set('install_profile_modules', array_diff($modules, array('system'))); $install_state['database_tables_exist'] = TRUE; + + // Prevent the hook_requirements() check from telling us to convert the + // database to utf8mb4. + $connection = Database::getConnection(); + if ($connection->utf8mb4IsConfigurable() && $connection->utf8mb4IsActive()) { + variable_set('drupal_all_databases_are_utf8mb4', TRUE); + } } /** diff -Naur drupal-7.41/includes/install.inc drupal-7.66/includes/install.inc --- drupal-7.41/includes/install.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/install.inc 2019-04-17 22:20:46.000000000 +0200 @@ -750,7 +750,7 @@ /** * Uninstalls a given list of disabled modules. * - * @param array $module_list + * @param string[] $module_list * The modules to uninstall. It is the caller's responsibility to ensure that * all modules in this list have already been disabled before this function * is called. @@ -769,6 +769,7 @@ * included in $module_list). * * @see module_disable() + * @see module_enable() */ function drupal_uninstall_modules($module_list = array(), $uninstall_dependents = TRUE) { if ($uninstall_dependents) { @@ -778,7 +779,7 @@ $module_list = array_flip(array_values($module_list)); $profile = drupal_get_profile(); - while (list($module) = each($module_list)) { + foreach (array_keys($module_list) as $module) { if (!isset($module_data[$module]) || drupal_get_installed_schema_version($module) == SCHEMA_UNINSTALLED) { // This module doesn't exist or is already uninstalled. Skip it. unset($module_list[$module]); diff -Naur drupal-7.41/includes/locale.inc drupal-7.66/includes/locale.inc --- drupal-7.41/includes/locale.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/locale.inc 2019-04-17 22:20:46.000000000 +0200 @@ -435,6 +435,13 @@ switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) { case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN: if ($options['language']->domain) { + // Save the original base URL. If it contains a port, we need to + // retain it below. + if (!empty($options['base_url'])) { + // The colon in the URL scheme messes up the port checking below. + $normalized_base_url = str_replace(array('https://', 'http://'), '', $options['base_url']); + } + // Ask for an absolute URL with our modified base_url. global $is_https; $url_scheme = ($is_https) ? 'https://' : 'http://'; @@ -449,6 +456,19 @@ // Apply the appropriate protocol to the URL. $options['base_url'] = $url_scheme . $host; + + // In case either the original base URL or the HTTP host contains a + // port, retain it. + $http_host = $_SERVER['HTTP_HOST']; + if (isset($normalized_base_url) && strpos($normalized_base_url, ':') !== FALSE) { + list($host, $port) = explode(':', $normalized_base_url); + $options['base_url'] .= ':' . $port; + } + elseif (strpos($http_host, ':') !== FALSE) { + list($host, $port) = explode(':', $http_host); + $options['base_url'] .= ':' . $port; + } + if (isset($options['https']) && variable_get('https', FALSE)) { if ($options['https'] === TRUE) { $options['base_url'] = str_replace('http://', 'https://', $options['base_url']); @@ -523,6 +543,22 @@ * possible attack vector (img). */ function locale_string_is_safe($string) { + // Some strings have tokens in them. For tokens in the first part of href or + // src HTML attributes, filter_xss() removes part of the token, the part + // before the first colon. filter_xss() assumes it could be an attempt to + // inject javascript. When filter_xss() removes part of tokens, it causes the + // string to not be translatable when it should be translatable. See + // LocaleStringIsSafeTest::testLocaleStringIsSafe(). + // + // We can recognize tokens since they are wrapped with brackets and are only + // composed of alphanumeric characters, colon, underscore, and dashes. We can + // be sure these strings are safe to strip out before the string is checked in + // filter_xss() because no dangerous javascript will match that pattern. + // + // @todo Do not strip out the token. Fix filter_xss() to not incorrectly + // alter the string. https://www.drupal.org/node/2372127 + $string = preg_replace('/\[[a-z0-9_-]+(:[a-z0-9_-]+)+\]/i', '', $string); + return decode_entities($string) == decode_entities(filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var'))); } @@ -631,9 +667,6 @@ * translations). */ function _locale_import_po($file, $langcode, $mode, $group = NULL) { - // Try to allocate enough time to parse and import the data. - drupal_set_time_limit(240); - // Check if we have the language already in the database. if (!db_query("SELECT COUNT(language) FROM {languages} WHERE language = :language", array(':language' => $langcode))->fetchField()) { drupal_set_message(t('The language selected for import is not supported.'), 'error'); @@ -717,6 +750,12 @@ $lineno = 0; while (!feof($fd)) { + // Refresh the time limit every 10 parsed rows to ensure there is always + // enough time to import the data for large PO files. + if (!($lineno % 10)) { + drupal_set_time_limit(30); + } + // A line should not be longer than 10 * 1024. $line = fgets($fd, 10 * 1024); diff -Naur drupal-7.41/includes/mail.inc drupal-7.66/includes/mail.inc --- drupal-7.41/includes/mail.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/mail.inc 2019-04-17 22:20:46.000000000 +0200 @@ -566,7 +566,7 @@ // Use soft-breaks only for purely quoted or unindented text. $line = wordwrap($line, 77 - $values['length'], $values['soft'] ? " \n" : "\n"); // Break really long words at the maximum width allowed. - $line = wordwrap($line, 996 - $values['length'], $values['soft'] ? " \n" : "\n"); + $line = wordwrap($line, 996 - $values['length'], $values['soft'] ? " \n" : "\n", TRUE); } /** diff -Naur drupal-7.41/includes/menu.inc drupal-7.66/includes/menu.inc --- drupal-7.41/includes/menu.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/menu.inc 2019-04-17 22:20:46.000000000 +0200 @@ -576,7 +576,8 @@ // 'load arguments' in the hook_menu() entry, but they need // some processing. In this case the $function is the key to the // load_function array, and the value is the list of arguments. - list($function, $args) = each($function); + $args = current($function); + $function = key($function); $load_functions[$index] = $function; // Some arguments are placeholders for dynamic items to process. @@ -1606,6 +1607,7 @@ * Implements template_preprocess_HOOK() for theme_menu_tree(). */ function template_preprocess_menu_tree(&$variables) { + $variables['#tree'] = $variables['tree']; $variables['tree'] = $variables['tree']['#children']; } @@ -2401,7 +2403,8 @@ // a stripped down menu tree containing the active trail only, in case // the given menu has not been built in this request yet. $tree = menu_tree_page_data($preferred_link['menu_name'], NULL, TRUE); - list($key, $curr) = each($tree); + $curr = current($tree); + next($tree); } // There is no link for the current path. else { @@ -2419,7 +2422,7 @@ // argument placeholders (%). Such links are not contained in regular // menu trees, and have only been loaded for the additional // translation that happens here, so as to be able to display them in - // the breadcumb for the current page. + // the breadcrumb for the current page. // @see _menu_tree_check_access() // @see _menu_link_translate() if (strpos($link['href'], '%') !== FALSE) { @@ -2431,7 +2434,8 @@ } $tree = $curr['below'] ? $curr['below'] : array(); } - list($key, $curr) = each($tree); + $curr = current($tree); + next($tree); } // Make sure the current page is in the trail to build the page title, by // appending either the preferred link or the menu router item for the @@ -2682,7 +2686,7 @@ } /** - * Clears the cached cached data for a single named menu. + * Clears the cached data for a single named menu. */ function menu_cache_clear($menu_name = 'navigation') { $cache_cleared = &drupal_static(__FUNCTION__, array()); diff -Naur drupal-7.41/includes/module.inc drupal-7.66/includes/module.inc --- drupal-7.41/includes/module.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/module.inc 2019-04-17 22:20:46.000000000 +0200 @@ -227,6 +227,10 @@ drupal_static_reset('list_themes'); cache_clear_all('bootstrap_modules', 'cache_bootstrap'); cache_clear_all('system_list', 'cache_bootstrap'); + + // Clean up the bootstrap file scan cache. + drupal_static_reset('_drupal_file_scan_cache'); + cache_clear_all('_drupal_file_scan_cache', 'cache_bootstrap'); } /** @@ -320,16 +324,27 @@ * The name of the included file, if successful; FALSE otherwise. */ function module_load_include($type, $module, $name = NULL) { + static $files = array(); + if (!isset($name)) { $name = $module; } + $key = $type . ':' . $module . ':' . $name; + if (isset($files[$key])) { + return $files[$key]; + } + if (function_exists('drupal_get_path')) { $file = DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . "/$name.$type"; if (is_file($file)) { require_once $file; + $files[$key] = $file; return $file; } + else { + $files[$key] = FALSE; + } } return FALSE; } @@ -365,20 +380,22 @@ * - Invoke hook_modules_installed(). * - Invoke hook_modules_enabled(). * - * @param $module_list + * @param string[] $module_list * An array of module names. - * @param $enable_dependencies + * @param bool $enable_dependencies * If TRUE, dependencies will automatically be added and enabled in the * correct order. This incurs a significant performance cost, so use FALSE * if you know $module_list is already complete and in the correct order. * - * @return + * @return bool * FALSE if one or more dependencies are missing, TRUE otherwise. * * @see hook_install() * @see hook_enable() * @see hook_modules_installed() * @see hook_modules_enabled() + * @see module_disable() + * @see drupal_uninstall_modules() */ function module_enable($module_list, $enable_dependencies = TRUE) { if ($enable_dependencies) { @@ -387,7 +404,11 @@ // Create an associative array with weights as values. $module_list = array_flip(array_values($module_list)); - while (list($module) = each($module_list)) { + // The array is iterated over manually (instead of using a foreach) because + // modules may be added to the list within the loop and we need to process + // them. + while ($module = key($module_list)) { + next($module_list); if (!isset($module_data[$module])) { // This module is not found in the filesystem, abort. return FALSE; @@ -505,12 +526,15 @@ /** * Disables a given set of modules. * - * @param $module_list + * @param string[] $module_list * An array of module names. - * @param $disable_dependents + * @param bool $disable_dependents * If TRUE, dependent modules will automatically be added and disabled in the * correct order. This incurs a significant performance cost, so use FALSE * if you know $module_list is already complete and in the correct order. + * + * @see drupal_uninstall_modules() + * @see module_enable() */ function module_disable($module_list, $disable_dependents = TRUE) { if ($disable_dependents) { @@ -520,7 +544,11 @@ $module_list = array_flip(array_values($module_list)); $profile = drupal_get_profile(); - while (list($module) = each($module_list)) { + // The array is iterated over manually (instead of using a foreach) because + // modules may be added to the list within the loop and we need to process + // them. + while ($module = key($module_list)) { + next($module_list); if (!isset($module_data[$module]) || !$module_data[$module]->status) { // This module doesn't exist or is already disabled, skip it. unset($module_list[$module]); @@ -722,6 +750,7 @@ drupal_static_reset('module_hook_info'); drupal_static_reset('drupal_alter'); cache_clear_all('hook_info', 'cache_bootstrap'); + cache_clear_all('system_cache_tables', 'cache'); return; } @@ -919,7 +948,9 @@ * * @return * An array of return values of the hook implementations. If modules return - * arrays from their implementations, those are merged into one array. + * arrays from their implementations, those are merged into one array + * recursively. Note: integer keys in arrays will be lost, as the merge is + * done using array_merge_recursive(). * * @see drupal_alter() */ diff -Naur drupal-7.41/includes/path.inc drupal-7.66/includes/path.inc --- drupal-7.41/includes/path.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/path.inc 2019-04-17 22:20:46.000000000 +0200 @@ -347,7 +347,8 @@ * drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL) makes this function available. * * @return - * The current Drupal URL path. + * The current Drupal URL path. The path is untrusted user input and must be + * treated as such. * * @see request_path() */ diff -Naur drupal-7.41/includes/registry.inc drupal-7.66/includes/registry.inc --- drupal-7.41/includes/registry.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/registry.inc 2019-04-17 22:20:46.000000000 +0200 @@ -19,7 +19,6 @@ * Does the work for registry_update(). */ function _registry_update() { - // The registry serves as a central autoloader for all classes, including // the database query builders. However, the registry rebuild process // requires write ability to the database, which means having access to the @@ -33,6 +32,11 @@ require_once DRUPAL_ROOT . '/includes/database/select.inc'; require_once DRUPAL_ROOT . '/includes/database/' . $driver . '/query.inc'; + // During the first registry rebuild in a request, we check all the files. + // During subsequent rebuilds, we only add new files. It makes the rebuilding + // process faster during installation of modules. + static $check_existing_files = TRUE; + // Get current list of modules and their files. $modules = db_query("SELECT * FROM {system} WHERE type = 'module'")->fetchAll(); // Get the list of files we are going to parse. @@ -55,6 +59,9 @@ $files["$filename"] = array('module' => '', 'weight' => 0); } + // Initialize an empty array for the unchanged files. + $unchanged_files = array(); + $transaction = db_transaction(); try { // Allow modules to manually modify the list of files before the registry @@ -63,10 +70,19 @@ // list can then be added to the list of files that the registry will parse, // or modify attributes of a file. drupal_alter('registry_files', $files, $modules); + foreach (registry_get_parsed_files() as $filename => $file) { // Add the hash for those files we have already parsed. if (isset($files[$filename])) { - $files[$filename]['hash'] = $file['hash']; + if ($check_existing_files === TRUE) { + $files[$filename]['hash'] = $file['hash']; + } + else { + // Ignore that file for this request, it has been parsed previously + // and it is unlikely it has changed. + unset($files[$filename]); + $unchanged_files[$filename] = $file; + } } else { // Flush the registry of resources in files that are no longer on disc @@ -79,8 +95,12 @@ ->execute(); } } + $parsed_files = _registry_parse_files($files); + // Add unchanged files to the files. + $files += $unchanged_files; + $unchanged_resources = array(); $lookup_cache = array(); if ($cache = cache_get('lookup_cache', 'cache_bootstrap')) { @@ -89,12 +109,10 @@ foreach ($lookup_cache as $key => $file) { // If the file for this cached resource is carried over unchanged from // the last registry build, then we can safely re-cache it. - if ($file && in_array($file, array_keys($files)) && !in_array($file, $parsed_files)) { + if ($file && isset($files[$file]) && !in_array($file, $parsed_files, TRUE)) { $unchanged_resources[$key] = $file; } } - module_implements('', FALSE, TRUE); - _registry_check_code(REGISTRY_RESET_LOOKUP_CACHE); } catch (Exception $e) { $transaction->rollback(); @@ -102,6 +120,13 @@ throw $e; } + module_implements('', FALSE, TRUE); + _registry_check_code(REGISTRY_RESET_LOOKUP_CACHE); + + // During the next run in this request, don't bother re-checking existing + // files. + $check_existing_files = FALSE; + // We have some unchanged resources, warm up the cache - no need to pay // for looking them up again. if (count($unchanged_resources) > 0) { diff -Naur drupal-7.41/includes/request-sanitizer.inc drupal-7.66/includes/request-sanitizer.inc --- drupal-7.41/includes/request-sanitizer.inc 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/includes/request-sanitizer.inc 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,114 @@ + implode(', ', $get_sanitized_keys))), E_USER_NOTICE); + } + + // Process request body parameters. + $post_sanitized_keys = array(); + $_POST = self::stripDangerousValues($_POST, $whitelist, $post_sanitized_keys); + if ($log_sanitized_keys && $post_sanitized_keys) { + _drupal_trigger_error_with_delayed_logging(format_string('Potentially unsafe keys removed from request body parameters (POST): @keys', array('@keys' => implode(', ', $post_sanitized_keys))), E_USER_NOTICE); + } + + // Process cookie parameters. + $cookie_sanitized_keys = array(); + $_COOKIE = self::stripDangerousValues($_COOKIE, $whitelist, $cookie_sanitized_keys); + if ($log_sanitized_keys && $cookie_sanitized_keys) { + _drupal_trigger_error_with_delayed_logging(format_string('Potentially unsafe keys removed from cookie parameters (COOKIE): @keys', array('@keys' => implode(', ', $cookie_sanitized_keys))), E_USER_NOTICE); + } + + $request_sanitized_keys = array(); + $_REQUEST = self::stripDangerousValues($_REQUEST, $whitelist, $request_sanitized_keys); + + self::$sanitized = TRUE; + } + } + + /** + * Removes the destination if it is dangerous. + * + * Note this can only be called after common.inc has been included. + * + * @return bool + * TRUE if the destination has been removed from $_GET, FALSE if not. + */ + public static function cleanDestination() { + $dangerous_keys = array(); + $log_sanitized_keys = variable_get('sanitize_input_logging', FALSE); + + $parts = drupal_parse_url($_GET['destination']); + // If there is a query string, check its query parameters. + if (!empty($parts['query'])) { + $whitelist = variable_get('sanitize_input_whitelist', array()); + + self::stripDangerousValues($parts['query'], $whitelist, $dangerous_keys); + if (!empty($dangerous_keys)) { + // The destination is removed rather than sanitized to mirror the + // handling of external destinations. + unset($_GET['destination']); + unset($_REQUEST['destination']); + if ($log_sanitized_keys) { + trigger_error(format_string('Potentially unsafe destination removed from query string parameters (GET) because it contained the following keys: @keys', array('@keys' => implode(', ', $dangerous_keys)))); + } + return TRUE; + } + } + return FALSE; + } + + /** + * Strips dangerous keys from the provided input. + * + * @param mixed $input + * The input to sanitize. + * @param string[] $whitelist + * An array of keys to whitelist as safe. + * @param string[] $sanitized_keys + * An array of keys that have been removed. + * + * @return mixed + * The sanitized input. + */ + protected static function stripDangerousValues($input, array $whitelist, array &$sanitized_keys) { + if (is_array($input)) { + foreach ($input as $key => $value) { + if ($key !== '' && $key[0] === '#' && !in_array($key, $whitelist, TRUE)) { + unset($input[$key]); + $sanitized_keys[] = $key; + } + else { + $input[$key] = self::stripDangerousValues($input[$key], $whitelist, $sanitized_keys); + } + } + } + return $input; + } + +} diff -Naur drupal-7.41/includes/session.inc drupal-7.66/includes/session.inc --- drupal-7.41/includes/session.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/session.inc 2019-04-17 22:20:46.000000000 +0200 @@ -163,7 +163,7 @@ try { if (!drupal_save_session()) { // We don't have anything to do if we are not allowed to save the session. - return; + return TRUE; } // Check whether $_SESSION has been changed in this request. @@ -425,7 +425,7 @@ // Nothing to do if we are not allowed to change the session. if (!drupal_save_session()) { - return; + return TRUE; } // Delete session data. @@ -446,6 +446,8 @@ elseif (variable_get('https', FALSE)) { _drupal_session_delete_cookie('S' . session_name(), TRUE); } + + return TRUE; } /** diff -Naur drupal-7.41/includes/stream_wrappers.inc drupal-7.66/includes/stream_wrappers.inc --- drupal-7.41/includes/stream_wrappers.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/stream_wrappers.inc 2019-04-17 22:20:46.000000000 +0200 @@ -133,7 +133,7 @@ * @param $uri * A string containing the URI that should be used for this instance. */ - function setUri($uri); + public function setUri($uri); /** * Returns the stream resource URI. @@ -219,7 +219,6 @@ public function dirname($uri = NULL); } - /** * Drupal stream wrapper base class for local files. * @@ -550,6 +549,155 @@ } /** + * Sets metadata on the stream. + * + * WARNING: Do not call this method directly! It will be called internally by + * PHP itself when one of the following functions is called on a stream URL: + * + * @param string $uri + * A string containing the URI to the file to set metadata on. + * @param int $option + * One of: + * - STREAM_META_TOUCH: The method was called in response to touch(). + * - STREAM_META_OWNER_NAME: The method was called in response to chown() + * with string parameter. + * - STREAM_META_OWNER: The method was called in response to chown(). + * - STREAM_META_GROUP_NAME: The method was called in response to chgrp(). + * - STREAM_META_GROUP: The method was called in response to chgrp(). + * - STREAM_META_ACCESS: The method was called in response to chmod(). + * @param mixed $value + * If option is: + * - STREAM_META_TOUCH: Array consisting of two arguments of the touch() + * function. + * - STREAM_META_OWNER_NAME or STREAM_META_GROUP_NAME: The name of the owner + * user/group as string. + * - STREAM_META_OWNER or STREAM_META_GROUP: The value of the owner + * user/group as integer. + * - STREAM_META_ACCESS: The argument of the chmod() as integer. + * + * @return bool + * Returns TRUE on success or FALSE on failure. If $option is not + * implemented, FALSE should be returned. + * + * @see touch() + * @see chmod() + * @see chown() + * @see chgrp() + * @link http://php.net/manual/streamwrapper.stream-metadata.php + */ + public function stream_metadata($uri, $option, $value) { + $target = $this->getLocalPath($uri); + $return = FALSE; + switch ($option) { + case STREAM_META_TOUCH: + if (!empty($value)) { + $return = touch($target, $value[0], $value[1]); + } + else { + $return = touch($target); + } + break; + + case STREAM_META_OWNER_NAME: + case STREAM_META_OWNER: + $return = chown($target, $value); + break; + + case STREAM_META_GROUP_NAME: + case STREAM_META_GROUP: + $return = chgrp($target, $value); + break; + + case STREAM_META_ACCESS: + $return = chmod($target, $value); + break; + } + if ($return) { + // For convenience clear the file status cache of the underlying file, + // since metadata operations are often followed by file status checks. + clearstatcache(TRUE, $target); + } + return $return; + } + + /** + * Truncate stream. + * + * Will respond to truncation; e.g., through ftruncate(). + * + * @param int $new_size + * The new size. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function stream_truncate($new_size) { + return ftruncate($this->handle, $new_size); + } + + /** + * Retrieve the underlying stream resource. + * + * This method is called in response to stream_select(). + * + * @param int $cast_as + * Can be STREAM_CAST_FOR_SELECT when stream_select() is calling + * stream_cast() or STREAM_CAST_AS_STREAM when stream_cast() is called for + * other uses. + * + * @return resource|false + * The underlying stream resource or FALSE if stream_select() is not + * supported. + * + * @see stream_select() + * @link http://php.net/manual/streamwrapper.stream-cast.php + */ + public function stream_cast($cast_as) { + return $this->handle ? $this->handle : FALSE; + } + + /** + * Change stream options. + * + * This method is called to set options on the stream. + * + * Since Windows systems do not allow it and it is not needed for most use + * cases anyway, this method is not supported on local files and will trigger + * an error and return false. If needed, custom subclasses can provide + * OS-specific implementations for advanced use cases. + * + * @param int $option + * One of: + * - STREAM_OPTION_BLOCKING: The method was called in response to + * stream_set_blocking(). + * - STREAM_OPTION_READ_TIMEOUT: The method was called in response to + * stream_set_timeout(). + * - STREAM_OPTION_WRITE_BUFFER: The method was called in response to + * stream_set_write_buffer(). + * @param int $arg1 + * If option is: + * - STREAM_OPTION_BLOCKING: The requested blocking mode: + * - 1 means blocking. + * - 0 means not blocking. + * - STREAM_OPTION_READ_TIMEOUT: The timeout in seconds. + * - STREAM_OPTION_WRITE_BUFFER: The buffer mode, STREAM_BUFFER_NONE or + * STREAM_BUFFER_FULL. + * @param int $arg2 + * If option is: + * - STREAM_OPTION_BLOCKING: This option is not set. + * - STREAM_OPTION_READ_TIMEOUT: The timeout in microseconds. + * - STREAM_OPTION_WRITE_BUFFER: The requested buffer size. + * + * @return bool + * TRUE on success, FALSE otherwise. If $option is not implemented, FALSE + * should be returned. + */ + public function stream_set_option($option, $arg1, $arg2) { + trigger_error('stream_set_option() not supported for local file based stream wrappers', E_USER_WARNING); + return FALSE; + } + + /** * Support for unlink(). * * @param $uri diff -Naur drupal-7.41/includes/theme.inc drupal-7.66/includes/theme.inc --- drupal-7.41/includes/theme.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/theme.inc 2019-04-17 22:20:46.000000000 +0200 @@ -1248,6 +1248,7 @@ function drupal_find_theme_functions($cache, $prefixes) { $implementations = array(); $functions = get_defined_functions(); + $theme_functions = preg_grep('/^(' . implode(')|(', $prefixes) . ')_/', $functions['user']); foreach ($cache as $hook => $info) { foreach ($prefixes as $prefix) { @@ -1264,7 +1265,7 @@ // intermediary suggestion. $pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__'); if (!isset($info['base hook']) && !empty($pattern)) { - $matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $functions['user']); + $matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $theme_functions); if ($matches) { foreach ($matches as $match) { $new_hook = substr($match, strlen($prefix) + 1); @@ -1775,13 +1776,13 @@ * http://www.w3.org/TR/WCAG-TECHS/H42.html for more information. */ function theme_links($variables) { - $links = $variables['links']; - $attributes = $variables['attributes']; + $links = (array) $variables['links']; + $attributes = (array) $variables['attributes']; $heading = $variables['heading']; global $language_url; $output = ''; - if (count($links) > 0) { + if (!empty($links)) { // Treat the heading first if it is present to prepend it to the // list of links. if (!empty($heading)) { @@ -1809,7 +1810,8 @@ foreach ($links as $key => $link) { $class = array($key); - // Add first, last and active classes to the list of links to help out themers. + // Add first, last and active classes to the list of links to help out + // themers. if ($i == 1) { $class[] = 'first'; } @@ -1827,7 +1829,8 @@ $output .= l($link['title'], $link['href'], $link); } elseif (!empty($link['title'])) { - // Some links are actually not links, but we wrap these in for adding title and class attributes. + // Some links are actually not links, but we wrap these in for + // adding title and class attributes. if (empty($link['html'])) { $link['title'] = check_plain($link['title']); } @@ -1992,7 +1995,7 @@ $empty = $variables['empty']; // Add sticky headers, if applicable. - if (count($header) && $sticky) { + if (!empty($header) && $sticky) { drupal_add_js('misc/tableheader.js'); // Add 'sticky-enabled' class to the table to identify it for JS. // This is needed to target tables constructed by this function. @@ -2006,7 +2009,7 @@ } // Format the table columns: - if (count($colgroups)) { + if (!empty($colgroups)) { foreach ($colgroups as $number => $colgroup) { $attributes = array(); @@ -2041,38 +2044,40 @@ } // Add the 'empty' row message if available. - if (!count($rows) && $empty) { + if (empty($rows) && $empty) { $header_count = 0; - foreach ($header as $header_cell) { - if (is_array($header_cell)) { - $header_count += isset($header_cell['colspan']) ? $header_cell['colspan'] : 1; - } - else { - $header_count++; + if (!empty($header)) { + foreach ($header as $header_cell) { + if (is_array($header_cell)) { + $header_count += isset($header_cell['colspan']) ? $header_cell['colspan'] : 1; + } + else { + $header_count++; + } } } $rows[] = array(array('data' => $empty, 'colspan' => $header_count, 'class' => array('empty', 'message'))); } // Format the table header: - if (count($header)) { + if (!empty($header)) { $ts = tablesort_init($header); // HTML requires that the thead tag has tr tags in it followed by tbody // tags. Using ternary operator to check and see if we have any rows. - $output .= (count($rows) ? ' ' : ' '); + $output .= (!empty($rows) ? ' ' : ' '); foreach ($header as $cell) { $cell = tablesort_header($cell, $header, $ts); $output .= _theme_table_cell($cell, TRUE); } // Using ternary operator to close the tags based on whether or not there are rows - $output .= (count($rows) ? " \n" : "\n"); + $output .= (!empty($rows) ? " \n" : "\n"); } else { $ts = array(); } // Format the table rows: - if (count($rows)) { + if (!empty($rows)) { $output .= "\n"; $flip = array('even' => 'odd', 'odd' => 'even'); $class = 'even'; @@ -2092,7 +2097,7 @@ $attributes = array(); $no_striping = FALSE; } - if (count($cells)) { + if (!empty($cells)) { // Add odd/even class if (!$no_striping) { $class = $flip[$class]; @@ -2636,7 +2641,7 @@ // Move some variables to the top level for themer convenience and template cleanliness. $variables['show_messages'] = $variables['page']['#show_messages']; - foreach (system_region_list($GLOBALS['theme']) as $region_key => $region_name) { + foreach (system_region_list($GLOBALS['theme'], REGIONS_ALL, FALSE) as $region_key) { if (!isset($variables['page'][$region_key])) { $variables['page'][$region_key] = array(); } diff -Naur drupal-7.41/includes/update.inc drupal-7.66/includes/update.inc --- drupal-7.41/includes/update.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/update.inc 2019-04-17 22:20:46.000000000 +0200 @@ -795,6 +795,14 @@ function update_fix_d7_install_profile() { $profile = drupal_get_profile(); + // 'Default' profile has been renamed to 'Standard' in D7. + // We change the profile here to prevent a broken record in the system table. + // See system_update_7049(). + if ($profile == 'default') { + $profile = 'standard'; + variable_set('install_profile', $profile); + } + $results = db_select('system', 's') ->fields('s', array('name', 'schema_version')) ->condition('name', $profile) diff -Naur drupal-7.41/includes/xmlrpcs.inc drupal-7.66/includes/xmlrpcs.inc --- drupal-7.41/includes/xmlrpcs.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/includes/xmlrpcs.inc 2019-04-17 22:20:46.000000000 +0200 @@ -264,6 +264,10 @@ */ function xmlrpc_server_multicall($methodcalls) { // See http://www.xmlrpc.com/discuss/msgReader$1208 + // To avoid multicall expansion attacks, limit the number of duplicate method + // calls allowed with a default of 1. Set to -1 for unlimited. + $duplicate_method_limit = variable_get('xmlrpc_multicall_duplicate_method_limit', 1); + $method_count = array(); $return = array(); $xmlrpc_server = xmlrpc_server_get(); foreach ($methodcalls as $call) { @@ -273,10 +277,14 @@ $ok = FALSE; } $method = $call['methodName']; + $method_count[$method] = isset($method_count[$method]) ? $method_count[$method] + 1 : 1; $params = $call['params']; if ($method == 'system.multicall') { $result = xmlrpc_error(-32600, t('Recursive calls to system.multicall are forbidden.')); } + elseif ($duplicate_method_limit > 0 && $method_count[$method] > $duplicate_method_limit) { + $result = xmlrpc_error(-156579, t('Too many duplicate method calls in system.multicall.')); + } elseif ($ok) { $result = xmlrpc_server_call($xmlrpc_server, $method, $params); } diff -Naur drupal-7.41/misc/ajax.js drupal-7.66/misc/ajax.js --- drupal-7.41/misc/ajax.js 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/misc/ajax.js 2019-04-17 22:20:46.000000000 +0200 @@ -476,7 +476,7 @@ * Handler for the form redirection error. */ Drupal.ajax.prototype.error = function (xmlhttprequest, uri, customMessage) { - alert(Drupal.ajaxError(xmlhttprequest, uri, customMessage)); + Drupal.displayAjaxError(Drupal.ajaxError(xmlhttprequest, uri, customMessage)); // Remove the progress element. if (this.progress.element) { $(this.progress.element).remove(); diff -Naur drupal-7.41/misc/autocomplete.js drupal-7.66/misc/autocomplete.js --- drupal-7.41/misc/autocomplete.js 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/misc/autocomplete.js 2019-04-17 22:20:46.000000000 +0200 @@ -310,7 +310,7 @@ } }, error: function (xmlhttp) { - alert(Drupal.ajaxError(xmlhttp, db.uri)); + Drupal.displayAjaxError(Drupal.ajaxError(xmlhttp, db.uri)); } }); }, this.delay); diff -Naur drupal-7.41/misc/drupal.js drupal-7.66/misc/drupal.js --- drupal-7.41/misc/drupal.js 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/misc/drupal.js 2019-04-17 22:20:46.000000000 +0200 @@ -28,6 +28,42 @@ $.fn.init.prototype = jquery_init.prototype; /** + * Pre-filter Ajax requests to guard against XSS attacks. + * + * See https://github.com/jquery/jquery/issues/2432 + */ +if ($.ajaxPrefilter) { + // For newer versions of jQuery, use an Ajax prefilter to prevent + // auto-executing script tags from untrusted domains. This is similar to the + // fix that is built in to jQuery 3.0 and higher. + $.ajaxPrefilter(function (s) { + if (s.crossDomain) { + s.contents.script = false; + } + }); +} +else if ($.httpData) { + // For the version of jQuery that ships with Drupal core, override + // jQuery.httpData to prevent auto-detecting "script" data types from + // untrusted domains. + var jquery_httpData = $.httpData; + $.httpData = function (xhr, type, s) { + // @todo Consider backporting code from newer jQuery versions to check for + // a cross-domain request here, rather than using Drupal.urlIsLocal() to + // block scripts from all URLs that are not on the same site. + if (!type && !Drupal.urlIsLocal(s.url)) { + var content_type = xhr.getResponseHeader('content-type') || ''; + if (content_type.indexOf('javascript') >= 0) { + // Default to a safe data type. + type = 'text'; + } + } + return jquery_httpData.call(this, xhr, type, s); + }; + $.httpData.prototype = jquery_httpData.prototype; +} + +/** * Attach all registered behaviors to a page element. * * Behaviors are event-triggered actions that attach to page elements, enhancing @@ -137,7 +173,7 @@ */ Drupal.checkPlain = function (str) { var character, regex, - replace = { '&': '&', '"': '"', '<': '<', '>': '>' }; + replace = { '&': '&', "'": ''', '"': '"', '<': '<', '>': '>' }; str = String(str); for (character in replace) { if (replace.hasOwnProperty(character)) { @@ -168,23 +204,76 @@ Drupal.formatString = function(str, args) { // Transform arguments before inserting them. for (var key in args) { - switch (key.charAt(0)) { - // Escaped only. - case '@': - args[key] = Drupal.checkPlain(args[key]); - break; - // Pass-through. - case '!': - break; - // Escaped and placeholder. - case '%': - default: - args[key] = Drupal.theme('placeholder', args[key]); - break; + if (args.hasOwnProperty(key)) { + switch (key.charAt(0)) { + // Escaped only. + case '@': + args[key] = Drupal.checkPlain(args[key]); + break; + // Pass-through. + case '!': + break; + // Escaped and placeholder. + default: + args[key] = Drupal.theme('placeholder', args[key]); + break; + } } - str = str.replace(key, args[key]); } - return str; + + return Drupal.stringReplace(str, args, null); +}; + +/** + * Replace substring. + * + * The longest keys will be tried first. Once a substring has been replaced, + * its new value will not be searched again. + * + * @param {String} str + * A string with placeholders. + * @param {Object} args + * Key-value pairs. + * @param {Array|null} keys + * Array of keys from the "args". Internal use only. + * + * @return {String} + * Returns the replaced string. + */ +Drupal.stringReplace = function (str, args, keys) { + if (str.length === 0) { + return str; + } + + // If the array of keys is not passed then collect the keys from the args. + if (!$.isArray(keys)) { + keys = []; + for (var k in args) { + if (args.hasOwnProperty(k)) { + keys.push(k); + } + } + + // Order the keys by the character length. The shortest one is the first. + keys.sort(function (a, b) { return a.length - b.length; }); + } + + if (keys.length === 0) { + return str; + } + + // Take next longest one from the end. + var key = keys.pop(); + var fragments = str.split(key); + + if (keys.length) { + for (var i = 0; i < fragments.length; i++) { + // Process each fragment with a copy of remaining keys. + fragments[i] = Drupal.stringReplace(fragments[i], args, keys.slice(0)); + } + } + + return fragments.join(args[key]); }; /** @@ -251,7 +340,7 @@ * A translated string. */ Drupal.formatPlural = function (count, singular, plural, args, options) { - var args = args || {}; + args = args || {}; args['@count'] = count; // Determine the index of the plural form. var index = Drupal.locale.pluralFormula ? Drupal.locale.pluralFormula(args['@count']) : ((args['@count'] == 1) ? 0 : 1); @@ -414,6 +503,29 @@ }; /** + * Add a global variable which determines if the window is being unloaded. + * + * This is primarily used by Drupal.displayAjaxError(). + */ +Drupal.beforeUnloadCalled = false; +$(window).bind('beforeunload pagehide', function () { + Drupal.beforeUnloadCalled = true; +}); + +/** + * Displays a JavaScript error from an Ajax response when appropriate to do so. + */ +Drupal.displayAjaxError = function (message) { + // Skip displaying the message if the user deliberately aborted (for example, + // by reloading the page or navigating to a different page) while the Ajax + // request was still ongoing. See, for example, the discussion at + // http://stackoverflow.com/questions/699941/handle-ajax-error-when-a-user-clicks-refresh. + if (!Drupal.beforeUnloadCalled) { + alert(message); + } +}; + +/** * Build an error message from an Ajax response. */ Drupal.ajaxError = function (xmlhttp, uri, customMessage) { diff -Naur drupal-7.41/misc/jquery-extend-3.4.0.js drupal-7.66/misc/jquery-extend-3.4.0.js --- drupal-7.41/misc/jquery-extend-3.4.0.js 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/misc/jquery-extend-3.4.0.js 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,112 @@ +/** + * For jQuery versions less than 3.4.0, this replaces the jQuery.extend + * function with the one from jQuery 3.4.0, slightly modified (documented + * below) to be compatible with older jQuery versions and browsers. + * + * This provides the Object.prototype pollution vulnerability fix to Drupal + * installations running older jQuery versions, including the versions shipped + * with Drupal core and https://www.drupal.org/project/jquery_update. + * + * @see https://github.com/jquery/jquery/pull/4333 + */ + +(function (jQuery) { + +// Do not override jQuery.extend() if the jQuery version is already >=3.4.0. +var versionParts = jQuery.fn.jquery.split('.'); +var majorVersion = parseInt(versionParts[0]); +var minorVersion = parseInt(versionParts[1]); +var patchVersion = parseInt(versionParts[2]); +var isPreReleaseVersion = (patchVersion.toString() !== versionParts[2]); +if ( + (majorVersion > 3) || + (majorVersion === 3 && minorVersion > 4) || + (majorVersion === 3 && minorVersion === 4 && patchVersion > 0) || + (majorVersion === 3 && minorVersion === 4 && patchVersion === 0 && !isPreReleaseVersion) +) { + return; +} + +/** + * This is almost verbatim copied from jQuery 3.4.0. + * + * Only two minor changes have been made: + * - The call to isFunction() is changed to jQuery.isFunction(). + * - The two calls to Array.isArray() is changed to jQuery.isArray(). + * + * The above two changes ensure compatibility with all older jQuery versions + * (1.4.4 - 3.3.1) and older browser versions (e.g., IE8). + */ +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = jQuery.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !jQuery.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +})(jQuery); diff -Naur drupal-7.41/misc/tabledrag.js drupal-7.66/misc/tabledrag.js --- drupal-7.41/misc/tabledrag.js 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/misc/tabledrag.js 2019-04-17 22:20:46.000000000 +0200 @@ -106,8 +106,10 @@ // Add mouse bindings to the document. The self variable is passed along // as event handlers do not have direct access to the tableDrag object. - $(document).bind('mousemove', function (event) { return self.dragRow(event, self); }); - $(document).bind('mouseup', function (event) { return self.dropRow(event, self); }); + $(document).bind('mousemove pointermove', function (event) { return self.dragRow(event, self); }); + $(document).bind('mouseup pointerup', function (event) { return self.dropRow(event, self); }); + $(document).bind('touchmove', function (event) { return self.dragRow(event.originalEvent.touches[0], self); }); + $(document).bind('touchend', function (event) { return self.dropRow(event.originalEvent.touches[0], self); }); }; /** @@ -274,7 +276,10 @@ }); // Add the mousedown action for the handle. - handle.mousedown(function (event) { + handle.bind('mousedown touchstart pointerdown', function (event) { + if (event.originalEvent.type == "touchstart") { + event = event.originalEvent.touches[0]; + } // Create a new dragObject recording the event information. self.dragObject = {}; self.dragObject.initMouseOffset = self.getMouseOffset(item, event); @@ -575,13 +580,43 @@ * Get the mouse coordinates from the event (allowing for browser differences). */ Drupal.tableDrag.prototype.mouseCoords = function (event) { - if (event.pageX || event.pageY) { - return { x: event.pageX, y: event.pageY }; + + // Match both null and undefined, but not zero, by using != null. + // See https://stackoverflow.com/questions/2647867/how-to-determine-if-variable-is-undefined-or-null + if (event.pageX != null && event.pageY != null) { + return {x: event.pageX, y: event.pageY}; + } + + // Complete support for pointer events was only introduced to jQuery in + // version 1.11.1; between versions 1.7 and 1.11.0 pointer events have the + // pageX and pageY properties undefined. In those cases, the properties must + // be retrieved from the event.originalEvent object instead. + if (event.originalEvent && event.originalEvent.pageX != null && event.originalEvent.pageY != null) { + return {x: event.originalEvent.pageX, y: event.originalEvent.pageY}; + } + + // Some old browsers do not support MouseEvent.pageX and *.pageY at all. + // See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageY + // For those, we look at event.clientX and event.clientY instead. + if (event.clientX == null || event.clientY == null) { + // In some jQuery versions, some events created by jQuery do not have + // clientX and clientY. But the original event might have. + if (!event.originalEvent) { + throw new Error("The event has no coordinates, and no event.originalEvent."); + } + event = event.originalEvent; + if (event.clientX == null || event.clientY == null) { + throw new Error("The original event has no coordinates."); + } } - return { - x: event.clientX + document.body.scrollLeft - document.body.clientLeft, - y: event.clientY + document.body.scrollTop - document.body.clientTop - }; + + // Copied from jQuery.event.fix() in jQuery 1.4.1. + // In newer jQuery versions, this code is in jQuery.event.mouseHooks.filter(). + var doc = document.documentElement, body = document.body; + var pageX = event.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + var pageY = event.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + + return {x: pageX, y: pageY}; }; /** diff -Naur drupal-7.41/misc/typo3/drupal-security/PharExtensionInterceptor.php drupal-7.66/misc/typo3/drupal-security/PharExtensionInterceptor.php --- drupal-7.41/misc/typo3/drupal-security/PharExtensionInterceptor.php 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/misc/typo3/drupal-security/PharExtensionInterceptor.php 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,79 @@ +baseFileContainsPharExtension($path)) { + return TRUE; + } + throw new Exception( + sprintf( + 'Unexpected file extension in "%s"', + $path + ), + 1535198703 + ); + } + + /** + * Determines if a path has a .phar extension or invoked execution. + * + * @param string $path + * The path of the phar file to check. + * + * @return bool + * TRUE if the file has a .phar extension or if the execution has been + * invoked by the phar file. + */ + private function baseFileContainsPharExtension($path) { + $baseFile = Helper::determineBaseFile($path); + if ($baseFile === NULL) { + return FALSE; + } + // If the stream wrapper is registered by invoking a phar file that does + // not not have .phar extension then this should be allowed. For + // example, some CLI tools recommend removing the extension. + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + // Find the last entry in the backtrace containing a 'file' key as + // sometimes the last caller is executed outside the scope of a file. For + // example, this occurs with shutdown functions. + do { + $caller = array_pop($backtrace); + } while (empty($caller['file']) && !empty($backtrace)); + if (isset($caller['file']) && $baseFile === Helper::determineBaseFile($caller['file'])) { + return TRUE; + } + $fileExtension = pathinfo($baseFile, PATHINFO_EXTENSION); + return strtolower($fileExtension) === 'phar'; + } + +} diff -Naur drupal-7.41/misc/typo3/phar-stream-wrapper/LICENSE drupal-7.66/misc/typo3/phar-stream-wrapper/LICENSE --- drupal-7.41/misc/typo3/phar-stream-wrapper/LICENSE 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/misc/typo3/phar-stream-wrapper/LICENSE 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 TYPO3 project - https://typo3.org/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff -Naur drupal-7.41/misc/typo3/phar-stream-wrapper/README.md drupal-7.66/misc/typo3/phar-stream-wrapper/README.md --- drupal-7.41/misc/typo3/phar-stream-wrapper/README.md 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/misc/typo3/phar-stream-wrapper/README.md 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,155 @@ +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/TYPO3/phar-stream-wrapper/badges/quality-score.png?b=v2)](https://scrutinizer-ci.com/g/TYPO3/phar-stream-wrapper/?branch=v2) +[![Travis CI Build Status](https://travis-ci.org/TYPO3/phar-stream-wrapper.svg?branch=v2)](https://travis-ci.org/TYPO3/phar-stream-wrapper) + +# PHP Phar Stream Wrapper + +## Abstract & History + +Based on Sam Thomas' findings concerning +[insecure deserialization in combination with obfuscation strategies](https://blog.secarma.co.uk/labs/near-phar-dangerous-unserialization-wherever-you-are) +allowing to hide Phar files inside valid image resources, the TYPO3 project +decided back then to introduce a `PharStreamWrapper` to intercept invocations +of the `phar://` stream in PHP and only allow usage for defined locations in +the file system. + +Since the TYPO3 mission statement is **inspiring people to share**, we thought +it would be helpful for others to release our `PharStreamWrapper` as standalone +package to the PHP community. + +The mentioned security issue was reported to TYPO3 on 10th June 2018 by Sam Thomas +and has been addressed concerning the specific attack vector and for this generic +`PharStreamWrapper` in TYPO3 versions 7.6.30 LTS, 8.7.17 LTS and 9.3.1 on 12th +July 2018. + +* https://typo3.org/security/advisory/typo3-core-sa-2018-002/ +* https://blog.secarma.co.uk/labs/near-phar-dangerous-unserialization-wherever-you-are +* https://youtu.be/GePBmsNJw6Y + +## License + +In general the TYPO3 core is released under the GNU General Public License version +2 or any later version (`GPL-2.0-or-later`). In order to avoid licensing issues and +incompatibilities this `PharStreamWrapper` is licenced under the MIT License. In case +you duplicate or modify source code, credits are not required but really appreciated. + +## Credits + +Thanks to [Alex Pott](https://github.com/alexpott), Drupal for creating +back-ports of all sources in order to provide compatibility with PHP v5.3. + +## Installation + +The `PharStreamWrapper` is provided as composer package `typo3/phar-stream-wrapper` +and has minimum requirements of PHP v5.3 ([`v2`](https://github.com/TYPO3/phar-stream-wrapper/tree/v2) branch) and PHP v7.0 ([`master`](https://github.com/TYPO3/phar-stream-wrapper) branch). + +### Installation for PHP v7.0 + +``` +composer require typo3/phar-stream-wrapper ^3.0 +``` + +### Installation for PHP v5.3 + +``` +composer require typo3/phar-stream-wrapper ^2.0 +``` + +## Example + +The following example is bundled within this package, the shown +`PharExtensionInterceptor` denies all stream wrapper invocations files +not having the `.phar` suffix. Interceptor logic has to be individual and +adjusted to according requirements. + +``` +$behavior = new \TYPO3\PharStreamWrapper\Behavior(); +Manager::initialize( + $behavior->withAssertion(new PharExtensionInterceptor()) +); + +if (in_array('phar', stream_get_wrappers())) { + stream_wrapper_unregister('phar'); + stream_wrapper_register('phar', 'TYPO3\\PharStreamWrapper\\PharStreamWrapper'); +} +``` + +* `PharStreamWrapper` defined as class reference will be instantiated each time + `phar://` streams shall be processed. +* `Manager` as singleton pattern being called by `PharStreamWrapper` instances + in order to retrieve individual behavior and settings. +* `Behavior` holds reference to interceptor(s) that shall assert correct/allowed + invocation of a given `$path` for a given `$command`. Interceptors implement + the interface `Assertable`. Interceptors can act individually on following + commands or handle all of them in case not defined specifically: + + `COMMAND_DIR_OPENDIR` + + `COMMAND_MKDIR` + + `COMMAND_RENAME` + + `COMMAND_RMDIR` + + `COMMAND_STEAM_METADATA` + + `COMMAND_STREAM_OPEN` + + `COMMAND_UNLINK` + + `COMMAND_URL_STAT` + +## Interceptor + +The following interceptor is shipped with the package and ready to use in order +to block any Phar invocation of files not having a `.phar` suffix. Besides that +individual interceptors are possible of course. + +``` +class PharExtensionInterceptor implements Assertable +{ + /** + * Determines whether the base file name has a ".phar" suffix. + * + * @param string $path + * @param string $command + * @return bool + * @throws Exception + */ + public function assert($path, $command) + { + if ($this->baseFileContainsPharExtension($path)) { + return true; + } + throw new Exception( + sprintf( + 'Unexpected file extension in "%s"', + $path + ), + 1535198703 + ); + } + + /** + * @param string $path + * @return bool + */ + private function baseFileContainsPharExtension($path) + { + $baseFile = Helper::determineBaseFile($path); + if ($baseFile === null) { + return false; + } + $fileExtension = pathinfo($baseFile, PATHINFO_EXTENSION); + return strtolower($fileExtension) === 'phar'; + } +} +``` + +## Helper + +* `Helper::determineBaseFile(string $path)`: Determines base file that can be + accessed using the regular file system. For instance the following path + `phar:///home/user/bundle.phar/content.txt` would be resolved to + `/home/user/bundle.phar`. +* `Helper::resetOpCache()`: Resets PHP's OPcache if enabled as work-around for + issues in `include()` or `require()` calls and OPcache delivering wrong + results. More details can be found in PHP's bug tracker, for instance like + https://bugs.php.net/bug.php?id=66569 + +## Security Contact + +In case of finding additional security issues in the TYPO3 project or in this +`PharStreamWrapper` package in particular, please get in touch with the +[TYPO3 Security Team](mailto:security@typo3.org). diff -Naur drupal-7.41/misc/typo3/phar-stream-wrapper/composer.json drupal-7.66/misc/typo3/phar-stream-wrapper/composer.json --- drupal-7.41/misc/typo3/phar-stream-wrapper/composer.json 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/misc/typo3/phar-stream-wrapper/composer.json 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,24 @@ +{ + "name": "typo3/phar-stream-wrapper", + "description": "Interceptors for PHP's native phar:// stream handling", + "type": "library", + "license": "MIT", + "homepage": "https://typo3.org/", + "keywords": ["php", "phar", "stream-wrapper", "security"], + "require": { + "php": "^5.3.3|^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "autoload": { + "psr-4": { + "TYPO3\\PharStreamWrapper\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "TYPO3\\PharStreamWrapper\\Tests\\": "tests/" + } + } +} diff -Naur drupal-7.41/misc/typo3/phar-stream-wrapper/src/Assertable.php drupal-7.66/misc/typo3/phar-stream-wrapper/src/Assertable.php --- drupal-7.41/misc/typo3/phar-stream-wrapper/src/Assertable.php 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/misc/typo3/phar-stream-wrapper/src/Assertable.php 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,22 @@ +assertCommands($commands); + $commands = $commands ?: $this->availableCommands; + + $target = clone $this; + foreach ($commands as $command) { + $target->assertions[$command] = $assertable; + } + return $target; + } + + /** + * @param string $path + * @param string $command + * @return bool + */ + public function assert($path, $command) + { + $this->assertCommand($command); + $this->assertAssertionCompleteness(); + + return $this->assertions[$command]->assert($path, $command); + } + + /** + * @param array $commands + */ + private function assertCommands(array $commands) + { + $unknownCommands = array_diff($commands, $this->availableCommands); + if (empty($unknownCommands)) { + return; + } + throw new \LogicException( + sprintf( + 'Unknown commands: %s', + implode(', ', $unknownCommands) + ), + 1535189881 + ); + } + + private function assertCommand($command) + { + if (in_array($command, $this->availableCommands, true)) { + return; + } + throw new \LogicException( + sprintf( + 'Unknown command "%s"', + $command + ), + 1535189882 + ); + } + + private function assertAssertionCompleteness() + { + $undefinedAssertions = array_diff( + $this->availableCommands, + array_keys($this->assertions) + ); + if (empty($undefinedAssertions)) { + return; + } + throw new \LogicException( + sprintf( + 'Missing assertions for commands: %s', + implode(', ', $undefinedAssertions) + ), + 1535189883 + ); + } +} diff -Naur drupal-7.41/misc/typo3/phar-stream-wrapper/src/Exception.php drupal-7.66/misc/typo3/phar-stream-wrapper/src/Exception.php --- drupal-7.41/misc/typo3/phar-stream-wrapper/src/Exception.php 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/misc/typo3/phar-stream-wrapper/src/Exception.php 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,16 @@ += 1) { + // Rremove this and previous element + array_splice($pathParts, $partCount - 1, 2); + $partCount -= 2; + $pathPartsLength -= 2; + } elseif ($absolutePathPrefix) { + // can't go higher than root dir + // simply remove this part and continue + array_splice($pathParts, $partCount, 1); + $partCount--; + $pathPartsLength--; + } + } + } + + return $absolutePathPrefix . implode('/', $pathParts); + } + + /** + * Checks if the $path is absolute or relative (detecting either '/' or + * 'x:/' as first part of string) and returns TRUE if so. + * + * @param string $path File path to evaluate + * @return bool + */ + private static function isAbsolutePath($path) + { + // Path starting with a / is always absolute, on every system + // On Windows also a path starting with a drive letter is absolute: X:/ + return (isset($path[0]) ? $path[0] : null) === '/' + || static::isWindows() && ( + strpos($path, ':/') === 1 + || strpos($path, ':\\') === 1 + ); + } + + /** + * @return bool + */ + private static function isWindows() + { + return stripos(PHP_OS, 'WIN') === 0; + } +} \ No newline at end of file diff -Naur drupal-7.41/misc/typo3/phar-stream-wrapper/src/Interceptor/PharExtensionInterceptor.php drupal-7.66/misc/typo3/phar-stream-wrapper/src/Interceptor/PharExtensionInterceptor.php --- drupal-7.41/misc/typo3/phar-stream-wrapper/src/Interceptor/PharExtensionInterceptor.php 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/misc/typo3/phar-stream-wrapper/src/Interceptor/PharExtensionInterceptor.php 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,55 @@ +baseFileContainsPharExtension($path)) { + return true; + } + throw new Exception( + sprintf( + 'Unexpected file extension in "%s"', + $path + ), + 1535198703 + ); + } + + /** + * @param string $path + * @return bool + */ + private function baseFileContainsPharExtension($path) + { + $baseFile = Helper::determineBaseFile($path); + if ($baseFile === null) { + return false; + } + $fileExtension = pathinfo($baseFile, PATHINFO_EXTENSION); + return strtolower($fileExtension) === 'phar'; + } +} diff -Naur drupal-7.41/misc/typo3/phar-stream-wrapper/src/Manager.php drupal-7.66/misc/typo3/phar-stream-wrapper/src/Manager.php --- drupal-7.41/misc/typo3/phar-stream-wrapper/src/Manager.php 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/misc/typo3/phar-stream-wrapper/src/Manager.php 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,85 @@ +behavior = $behaviour; + } + + /** + * @param string $path + * @param string $command + * @return bool + */ + public function assert($path, $command) + { + return $this->behavior->assert($path, $command); + } +} diff -Naur drupal-7.41/misc/typo3/phar-stream-wrapper/src/PharStreamWrapper.php drupal-7.66/misc/typo3/phar-stream-wrapper/src/PharStreamWrapper.php --- drupal-7.41/misc/typo3/phar-stream-wrapper/src/PharStreamWrapper.php 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/misc/typo3/phar-stream-wrapper/src/PharStreamWrapper.php 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,477 @@ +internalResource)) { + return false; + } + + $this->invokeInternalStreamWrapper( + 'closedir', + $this->internalResource + ); + return !is_resource($this->internalResource); + } + + /** + * @param string $path + * @param int $options + * @return bool + */ + public function dir_opendir($path, $options) + { + $this->assert($path, Behavior::COMMAND_DIR_OPENDIR); + $this->internalResource = $this->invokeInternalStreamWrapper( + 'opendir', + $path, + $this->context + ); + return is_resource($this->internalResource); + } + + /** + * @return string|false + */ + public function dir_readdir() + { + return $this->invokeInternalStreamWrapper( + 'readdir', + $this->internalResource + ); + } + + /** + * @return bool + */ + public function dir_rewinddir() + { + if (!is_resource($this->internalResource)) { + return false; + } + + $this->invokeInternalStreamWrapper( + 'rewinddir', + $this->internalResource + ); + return is_resource($this->internalResource); + } + + /** + * @param string $path + * @param int $mode + * @param int $options + * @return bool + */ + public function mkdir($path, $mode, $options) + { + $this->assert($path, Behavior::COMMAND_MKDIR); + return $this->invokeInternalStreamWrapper( + 'mkdir', + $path, + $mode, + (bool) ($options & STREAM_MKDIR_RECURSIVE), + $this->context + ); + } + + /** + * @param string $path_from + * @param string $path_to + * @return bool + */ + public function rename($path_from, $path_to) + { + $this->assert($path_from, Behavior::COMMAND_RENAME); + $this->assert($path_to, Behavior::COMMAND_RENAME); + return $this->invokeInternalStreamWrapper( + 'rename', + $path_from, + $path_to, + $this->context + ); + } + + /** + * @param string $path + * @param int $options + * @return bool + */ + public function rmdir($path, $options) + { + $this->assert($path, Behavior::COMMAND_RMDIR); + return $this->invokeInternalStreamWrapper( + 'rmdir', + $path, + $this->context + ); + } + + /** + * @param int $cast_as + */ + public function stream_cast($cast_as) + { + throw new Exception( + 'Method stream_select() cannot be used', + 1530103999 + ); + } + + public function stream_close() + { + $this->invokeInternalStreamWrapper( + 'fclose', + $this->internalResource + ); + } + + /** + * @return bool + */ + public function stream_eof() + { + return $this->invokeInternalStreamWrapper( + 'feof', + $this->internalResource + ); + } + + /** + * @return bool + */ + public function stream_flush() + { + return $this->invokeInternalStreamWrapper( + 'fflush', + $this->internalResource + ); + } + + /** + * @param int $operation + * @return bool + */ + public function stream_lock($operation) + { + return $this->invokeInternalStreamWrapper( + 'flock', + $this->internalResource, + $operation + ); + } + + /** + * @param string $path + * @param int $option + * @param string|int $value + * @return bool + */ + public function stream_metadata($path, $option, $value) + { + $this->assert($path, Behavior::COMMAND_STEAM_METADATA); + if ($option === STREAM_META_TOUCH) { + return call_user_func_array( + array($this, 'invokeInternalStreamWrapper'), + array_merge(array('touch', $path), (array) $value) + ); + } + if ($option === STREAM_META_OWNER_NAME || $option === STREAM_META_OWNER) { + return $this->invokeInternalStreamWrapper( + 'chown', + $path, + $value + ); + } + if ($option === STREAM_META_GROUP_NAME || $option === STREAM_META_GROUP) { + return $this->invokeInternalStreamWrapper( + 'chgrp', + $path, + $value + ); + } + if ($option === STREAM_META_ACCESS) { + return $this->invokeInternalStreamWrapper( + 'chmod', + $path, + $value + ); + } + return false; + } + + /** + * @param string $path + * @param string $mode + * @param int $options + * @param string|null $opened_path + * @return bool + */ + public function stream_open( + $path, + $mode, + $options, + &$opened_path = null + ) { + $this->assert($path, Behavior::COMMAND_STREAM_OPEN); + $arguments = array($path, $mode, (bool) ($options & STREAM_USE_PATH)); + // only add stream context for non include/require calls + if (!($options & static::STREAM_OPEN_FOR_INCLUDE)) { + $arguments[] = $this->context; + // work around https://bugs.php.net/bug.php?id=66569 + // for including files from Phar stream with OPcache enabled + } else { + Helper::resetOpCache(); + } + $this->internalResource = call_user_func_array( + array($this, 'invokeInternalStreamWrapper'), + array_merge(array('fopen'), $arguments) + ); + if (!is_resource($this->internalResource)) { + return false; + } + if ($opened_path !== null) { + $metaData = stream_get_meta_data($this->internalResource); + $opened_path = $metaData['uri']; + } + return true; + } + + /** + * @param int $count + * @return string + */ + public function stream_read($count) + { + return $this->invokeInternalStreamWrapper( + 'fread', + $this->internalResource, + $count + ); + } + + /** + * @param int $offset + * @param int $whence + * @return bool + */ + public function stream_seek($offset, $whence = SEEK_SET) + { + return $this->invokeInternalStreamWrapper( + 'fseek', + $this->internalResource, + $offset, + $whence + ) !== -1; + } + + /** + * @param int $option + * @param int $arg1 + * @param int $arg2 + * @return bool + */ + public function stream_set_option($option, $arg1, $arg2) + { + if ($option === STREAM_OPTION_BLOCKING) { + return $this->invokeInternalStreamWrapper( + 'stream_set_blocking', + $this->internalResource, + $arg1 + ); + } + if ($option === STREAM_OPTION_READ_TIMEOUT) { + return $this->invokeInternalStreamWrapper( + 'stream_set_timeout', + $this->internalResource, + $arg1, + $arg2 + ); + } + if ($option === STREAM_OPTION_WRITE_BUFFER) { + return $this->invokeInternalStreamWrapper( + 'stream_set_write_buffer', + $this->internalResource, + $arg2 + ) === 0; + } + return false; + } + + /** + * @return array + */ + public function stream_stat() + { + return $this->invokeInternalStreamWrapper( + 'fstat', + $this->internalResource + ); + } + + /** + * @return int + */ + public function stream_tell() + { + return $this->invokeInternalStreamWrapper( + 'ftell', + $this->internalResource + ); + } + + /** + * @param int $new_size + * @return bool + */ + public function stream_truncate($new_size) + { + return $this->invokeInternalStreamWrapper( + 'ftruncate', + $this->internalResource, + $new_size + ); + } + + /** + * @param string $data + * @return int + */ + public function stream_write($data) + { + return $this->invokeInternalStreamWrapper( + 'fwrite', + $this->internalResource, + $data + ); + } + + /** + * @param string $path + * @return bool + */ + public function unlink($path) + { + $this->assert($path, Behavior::COMMAND_UNLINK); + return $this->invokeInternalStreamWrapper( + 'unlink', + $path, + $this->context + ); + } + + /** + * @param string $path + * @param int $flags + * @return array|false + */ + public function url_stat($path, $flags) + { + $this->assert($path, Behavior::COMMAND_URL_STAT); + $functionName = $flags & STREAM_URL_STAT_QUIET ? '@stat' : 'stat'; + return $this->invokeInternalStreamWrapper($functionName, $path); + } + + /** + * @param string $path + * @param string $command + */ + protected function assert($path, $command) + { + if ($this->resolveAssertable()->assert($path, $command) === true) { + return; + } + + throw new Exception( + sprintf( + 'Denied invocation of "%s" for command "%s"', + $path, + $command + ), + 1535189880 + ); + } + + /** + * @return Assertable + */ + protected function resolveAssertable() + { + return Manager::instance(); + } + + /** + * Invokes commands on the native PHP Phar stream wrapper. + * + * @param string $functionName + * @param mixed ...$arguments + * @return mixed + */ + private function invokeInternalStreamWrapper($functionName) + { + $arguments = func_get_args(); + array_shift($arguments); + $silentExecution = $functionName{0} === '@'; + $functionName = ltrim($functionName, '@'); + $this->restoreInternalSteamWrapper(); + + try { + if ($silentExecution) { + $result = @call_user_func_array($functionName, $arguments); + } else { + $result = call_user_func_array($functionName, $arguments); + } + } catch (\Exception $exception) { + $this->registerStreamWrapper(); + throw $exception; + } catch (\Throwable $throwable) { + $this->registerStreamWrapper(); + throw $throwable; + } + + $this->registerStreamWrapper(); + return $result; + } + + private function restoreInternalSteamWrapper() + { + stream_wrapper_restore('phar'); + } + + private function registerStreamWrapper() + { + stream_wrapper_unregister('phar'); + stream_wrapper_register('phar', get_class($this)); + } +} diff -Naur drupal-7.41/modules/aggregator/aggregator.info drupal-7.66/modules/aggregator/aggregator.info --- drupal-7.41/modules/aggregator/aggregator.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/aggregator/aggregator.info 2019-04-17 22:39:36.000000000 +0200 @@ -7,8 +7,7 @@ configure = admin/config/services/aggregator/settings stylesheets[all][] = aggregator.css -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/aggregator/aggregator.module drupal-7.66/modules/aggregator/aggregator.module --- drupal-7.41/modules/aggregator/aggregator.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/aggregator/aggregator.module 2019-04-17 22:20:46.000000000 +0200 @@ -455,6 +455,14 @@ db_delete('aggregator_category') ->condition('cid', $edit['cid']) ->execute(); + // Remove category from feeds. + db_delete('aggregator_category_feed') + ->condition('cid', $edit['cid']) + ->execute(); + // Remove category from feed items. + db_delete('aggregator_category_item') + ->condition('cid', $edit['cid']) + ->execute(); // Make sure there is no active block for this category. if (module_exists('block')) { db_delete('block') diff -Naur drupal-7.41/modules/aggregator/aggregator.processor.inc drupal-7.66/modules/aggregator/aggregator.processor.inc --- drupal-7.41/modules/aggregator/aggregator.processor.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/aggregator/aggregator.processor.inc 2019-04-17 22:20:46.000000000 +0200 @@ -72,7 +72,7 @@ */ function aggregator_form_aggregator_admin_form_alter(&$form, $form_state) { if (in_array('aggregator', variable_get('aggregator_processors', array('aggregator')))) { - $info = module_invoke('aggregator', 'aggregator_process', 'info'); + $info = module_invoke('aggregator', 'aggregator_process_info'); $items = drupal_map_assoc(array(3, 5, 10, 15, 20, 25), '_aggregator_items'); $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval'); $period[AGGREGATOR_CLEAR_NEVER] = t('Never'); diff -Naur drupal-7.41/modules/aggregator/aggregator.test drupal-7.66/modules/aggregator/aggregator.test --- drupal-7.41/modules/aggregator/aggregator.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/aggregator/aggregator.test 2019-04-17 22:20:46.000000000 +0200 @@ -418,7 +418,7 @@ } /** - * Creates a feed and makes sure you can add more than one category to it. + * Creates a feed and makes sure you can add/delete categories to it. */ function testCategorizeFeed() { @@ -448,7 +448,31 @@ // Assert the feed has two categories. $this->getFeedCategories($db_feed); $this->assertEqual(count($db_feed->categories), 2, 'Feed has 2 categories'); + + // Use aggregator_save_feed() to delete a category. + $category = reset($categories); + aggregator_save_category(array('cid' => $category->cid)); + + // Assert that category is deleted. + $db_category = db_query("SELECT COUNT(*) FROM {aggregator_category} WHERE cid = :cid", array(':cid' => $category->cid))->fetchField(); + $this->assertFalse($db_category, format_string('The category %title has been deleted.', array('%title' => $category->title))); + + // Assert that category has been removed from feed. + $categorized_feeds = db_query("SELECT COUNT(*) FROM {aggregator_category_feed} WHERE cid = :cid", array(':cid' => $category->cid))->fetchField(); + $this->assertFalse($categorized_feeds, format_string('The category %title has been removed from feed %feed_title.', array('%title' => $category->title, '%feed_title' => $feed['title']))); + + // Assert that no broken links (associated with the deleted category) + // appear on one of the other category pages. + $this->createSampleNodes(); + $this->drupalGet('admin/config/services/aggregator'); + $this->clickLink('update items'); + $categories = $this->getCategories(); + $category = reset($categories); + $this->drupalGet('aggregator/categories/' . $category->cid); + global $base_path; + $this->assertNoRaw(','); } + } /** @@ -685,9 +709,21 @@ } } + // Delete category from feed items when category is deleted. + $cid = reset($feed->categories); + $categories = $this->getCategories(); + $category_title = $categories[$cid]->title; + + // Delete category. + aggregator_save_category(array('cid' => $cid)); + + // Assert category has been removed from feed items. + $categorized_count = db_query("SELECT COUNT(*) FROM {aggregator_category_item} WHERE cid = :cid", array(':cid' => $cid))->fetchField(); + $this->assertFalse($categorized_count, format_string('The category %title has been removed from feed items.', array('%title' => $category_title))); // Delete feed. $this->deleteFeed($feed); } + } /** diff -Naur drupal-7.41/modules/aggregator/tests/aggregator_test.info drupal-7.66/modules/aggregator/tests/aggregator_test.info --- drupal-7.41/modules/aggregator/tests/aggregator_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/aggregator/tests/aggregator_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/block/block.info drupal-7.66/modules/block/block.info --- drupal-7.41/modules/block/block.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/block/block.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ files[] = block.test configure = admin/structure/block -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/block/block.module drupal-7.66/modules/block/block.module --- drupal-7.41/modules/block/block.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/block/block.module 2019-04-17 22:20:46.000000000 +0200 @@ -16,7 +16,7 @@ define('BLOCK_CUSTOM_FIXED', 0); /** - * Shows this block by default, but lets individual users hide it. + * Shows this block by default, but lets individual users hide it. */ define('BLOCK_CUSTOM_ENABLED', 1); @@ -59,6 +59,7 @@ $output .= '
' . t('Users with the Administer blocks permission can add custom blocks, which are then listed on the Blocks administration page. Once created, custom blocks behave just like default and module-generated blocks.', array('@blocks' => url('admin/structure/block'), '@block-add' => url('admin/structure/block/add'))) . '
'; $output .= ''; return $output; + case 'admin/structure/block/add': return '

' . t('Use this page to create a new custom block.') . '

'; } @@ -189,6 +190,7 @@ * @param $theme * The theme whose blocks are being configured. If not set, the default theme * is assumed. + * * @return * The theme that should be used for the block configuration page, or NULL * to indicate that the default theme should be used. @@ -283,8 +285,7 @@ // Append region description if we are rendering the regions demo page. $item = menu_get_item(); if ($item['path'] == 'admin/structure/block/demo/' . $theme) { - $visible_regions = array_keys(system_region_list($theme, REGIONS_VISIBLE)); - foreach ($visible_regions as $region) { + foreach (system_region_list($theme, REGIONS_VISIBLE, FALSE) as $region) { $description = '
' . $all_regions[$region] . '
'; $page[$region]['block_description'] = array( '#markup' => $description, @@ -343,7 +344,10 @@ // to perform contextual actions on the help block, and the links needlessly // draw attention on it. if ($key != 'system_main' && $key != 'system_help') { - $build[$key]['#contextual_links']['block'] = array('admin/structure/block/manage', array($block->module, $block->delta)); + $build[$key]['#contextual_links']['block'] = array( + 'admin/structure/block/manage', + array($block->module, $block->delta), + ); } $build[$key] += array( @@ -386,18 +390,20 @@ // Gather the blocks defined by modules. foreach (module_implements('block_info') as $module) { $module_blocks = module_invoke($module, 'block_info'); + $delta_list = array(); foreach ($module_blocks as $delta => $block) { // Compile a condition to retrieve this block from the database. - $condition = db_and() - ->condition('module', $module) - ->condition('delta', $delta); - $or->condition($condition); // Add identifiers. + $delta_list[] = $delta; $block['module'] = $module; - $block['delta'] = $delta; - $block['theme'] = $theme; + $block['delta'] = $delta; + $block['theme'] = $theme; $current_blocks[$module][$delta] = $block; } + if (!empty($delta_list)) { + $condition = db_and()->condition('module', $module)->condition('delta', $delta_list); + $or->condition($condition); + } } // Save the blocks defined in code for alter context. $code_blocks = $current_blocks; @@ -426,23 +432,20 @@ drupal_alter('block_info', $current_blocks, $theme, $code_blocks); foreach ($current_blocks as $module => $module_blocks) { foreach ($module_blocks as $delta => $block) { - if (!isset($block['pages'])) { - // {block}.pages is type 'text', so it cannot have a - // default value, and not null, so we need to provide - // value if the module did not. - $block['pages'] = ''; - } - // Make sure weight is set. - if (!isset($block['weight'])) { - $block['weight'] = 0; - } + // Make sure certain attributes are set. + $block += array( + 'pages' => '', + 'weight' => 0, + 'status' => 0, + ); + // Check for active blocks in regions that are not available. if (!empty($block['region']) && $block['region'] != BLOCK_REGION_NONE && !isset($regions[$block['region']]) && $block['status'] == 1) { drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $block['info'], '%region' => $block['region'])), 'warning'); // Disabled modules are moved into the BLOCK_REGION_NONE later so no // need to move the block to another region. $block['status'] = 0; } - // Set region to none if not enabled and make sure status is set. + // Set region to none if not enabled. if (empty($block['status'])) { $block['status'] = 0; $block['region'] = BLOCK_REGION_NONE; @@ -644,7 +647,8 @@ $regions = system_region_list($theme, REGIONS_VISIBLE); $result = db_query("SELECT * FROM {block} WHERE theme = :theme", array(':theme' => $default_theme), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $block) { - // If the region isn't supported by the theme, assign the block to the theme's default region. + // If the region isn't supported by the theme, assign the block to the + // theme's default region. if ($block['status'] && !isset($regions[$block['region']])) { $block['region'] = system_default_region($theme); } @@ -812,17 +816,18 @@ // with different case. Ex: /Page, /page, /PAGE. $pages = drupal_strtolower($block->pages); if ($block->visibility < BLOCK_VISIBILITY_PHP) { - // Convert the Drupal path to lowercase + // Convert the Drupal path to lowercase. $path = drupal_strtolower(drupal_get_path_alias($_GET['q'])); // Compare the lowercase internal and lowercase path alias (if any). $page_match = drupal_match_path($path, $pages); if ($path != $_GET['q']) { $page_match = $page_match || drupal_match_path($_GET['q'], $pages); } - // When $block->visibility has a value of 0 (BLOCK_VISIBILITY_NOTLISTED), - // the block is displayed on all pages except those listed in $block->pages. - // When set to 1 (BLOCK_VISIBILITY_LISTED), it is displayed only on those - // pages listed in $block->pages. + // When $block->visibility has a value of 0 + // (BLOCK_VISIBILITY_NOTLISTED), the block is displayed on all pages + // except those listed in $block->pages. When set to 1 + // (BLOCK_VISIBILITY_LISTED), it is displayed only on those pages + // listed in $block->pages. $page_match = !($block->visibility xor $page_match); } elseif (module_exists('php')) { @@ -845,7 +850,8 @@ * Render the content and subject for a set of blocks. * * @param $region_blocks - * An array of block objects such as returned for one region by _block_load_blocks(). + * An array of block objects such as returned for one region by + * _block_load_blocks(). * * @return * An array of visible blocks as expected by drupal_render(). @@ -953,6 +959,8 @@ * Theme and language contexts are automatically differentiated. * * @param $block + * The block to get the cache_id from. + * * @return * The string used as cache_id for the block. */ diff -Naur drupal-7.41/modules/block/tests/block_test.info drupal-7.66/modules/block/tests/block_test.info --- drupal-7.41/modules/block/tests/block_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/block/tests/block_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/block/tests/themes/block_test_theme/block_test_theme.info drupal-7.66/modules/block/tests/themes/block_test_theme/block_test_theme.info --- drupal-7.41/modules/block/tests/themes/block_test_theme/block_test_theme.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/block/tests/themes/block_test_theme/block_test_theme.info 2019-04-17 22:39:36.000000000 +0200 @@ -13,8 +13,7 @@ regions[highlighted] = Highlighted regions[help] = Help -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/blog/blog.info drupal-7.66/modules/blog/blog.info --- drupal-7.41/modules/blog/blog.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/blog/blog.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x files[] = blog.test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/blog/blog.module drupal-7.66/modules/blog/blog.module --- drupal-7.41/modules/blog/blog.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/blog/blog.module 2019-04-17 22:20:46.000000000 +0200 @@ -152,7 +152,7 @@ } } // Provide a helper action link to the author on the 'blog/%' page. - elseif ($root_path == 'blog/%' && $router_item['page_arguments'][0]->uid == $user->uid) { + elseif ($root_path == 'blog/%' && isset($router_item['page_arguments'][0]->uid) && $router_item['page_arguments'][0]->uid == $user->uid) { $data['actions']['output']['blog'] = array( '#theme' => 'menu_local_action', ); diff -Naur drupal-7.41/modules/book/book.info drupal-7.66/modules/book/book.info --- drupal-7.41/modules/book/book.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/book/book.info 2019-04-17 22:39:36.000000000 +0200 @@ -7,8 +7,7 @@ configure = admin/content/book/settings stylesheets[all][] = book.css -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/book/book.module drupal-7.66/modules/book/book.module --- drupal-7.41/modules/book/book.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/book/book.module 2019-04-17 22:20:46.000000000 +0200 @@ -768,11 +768,13 @@ return NULL; } $flat = book_get_flat_menu($book_link); - // Assigning the array to $flat resets the array pointer for use with each(). + reset($flat); $curr = NULL; do { $prev = $curr; - list($key, $curr) = each($flat); + $curr = current($flat); + $key = key($flat); + next($flat); } while ($key && $key != $book_link['mlid']); if ($key == $book_link['mlid']) { @@ -806,9 +808,10 @@ */ function book_next($book_link) { $flat = book_get_flat_menu($book_link); - // Assigning the array to $flat resets the array pointer for use with each(). + reset($flat); do { - list($key, $curr) = each($flat); + $key = key($flat); + next($flat); } while ($key && $key != $book_link['mlid']); diff -Naur drupal-7.41/modules/color/color.info drupal-7.66/modules/color/color.info --- drupal-7.41/modules/color/color.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/color/color.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x files[] = color.test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/color/color.test drupal-7.66/modules/color/color.test --- drupal-7.41/modules/color/color.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/color/color.test 2019-04-17 22:20:46.000000000 +0200 @@ -122,7 +122,7 @@ $edit['palette[bg]'] = $color; $this->drupalPost($settings_path, $edit, t('Save configuration')); - if($is_valid) { + if ($is_valid) { $this->assertText('The configuration options have been saved.'); } else { diff -Naur drupal-7.41/modules/comment/comment.info drupal-7.66/modules/comment/comment.info --- drupal-7.41/modules/comment/comment.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/comment/comment.info 2019-04-17 22:39:36.000000000 +0200 @@ -9,8 +9,7 @@ configure = admin/content/comment stylesheets[all][] = comment.css -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/comment/comment.test drupal-7.66/modules/comment/comment.test --- drupal-7.41/modules/comment/comment.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/comment/comment.test 2019-04-17 22:20:46.000000000 +0200 @@ -11,9 +11,15 @@ protected $node; function setUp() { - parent::setUp('comment', 'search'); + $modules = func_get_args(); + if (isset($modules[0]) && is_array($modules[0])) { + $modules = $modules[0]; + } + $modules[] = 'comment'; + parent::setUp($modules); + // Create users and test node. - $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer comments', 'administer blocks', 'administer actions')); + $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer comments', 'administer blocks', 'administer actions', 'administer fields')); $this->web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'create article content', 'edit own comments')); $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->web_user->uid)); } @@ -1490,7 +1496,7 @@ } function setUp() { - DrupalWebTestCase::setUp('comment', 'search', 'node_access_test'); + parent::setUp('search', 'node_access_test'); node_access_rebuild(); // Create users and test node. diff -Naur drupal-7.41/modules/contact/contact.info drupal-7.66/modules/contact/contact.info --- drupal-7.41/modules/contact/contact.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/contact/contact.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ files[] = contact.test configure = admin/structure/contact -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/contact/contact.module drupal-7.66/modules/contact/contact.module --- drupal-7.41/modules/contact/contact.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/contact/contact.module 2019-04-17 22:20:46.000000000 +0200 @@ -234,7 +234,14 @@ * Implements hook_user_presave(). */ function contact_user_presave(&$edit, $account, $category) { - $edit['data']['contact'] = isset($edit['contact']) ? $edit['contact'] : variable_get('contact_default_status', 1); + if (isset($edit['contact'])) { + // Set new value. + $edit['data']['contact'] = $edit['contact']; + } + elseif (!isset($account->data['contact'])) { + // Use default if none has been set. + $edit['data']['contact'] = variable_get('contact_default_status', 1); + } } /** diff -Naur drupal-7.41/modules/contact/contact.test drupal-7.66/modules/contact/contact.test --- drupal-7.41/modules/contact/contact.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/contact/contact.test 2019-04-17 22:20:46.000000000 +0200 @@ -346,6 +346,28 @@ $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); $this->assertResponse(200); + // Test that users can disable their contact form. + $this->drupalLogin($this->contact_user); + $edit = array('contact' => FALSE); + $this->drupalPost('user/' . $this->contact_user->uid . '/edit', $edit, 'Save'); + $this->drupalLogout(); + $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); + $this->assertResponse(403); + + // Test that user's contact status stays disabled when saving. + $contact_user_temp = user_load($this->contact_user->uid, TRUE); + user_save($contact_user_temp); + $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); + $this->assertResponse(403); + + // Test that users can enable their contact form. + $this->drupalLogin($this->contact_user); + $edit = array('contact' => TRUE); + $this->drupalPost('user/' . $this->contact_user->uid . '/edit', $edit, 'Save'); + $this->drupalLogout(); + $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); + $this->assertResponse(200); + // Revoke the personal contact permission for the anonymous user. user_role_revoke_permissions(DRUPAL_ANONYMOUS_RID, array('access user contact forms')); $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); diff -Naur drupal-7.41/modules/contextual/contextual.info drupal-7.66/modules/contextual/contextual.info --- drupal-7.41/modules/contextual/contextual.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/contextual/contextual.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x files[] = contextual.test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/dashboard/dashboard.info drupal-7.66/modules/dashboard/dashboard.info --- drupal-7.41/modules/dashboard/dashboard.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/dashboard/dashboard.info 2019-04-17 22:39:36.000000000 +0200 @@ -7,8 +7,7 @@ dependencies[] = block configure = admin/dashboard/customize -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/dblog/dblog.admin.inc drupal-7.66/modules/dblog/dblog.admin.inc --- drupal-7.41/modules/dblog/dblog.admin.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/dblog/dblog.admin.inc 2019-04-17 22:20:46.000000000 +0200 @@ -294,11 +294,18 @@ else { $output = t($event->message, unserialize($event->variables)); } + // If the output is expected to be a link, strip all the tags and + // special characters by using filter_xss() without any allowed tags. + // If not, use filter_xss_admin() to allow some tags. if ($variables['link'] && isset($event->wid)) { - // Truncate message to 56 chars. + // Truncate message to 56 chars after stripping all the tags. $output = truncate_utf8(filter_xss($output, array()), 56, TRUE, TRUE); $output = l($output, 'admin/reports/event/' . $event->wid, array('html' => TRUE)); } + else { + // Prevent XSS in log detail pages. + $output = filter_xss_admin($output); + } } return $output; } @@ -413,6 +420,6 @@ */ function dblog_clear_log_submit() { $_SESSION['dblog_overview_filter'] = array(); - db_delete('watchdog')->execute(); + db_truncate('watchdog')->execute(); drupal_set_message(t('Database log cleared.')); } diff -Naur drupal-7.41/modules/dblog/dblog.info drupal-7.66/modules/dblog/dblog.info --- drupal-7.41/modules/dblog/dblog.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/dblog/dblog.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x files[] = dblog.test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/dblog/dblog.install drupal-7.66/modules/dblog/dblog.install --- drupal-7.41/modules/dblog/dblog.install 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/dblog/dblog.install 2019-04-17 22:20:46.000000000 +0200 @@ -155,5 +155,14 @@ } /** + * Account for possible legacy systems where dblog was not installed. + */ +function dblog_update_7003() { + if (!db_table_exists('watchdog')) { + db_create_table('watchdog', drupal_get_schema_unprocessed('dblog', 'watchdog')); + } +} + +/** * @} End of "addtogroup updates-7.x-extra". */ diff -Naur drupal-7.41/modules/dblog/dblog.module drupal-7.66/modules/dblog/dblog.module --- drupal-7.41/modules/dblog/dblog.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/dblog/dblog.module 2019-04-17 22:20:46.000000000 +0200 @@ -144,20 +144,30 @@ * Note: Some values may be truncated to meet database column size restrictions. */ function dblog_watchdog(array $log_entry) { - Database::getConnection('default', 'default')->insert('watchdog') - ->fields(array( - 'uid' => $log_entry['uid'], - 'type' => substr($log_entry['type'], 0, 64), - 'message' => $log_entry['message'], - 'variables' => serialize($log_entry['variables']), - 'severity' => $log_entry['severity'], - 'link' => substr($log_entry['link'], 0, 255), - 'location' => $log_entry['request_uri'], - 'referer' => $log_entry['referer'], - 'hostname' => substr($log_entry['ip'], 0, 128), - 'timestamp' => $log_entry['timestamp'], - )) - ->execute(); + if (!function_exists('drupal_substr')) { + require_once DRUPAL_ROOT . '/includes/unicode.inc'; + } + try { + Database::getConnection('default', 'default')->insert('watchdog') + ->fields(array( + 'uid' => $log_entry['uid'], + 'type' => drupal_substr($log_entry['type'], 0, 64), + 'message' => $log_entry['message'], + 'variables' => serialize($log_entry['variables']), + 'severity' => $log_entry['severity'], + 'link' => drupal_substr($log_entry['link'], 0, 255), + 'location' => $log_entry['request_uri'], + 'referer' => $log_entry['referer'], + 'hostname' => drupal_substr($log_entry['ip'], 0, 128), + 'timestamp' => $log_entry['timestamp'], + )) + ->execute(); + } + catch (Exception $e) { + // Exception is ignored so that watchdog does not break pages during the + // installation process or is not able to create the watchdog table during + // installation. + } } /** diff -Naur drupal-7.41/modules/dblog/dblog.test drupal-7.66/modules/dblog/dblog.test --- drupal-7.41/modules/dblog/dblog.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/dblog/dblog.test 2019-04-17 22:20:46.000000000 +0200 @@ -119,13 +119,18 @@ private function generateLogEntries($count, $type = 'custom', $severity = WATCHDOG_NOTICE) { global $base_root; + // This long URL makes it just a little bit harder to pass the link part of + // the test with a mix of English words and a repeating series of random + // percent-encoded Chinese characters. + $link = urldecode('/content/xo%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A-lake-isabelle'); + // Prepare the fields to be logged $log = array( 'type' => $type, 'message' => 'Log entry added to test the dblog row limit.', 'variables' => array(), 'severity' => $severity, - 'link' => NULL, + 'link' => $link, 'user' => $this->big_user, 'uid' => isset($this->big_user->uid) ? $this->big_user->uid : 0, 'request_uri' => $base_root . request_uri(), @@ -516,6 +521,33 @@ } /** + * Verifies that exceptions are caught in dblog_watchdog(). + */ + protected function testDBLogException() { + $log = array( + 'type' => 'custom', + 'message' => 'Log entry added to test watchdog handling of Exceptions.', + 'variables' => array(), + 'severity' => WATCHDOG_NOTICE, + 'link' => NULL, + 'user' => $this->big_user, + 'uid' => isset($this->big_user->uid) ? $this->big_user->uid : 0, + 'request_uri' => request_uri(), + 'referer' => $_SERVER['HTTP_REFERER'], + 'ip' => ip_address(), + 'timestamp' => REQUEST_TIME, + ); + + // Remove watchdog table temporarily to simulate it missing during + // installation. + db_query("DROP TABLE {watchdog}"); + + // Add a watchdog entry. + // This should not throw an Exception, but fail silently. + dblog_watchdog($log); + } + + /** * Gets the database log event information from the browser page. * * @return array @@ -633,5 +665,32 @@ // Document Object Model (DOM). $this->assertLink(html_entity_decode($message_text), 0, $message); } -} + /** + * Make sure HTML tags are filtered out in the log detail page. + */ + public function testLogMessageSanitized() { + $this->drupalLogin($this->big_user); + + // Make sure dangerous HTML tags are filtered out in log detail page. + $log = array( + 'uid' => 0, + 'type' => 'custom', + 'message' => " Lorem ipsum", + 'variables' => NULL, + 'severity' => WATCHDOG_NOTICE, + 'link' => 'foo/bar', + 'request_uri' => 'http://example.com?dblog=1', + 'referer' => 'http://example.org?dblog=2', + 'ip' => '0.0.1.0', + 'timestamp' => REQUEST_TIME, + ); + dblog_watchdog($log); + + $wid = db_query('SELECT MAX(wid) FROM {watchdog}')->fetchField(); + $this->drupalGet('admin/reports/event/' . $wid); + $this->assertResponse(200); + $this->assertNoRaw(""); + $this->assertRaw("alert('foo'); Lorem ipsum"); + } +} diff -Naur drupal-7.41/modules/field/field.crud.inc drupal-7.66/modules/field/field.crud.inc --- drupal-7.41/modules/field/field.crud.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/field/field.crud.inc 2019-04-17 22:20:46.000000000 +0200 @@ -189,7 +189,7 @@ } // Clear caches - field_cache_clear(TRUE); + field_cache_clear(); // Invoke external hooks after the cache is cleared for API consistency. module_invoke_all('field_create_field', $field); @@ -288,7 +288,7 @@ drupal_write_record('field_config', $field, $primary_key); // Clear caches - field_cache_clear(TRUE); + field_cache_clear(); // Invoke external hooks after the cache is cleared for API consistency. module_invoke_all('field_update_field', $field, $prior_field, $has_data); @@ -430,7 +430,7 @@ ->execute(); // Clear the cache. - field_cache_clear(TRUE); + field_cache_clear(); module_invoke_all('field_delete_field', $field); } diff -Naur drupal-7.41/modules/field/field.info drupal-7.66/modules/field/field.info --- drupal-7.41/modules/field/field.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/field/field.info 2019-04-17 22:39:36.000000000 +0200 @@ -11,8 +11,7 @@ required = TRUE stylesheets[all][] = theme/field.css -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/field/field.install drupal-7.66/modules/field/field.install --- drupal-7.41/modules/field/field.install 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/field/field.install 2019-04-17 22:20:46.000000000 +0200 @@ -468,5 +468,26 @@ } /** + * Grant the new "administer fields" permission to trusted users. + */ +function field_update_7004() { + // Assign the permission to anyone that already has a trusted core permission + // that would have previously let them administer fields on an entity type. + $rids = array(); + $permissions = array( + 'administer site configuration', + 'administer content types', + 'administer users', + ); + foreach ($permissions as $permission) { + $rids = array_merge($rids, array_keys(user_roles(FALSE, $permission))); + } + $rids = array_unique($rids); + foreach ($rids as $rid) { + _update_7000_user_role_grant_permissions($rid, array('administer fields'), 'field'); + } +} + +/** * @} End of "addtogroup updates-7.x-extra". */ diff -Naur drupal-7.41/modules/field/field.module drupal-7.66/modules/field/field.module --- drupal-7.41/modules/field/field.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/field/field.module 2019-04-17 22:20:46.000000000 +0200 @@ -317,6 +317,21 @@ } /** + * Implements hook_permission(). + */ +function field_permission() { + return array( + 'administer fields' => array( + 'title' => t('Administer fields'), + 'description' => t('Additional permissions are required based on what the fields are attached to (for example, administer content types to manage fields attached to content).', array( + '@url' => '#module-node', + )), + 'restrict access' => TRUE, + ), + ); +} + +/** * Implements hook_theme(). */ function field_theme() { diff -Naur drupal-7.41/modules/field/modules/field_sql_storage/field_sql_storage.info drupal-7.66/modules/field/modules/field_sql_storage/field_sql_storage.info --- drupal-7.41/modules/field/modules/field_sql_storage/field_sql_storage.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/field/modules/field_sql_storage/field_sql_storage.info 2019-04-17 22:39:36.000000000 +0200 @@ -7,8 +7,7 @@ files[] = field_sql_storage.test required = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/field/modules/list/list.info drupal-7.66/modules/field/modules/list/list.info --- drupal-7.41/modules/field/modules/list/list.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/field/modules/list/list.info 2019-04-17 22:39:36.000000000 +0200 @@ -7,8 +7,7 @@ dependencies[] = options files[] = tests/list.test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/field/modules/list/list.install drupal-7.66/modules/field/modules/list/list.install --- drupal-7.41/modules/field/modules/list/list.install 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/field/modules/list/list.install 2019-04-17 22:20:46.000000000 +0200 @@ -61,7 +61,7 @@ // Additionally, float keys need to be disambiguated ('.5' is '0.5'). if ($field['type'] == 'list_number' && !empty($allowed_values)) { - $keys = array_map(create_function('$a', 'return (string) (float) $a;'), array_keys($allowed_values)); + $keys = array_map('_list_update_7001_float_string_cast', array_keys($allowed_values)); $allowed_values = array_combine($keys, array_values($allowed_values)); } @@ -89,6 +89,13 @@ } /** + * Helper callback function to cast the array element. + */ +function _list_update_7001_float_string_cast($element) { + return (string) (float) $element; +} + +/** * Helper function for list_update_7001: extract allowed values from a string. * * This reproduces the parsing logic in use before D7 RC2. diff -Naur drupal-7.41/modules/field/modules/list/tests/list.test drupal-7.66/modules/field/modules/list/tests/list.test --- drupal-7.41/modules/field/modules/list/tests/list.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/field/modules/list/tests/list.test 2019-04-17 22:20:46.000000000 +0200 @@ -212,7 +212,7 @@ parent::setUp('field_test', 'field_ui'); // Create test user. - $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy')); + $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy', 'administer fields')); $this->drupalLogin($admin_user); // Create content type, with underscores. diff -Naur drupal-7.41/modules/field/modules/list/tests/list_test.info drupal-7.66/modules/field/modules/list/tests/list_test.info --- drupal-7.41/modules/field/modules/list/tests/list_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/field/modules/list/tests/list_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/field/modules/number/number.info drupal-7.66/modules/field/modules/number/number.info --- drupal-7.41/modules/field/modules/number/number.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/field/modules/number/number.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ dependencies[] = field files[] = number.test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/field/modules/number/number.module drupal-7.66/modules/field/modules/number/number.module --- drupal-7.41/modules/field/modules/number/number.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/field/modules/number/number.module 2019-04-17 22:20:46.000000000 +0200 @@ -164,6 +164,15 @@ } } } + if ($field['type'] == 'number_float') { + // Remove the decimal point from float values with decimal + // point but no decimal numbers. + foreach ($items as $delta => $item) { + if (isset($item['value'])) { + $items[$delta]['value'] = floatval($item['value']); + } + } + } } /** @@ -222,6 +231,8 @@ $display = $instance['display'][$view_mode]; $settings = $display['settings']; + $element = array(); + if ($display['type'] == 'number_decimal' || $display['type'] == 'number_integer') { $options = array( '' => t(''), diff -Naur drupal-7.41/modules/field/modules/number/number.test drupal-7.66/modules/field/modules/number/number.test --- drupal-7.41/modules/field/modules/number/number.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/field/modules/number/number.test 2019-04-17 22:20:46.000000000 +0200 @@ -23,7 +23,7 @@ function setUp() { parent::setUp('field_test'); - $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content', 'administer content types')); + $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content', 'administer content types', 'administer fields')); $this->drupalLogin($this->web_user); } @@ -69,7 +69,7 @@ preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match); $id = $match[1]; $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created'); - $this->assertRaw(round($value, 2), 'Value is displayed.'); + $this->assertRaw($value, 'Value is displayed.'); // Try to create entries with more than one decimal separator; assert fail. $wrong_entries = array( @@ -152,4 +152,50 @@ ); $this->drupalPost(NULL, $edit, t('Save')); } + + /** + * Test number_float field. + */ + function testNumberFloatField() { + $this->field = array( + 'field_name' => drupal_strtolower($this->randomName()), + 'type' => 'number_float', + 'settings' => array( + 'precision' => 8, 'scale' => 4, 'decimal_separator' => '.', + ) + ); + field_create_field($this->field); + $this->instance = array( + 'field_name' => $this->field['field_name'], + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + 'widget' => array( + 'type' => 'number', + ), + 'display' => array( + 'default' => array( + 'type' => 'number_float', + ), + ), + ); + field_create_instance($this->instance); + + $langcode = LANGUAGE_NONE; + $value = array( + '9.' => '9', + '.' => '0', + '123.55' => '123.55', + '.55' => '0.55', + '-0.55' => '-0.55', + ); + foreach($value as $key => $value) { + $edit = array( + "{$this->field['field_name']}[$langcode][0][value]" => $key, + ); + $this->drupalPost('test-entity/add/test-bundle', $edit, t('Save')); + $this->assertNoText("PDOException"); + $this->assertRaw($value, 'Correct value is displayed.'); + } + } + } diff -Naur drupal-7.41/modules/field/modules/options/options.info drupal-7.66/modules/field/modules/options/options.info --- drupal-7.41/modules/field/modules/options/options.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/field/modules/options/options.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ dependencies[] = field files[] = options.test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/field/modules/options/options.module drupal-7.66/modules/field/modules/options/options.module --- drupal-7.41/modules/field/modules/options/options.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/field/modules/options/options.module 2019-04-17 22:20:46.000000000 +0200 @@ -185,6 +185,7 @@ $base = array( 'filter_xss' => FALSE, 'strip_tags' => FALSE, + 'strip_tags_and_unescape' => FALSE, 'empty_option' => FALSE, 'optgroups' => FALSE, ); @@ -195,7 +196,7 @@ case 'select': $properties = array( // Select boxes do not support any HTML tag. - 'strip_tags' => TRUE, + 'strip_tags_and_unescape' => TRUE, 'optgroups' => TRUE, ); if ($multiple) { @@ -271,9 +272,16 @@ _options_prepare_options($options[$value], $properties); } else { + // The 'strip_tags' option is deprecated. Use 'strip_tags_and_unescape' + // when plain text is required (and where the output will be run through + // check_plain() before being inserted back into HTML) or 'filter_xss' + // when HTML is required. if ($properties['strip_tags']) { $options[$value] = strip_tags($label); } + if ($properties['strip_tags_and_unescape']) { + $options[$value] = decode_entities(strip_tags($label)); + } if ($properties['filter_xss']) { $options[$value] = field_filter_xss($label); } diff -Naur drupal-7.41/modules/field/modules/options/options.test drupal-7.66/modules/field/modules/options/options.test --- drupal-7.41/modules/field/modules/options/options.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/field/modules/options/options.test 2019-04-17 22:20:46.000000000 +0200 @@ -23,8 +23,15 @@ 'type' => 'list_integer', 'cardinality' => 1, 'settings' => array( - // Make sure that 0 works as an option. - 'allowed_values' => array(0 => 'Zero', 1 => 'One', 2 => 'Some & unescaped markup'), + 'allowed_values' => array( + // Make sure that 0 works as an option. + 0 => 'Zero', + 1 => 'One', + // Make sure that option text is properly sanitized. + 2 => 'Some & unescaped markup', + // Make sure that HTML entities in option text are not double-encoded. + 3 => 'Some HTML encoded markup with < & >', + ), ), ); $this->card_1 = field_create_field($this->card_1); @@ -35,8 +42,13 @@ 'type' => 'list_integer', 'cardinality' => 2, 'settings' => array( - // Make sure that 0 works as an option. - 'allowed_values' => array(0 => 'Zero', 1 => 'One', 2 => 'Some & unescaped markup'), + 'allowed_values' => array( + // Make sure that 0 works as an option. + 0 => 'Zero', + 1 => 'One', + // Make sure that option text is properly sanitized. + 2 => 'Some & unescaped markup', + ), ), ); $this->card_2 = field_create_field($this->card_2); @@ -47,14 +59,18 @@ 'type' => 'list_boolean', 'cardinality' => 1, 'settings' => array( - // Make sure that 0 works as a 'on' value'. - 'allowed_values' => array(1 => 'Zero', 0 => 'Some & unescaped markup'), + 'allowed_values' => array( + // Make sure that 1 works as a 'on' value'. + 1 => 'Zero', + // Make sure that option text is properly sanitized. + 0 => 'Some & unescaped markup', + ), ), ); $this->bool = field_create_field($this->bool); // Create a web user. - $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); + $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content', 'administer fields')); $this->drupalLogin($this->web_user); } @@ -233,6 +249,7 @@ $this->assertNoOptionSelected("edit-card-1-$langcode", 1); $this->assertNoOptionSelected("edit-card-1-$langcode", 2); $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.'); + $this->assertRaw('Some HTML encoded markup with < & >', 'HTML entities in option text were properly handled and not double-encoded'); // Submit form: select invalid 'none' option. $edit = array("card_1[$langcode]" => '_none'); @@ -459,7 +476,7 @@ $this->assertNoFieldChecked("edit-bool-$langcode"); // Create admin user. - $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy')); + $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy', 'administer fields')); $this->drupalLogin($admin_user); // Create a test field instance. diff -Naur drupal-7.41/modules/field/modules/text/text.info drupal-7.66/modules/field/modules/text/text.info --- drupal-7.41/modules/field/modules/text/text.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/field/modules/text/text.info 2019-04-17 22:39:36.000000000 +0200 @@ -7,8 +7,7 @@ files[] = text.test required = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/field/modules/text/text.module drupal-7.66/modules/field/modules/text/text.module --- drupal-7.41/modules/field/modules/text/text.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/field/modules/text/text.module 2019-04-17 22:20:46.000000000 +0200 @@ -223,11 +223,13 @@ if (strpos($display['type'], '_trimmed') !== FALSE) { $element['trim_length'] = array( - '#title' => t('Trim length'), + '#title' => t('Trimmed limit'), '#type' => 'textfield', + '#field_suffix' => t('characters'), '#size' => 10, '#default_value' => $settings['trim_length'], '#element_validate' => array('element_validate_integer_positive'), + '#description' => t('If the summary is not set, the trimmed %label field will be shorter than this character limit.', array('%label' => $instance['label'])), '#required' => TRUE, ); } @@ -245,7 +247,7 @@ $summary = ''; if (strpos($display['type'], '_trimmed') !== FALSE) { - $summary = t('Trim length') . ': ' . check_plain($settings['trim_length']); + $summary = t('Trimmed limit: @trim_length characters', array('@trim_length' => $settings['trim_length'])); } return $summary; diff -Naur drupal-7.41/modules/field/modules/text/text.test drupal-7.66/modules/field/modules/text/text.test --- drupal-7.41/modules/field/modules/text/text.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/field/modules/text/text.test 2019-04-17 22:20:46.000000000 +0200 @@ -424,6 +424,7 @@ 'administer content types', 'access administration pages', 'bypass node access', + 'administer fields', filter_permission_name($full_html_format), )); $this->translator = $this->drupalCreateUser(array('create article content', 'edit own article content', 'translate content')); diff -Naur drupal-7.41/modules/field/tests/field_test.info drupal-7.66/modules/field/tests/field_test.info --- drupal-7.41/modules/field/tests/field_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/field/tests/field_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/field/theme/field.tpl.php drupal-7.66/modules/field/theme/field.tpl.php --- drupal-7.41/modules/field/theme/field.tpl.php 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/field/theme/field.tpl.php 2019-04-17 22:20:46.000000000 +0200 @@ -4,8 +4,10 @@ * @file field.tpl.php * Default template implementation to display the value of a field. * - * This file is not used and is here as a starting point for customization only. - * @see theme_field() + * This file is not used by Drupal core, which uses theme functions instead for + * performance reasons. The markup is the same, though, so if you want to use + * template files rather than functions to extend field theming, copy this to + * your custom theme. See theme_field() for a discussion of performance. * * Available variables: * - $items: An array of field values. Use render() to output them. @@ -45,7 +47,7 @@ */ ?> ' placeholder. if ($mode) { $content = $match[1]; - $hash = md5($content); + $hash = hash('sha256', $content); $comments[$hash] = $content; return ""; } diff -Naur drupal-7.41/modules/filter/filter.test drupal-7.66/modules/filter/filter.test --- drupal-7.41/modules/filter/filter.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/filter/filter.test 2019-04-17 22:20:46.000000000 +0200 @@ -1120,8 +1120,12 @@ $f = filter_xss("", array('img')); $this->assertNoNormalized($f, 'cript', 'HTML scheme clearing evasion -- embedded nulls.'); - $f = filter_xss('', array('img')); - $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- spaces and metacharacters before scheme.'); + // @todo This dataset currently fails under 5.4 because of + // https://www.drupal.org/node/1210798. Restore after it's fixed. + if (version_compare(PHP_VERSION, '5.4.0', '<')) { + $f = filter_xss('', array('img')); + $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- spaces and metacharacters before scheme.'); + } $f = filter_xss('', array('img')); $this->assertNoNormalized($f, 'vbscript', 'HTML scheme clearing evasion -- another scheme.'); diff -Naur drupal-7.41/modules/forum/forum.info drupal-7.66/modules/forum/forum.info --- drupal-7.41/modules/forum/forum.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/forum/forum.info 2019-04-17 22:39:36.000000000 +0200 @@ -9,8 +9,7 @@ configure = admin/structure/forum stylesheets[all][] = forum.css -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/help/help.info drupal-7.66/modules/help/help.info --- drupal-7.41/modules/help/help.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/help/help.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x files[] = help.test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/image/image.admin.inc drupal-7.66/modules/image/image.admin.inc --- drupal-7.41/modules/image/image.admin.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/image/image.admin.inc 2019-04-17 22:20:46.000000000 +0200 @@ -736,7 +736,8 @@ if (!isset($form[$key]['#access']) || $form[$key]['#access']) { $rows[] = array( 'data' => $row, - 'class' => !empty($form[$key]['weight']['#access']) || $key == 'new' ? array('draggable') : array(), + // Use a strict (===) comparison since $key can be 0. + 'class' => !empty($form[$key]['weight']['#access']) || $key === 'new' ? array('draggable') : array(), ); } } diff -Naur drupal-7.41/modules/image/image.info drupal-7.66/modules/image/image.info --- drupal-7.41/modules/image/image.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/image/image.info 2019-04-17 22:39:36.000000000 +0200 @@ -7,8 +7,7 @@ files[] = image.test configure = admin/config/media/image-styles -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/image/image.module drupal-7.66/modules/image/image.module --- drupal-7.41/modules/image/image.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/image/image.module 2019-04-17 22:20:46.000000000 +0200 @@ -835,8 +835,8 @@ file_download($scheme, file_uri_target($derivative_uri)); } else { - $headers = module_invoke_all('file_download', $image_uri); - if (in_array(-1, $headers) || empty($headers)) { + $headers = file_download_headers($image_uri); + if (empty($headers)) { return MENU_ACCESS_DENIED; } if (count($headers)) { diff -Naur drupal-7.41/modules/image/image.test drupal-7.66/modules/image/image.test --- drupal-7.41/modules/image/image.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/image/image.test 2019-04-17 22:20:46.000000000 +0200 @@ -32,7 +32,7 @@ function setUp() { parent::setUp('image'); - $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer image styles')); + $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer image styles', 'administer fields')); $this->drupalLogin($this->admin_user); } @@ -202,6 +202,22 @@ } /** + * Test that we do not pass an array to drupal_add_http_header. + */ + function testImageContentTypeHeaders() { + $files = $this->drupalGetTestFiles('image'); + $file = array_shift($files); + // Copy the test file to private folder. + $private_file = file_copy($file, 'private://', FILE_EXISTS_RENAME); + // Tell image_module_test module to return the headers we want to test. + variable_set('image_module_test_invalid_headers', $private_file->uri); + // Invoke image_style_deliver so it will try to set headers. + $generated_url = image_style_url($this->style_name, $private_file->uri); + $this->drupalGet($generated_url); + variable_del('image_module_test_invalid_headers'); + } + + /** * Test image_style_url(). */ function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FALSE) { @@ -269,7 +285,7 @@ $this->assertEqual($this->drupalGetHeader('Content-Length'), $generated_image_info['file_size'], 'Expected Content-Length was reported.'); if ($scheme == 'private') { $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate, post-check=0, pre-check=0', 'Cache-Control header was set to prevent caching.'); + $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate', 'Cache-Control header was set to prevent caching.'); $this->assertEqual($this->drupalGetHeader('X-Image-Owned-By'), 'image_module_test', 'Expected custom header has been added.'); // Make sure that a second request to the already existing derivate works diff -Naur drupal-7.41/modules/image/tests/image_module_test.info drupal-7.66/modules/image/tests/image_module_test.info --- drupal-7.41/modules/image/tests/image_module_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/image/tests/image_module_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ files[] = image_module_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/image/tests/image_module_test.module drupal-7.66/modules/image/tests/image_module_test.module --- drupal-7.41/modules/image/tests/image_module_test.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/image/tests/image_module_test.module 2019-04-17 22:20:46.000000000 +0200 @@ -9,6 +9,9 @@ if (variable_get('image_module_test_file_download', FALSE) == $uri) { return array('X-Image-Owned-By' => 'image_module_test'); } + if (variable_get('image_module_test_invalid_headers', FALSE) == $uri) { + return array('Content-Type' => 'image/png'); + } } /** diff -Naur drupal-7.41/modules/locale/locale.admin.inc drupal-7.66/modules/locale/locale.admin.inc --- drupal-7.41/modules/locale/locale.admin.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/locale/locale.admin.inc 2019-04-17 22:20:46.000000000 +0200 @@ -1194,7 +1194,7 @@ $translation = db_query("SELECT translation FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $key))->fetchField(); if (!empty($value)) { // Only update or insert if we have a value to use. - if (!empty($translation)) { + if (is_string($translation)) { db_update('locales_target') ->fields(array( 'translation' => $value, diff -Naur drupal-7.41/modules/locale/locale.info drupal-7.66/modules/locale/locale.info --- drupal-7.41/modules/locale/locale.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/locale/locale.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ files[] = locale.test configure = admin/config/regional/language -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/locale/locale.test drupal-7.66/modules/locale/locale.test --- drupal-7.41/modules/locale/locale.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/locale/locale.test 2019-04-17 22:20:46.000000000 +0200 @@ -393,6 +393,16 @@ // The indicator should not be here. $this->assertNoRaw($language_indicator, 'String is translated.'); + // Verify that a translation set which has an empty target string can be + // updated without any database error. + db_update('locales_target') + ->fields(array('translation' => '')) + ->condition('language', $langcode, '=') + ->condition('lid', $lid, '=') + ->execute(); + $this->drupalPost('admin/config/regional/translate/edit/' . $lid, $edit, t('Save translations')); + $this->assertText(t('The string has been saved.'), 'The string has been saved.'); + // Try to edit a non-existent string and ensure we're redirected correctly. // Assuming we don't have 999,999 strings already. $random_lid = 999999; @@ -809,7 +819,7 @@ * Additional options to pass to the translation import form. */ function importPoFile($contents, array $options = array()) { - $name = tempnam('temporary://', "po_") . '.po'; + $name = drupal_tempnam('temporary://', "po_") . '.po'; file_put_contents($name, $contents); $options['files[file]'] = $name; $this->drupalPost('admin/config/regional/translate/import', $options, t('Import')); @@ -1103,7 +1113,7 @@ * Additional options to pass to the translation import form. */ function importPoFile($contents, array $options = array()) { - $name = tempnam('temporary://', "po_") . '.po'; + $name = drupal_tempnam('temporary://', "po_") . '.po'; file_put_contents($name, $contents); $options['files[file]'] = $name; $this->drupalPost('admin/config/regional/translate/import', $options, t('Import')); @@ -1330,7 +1340,7 @@ function testExportTranslation() { // First import some known translations. // This will also automatically enable the 'fr' language. - $name = tempnam('temporary://', "po_") . '.po'; + $name = drupal_tempnam('temporary://', "po_") . '.po'; file_put_contents($name, $this->getPoFile()); $this->drupalPost('admin/config/regional/translate/import', array( 'langcode' => 'fr', @@ -2237,6 +2247,37 @@ $this->drupalLogout(); } + + /** + * Verifies that nodes may be created with different languages. + */ + function testNodeCreationWithLanguage() { + // Create an admin user and log them in. + $perms = array( + // Standard node permissions. + 'create page content', + 'administer content types', + 'administer nodes', + 'bypass node access', + // Locale. + 'administer languages', + ); + $web_user = $this->drupalCreateUser($perms); + $this->drupalLogin($web_user); + + // Create some test nodes using different langcodes. + foreach (array(LANGUAGE_NONE, 'en', 'fr') as $langcode) { + $node_args = array( + 'type' => 'page', + 'promote' => 1, + 'language' => $langcode, + ); + $node = $this->drupalCreateNode($node_args); + $node_reloaded = node_load($node->nid, NULL, TRUE); + $this->assertEqual($node_reloaded->language, $langcode, format_string('The language code of the node was successfully set to @langcode.', array('@langcode' => $langcode))); + } + } + } /** @@ -2629,6 +2670,68 @@ $this->drupalGet("$prefix/$path"); $this->assertResponse(404, $message2); } + + /** + * Check URL rewriting when using a domain name and a non-standard port. + */ + function testDomainNameNegotiationPort() { + $language_domain = 'example.fr'; + $edit = array( + 'locale_language_negotiation_url_part' => 1, + ); + $this->drupalPost('admin/config/regional/language/configure/url', $edit, t('Save configuration')); + $edit = array( + 'prefix' => '', + 'domain' => $language_domain + ); + $this->drupalPost('admin/config/regional/language/edit/fr', $edit, t('Save language')); + + // Enable domain configuration. + variable_set('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN); + + // Reset static caching. + drupal_static_reset('language_list'); + drupal_static_reset('language_url_outbound_alter'); + drupal_static_reset('language_url_rewrite_url'); + + // In case index.php is part of the URLs, we need to adapt the asserted + // URLs as well. + $index_php = strpos(url('', array('absolute' => TRUE)), 'index.php') !== FALSE; + + // Remember current HTTP_HOST. + $http_host = $_SERVER['HTTP_HOST']; + + // Fake a different port. + $_SERVER['HTTP_HOST'] .= ':88'; + + // Create an absolute French link. + $languages = language_list(); + $language = $languages['fr']; + $url = url('', array( + 'absolute' => TRUE, + 'language' => $language + )); + + $expected = 'http://example.fr:88/'; + $expected .= $index_php ? 'index.php/' : ''; + + $this->assertEqual($url, $expected, 'The right port is used.'); + + // If we set the port explicitly in url(), it should not be overriden. + $url = url('', array( + 'absolute' => TRUE, + 'language' => $language, + 'base_url' => $GLOBALS['base_url'] . ':90', + )); + + $expected = 'http://example.fr:90/'; + $expected .= $index_php ? 'index.php/' : ''; + + $this->assertEqual($url, $expected, 'A given port is not overriden.'); + + // Restore HTTP_HOST. + $_SERVER['HTTP_HOST'] = $http_host; + } } /** @@ -3085,11 +3188,7 @@ foreach (language_types_info() as $type => $info) { if (isset($info['fixed'])) { $negotiation = variable_get("language_negotiation_$type", array()); - $equal = count($info['fixed']) == count($negotiation); - while ($equal && list($id) = each($negotiation)) { - list(, $info_id) = each($info['fixed']); - $equal = $info_id == $id; - } + $equal = array_keys($negotiation) === array_values($info['fixed']); $this->assertTrue($equal, format_string('language negotiation for %type is properly set up', array('%type' => $type))); } } @@ -3141,3 +3240,46 @@ $this->assertRaw('@import url("' . $base_url . '/modules/system/system.messages.css' . $query_string . '");' . "\n" . '@import url("' . $base_url . '/modules/system/system.messages-rtl.css' . $query_string . '");' . "\n", 'CSS: system.messages-rtl.css is added directly after system.messages.css.'); } } + +/** + * Tests locale translation safe string handling. + */ +class LocaleStringIsSafeTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Test if a string is safe', + 'description' => 'Tests locale translation safe string handling.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + } + + /** + * Tests for locale_string_is_safe(). + */ + public function testLocaleStringIsSafe() { + // Check a translatable string without HTML. + $string = 'Hello world!'; + $result = locale_string_is_safe($string); + $this->assertTrue($result); + + // Check a translatable string which includes trustable HTML. + $string = 'Hello world!'; + $result = locale_string_is_safe($string); + $this->assertTrue($result); + + // Check an untranslatable string which includes untrustable HTML (according + // to the locale_string_is_safe() function definition). + $string = 'Hello world!'; + $result = locale_string_is_safe($string); + $this->assertFalse($result); + + // Check a translatable string which includes a token in an href attribute. + $string = 'Hi user'; + $result = locale_string_is_safe($string); + $this->assertTrue($result); + } +} diff -Naur drupal-7.41/modules/locale/tests/locale_test.info drupal-7.66/modules/locale/tests/locale_test.info --- drupal-7.41/modules/locale/tests/locale_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/locale/tests/locale_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/menu/menu.admin.inc drupal-7.66/modules/menu/menu.admin.inc --- drupal-7.41/modules/menu/menu.admin.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/menu/menu.admin.inc 2019-04-17 22:20:46.000000000 +0200 @@ -281,6 +281,7 @@ $form['link_title'] = array( '#type' => 'textfield', '#title' => t('Menu link title'), + '#maxlength' => 255, '#default_value' => $item['link_title'], '#description' => t('The text to be used for this link in the menu.'), '#required' => TRUE, @@ -305,7 +306,7 @@ '#title' => t('Path'), '#maxlength' => 255, '#default_value' => $path, - '#description' => t('The path for this menu link. This can be an internal Drupal path such as %add-node or an external URL such as %drupal. Enter %front to link to the front page.', array('%front' => '', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org')), + '#description' => t('The path for this menu link. This can be an internal path such as %add-node or an external URL such as %example. Enter %front to link to the front page.', array('%front' => '', '%add-node' => 'node/add', '%example' => 'http://example.com')), '#required' => TRUE, ); $form['actions']['delete'] = array( diff -Naur drupal-7.41/modules/menu/menu.info drupal-7.66/modules/menu/menu.info --- drupal-7.41/modules/menu/menu.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/menu/menu.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ files[] = menu.test configure = admin/structure/menu -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/menu/menu.module drupal-7.66/modules/menu/menu.module --- drupal-7.41/modules/menu/menu.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/menu/menu.module 2019-04-17 22:20:46.000000000 +0200 @@ -674,6 +674,7 @@ $form['menu']['link']['link_title'] = array( '#type' => 'textfield', '#title' => t('Menu link title'), + '#maxlength' => 255, '#default_value' => $link['link_title'], ); diff -Naur drupal-7.41/modules/menu/menu.test drupal-7.66/modules/menu/menu.test --- drupal-7.41/modules/menu/menu.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/menu/menu.test 2019-04-17 22:20:46.000000000 +0200 @@ -648,7 +648,12 @@ ); $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); - // Create a node. + // Verify that the menu link title on the node add form has the correct + // maxlength. + $this->drupalGet('node/add/page'); + $this->assertPattern('//', 'Menu link title field has correct maxlength in node add form.'); + + // Create a node with menu link disabled. $node_title = $this->randomName(); $language = LANGUAGE_NONE; $edit = array( @@ -684,6 +689,10 @@ $this->drupalGet('node/' . $node->nid . '/edit'); $this->assertOptionSelected('edit-menu-weight', 17, 'Menu weight correct in edit form'); + // Verify that the menu link title on the node edit form has the correct + // maxlength. + $this->assertPattern('//', 'Menu link title field has correct maxlength in node edit form.'); + // Edit the node and remove the menu link. $edit = array( 'menu[enabled]' => FALSE, diff -Naur drupal-7.41/modules/node/content_types.inc drupal-7.66/modules/node/content_types.inc --- drupal-7.41/modules/node/content_types.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/node/content_types.inc 2019-04-17 22:20:46.000000000 +0200 @@ -11,7 +11,7 @@ function node_overview_types() { $types = node_type_get_types(); $names = node_type_get_names(); - $field_ui = module_exists('field_ui'); + $field_ui = module_exists('field_ui') && user_access('administer fields'); $header = array(t('Name'), array('data' => t('Operations'), 'colspan' => $field_ui ? '4' : '2')); $rows = array(); diff -Naur drupal-7.41/modules/node/node.admin.inc drupal-7.66/modules/node/node.admin.inc --- drupal-7.41/modules/node/node.admin.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/node/node.admin.inc 2019-04-17 22:20:46.000000000 +0200 @@ -508,14 +508,17 @@ $options = array(); foreach ($nodes as $node) { $langcode = entity_language('node', $node); - $l_options = $langcode != LANGUAGE_NONE && isset($languages[$langcode]) ? array('language' => $languages[$langcode]) : array(); + $uri = entity_uri('node', $node); + if ($langcode != LANGUAGE_NONE && isset($languages[$langcode])) { + $uri['options']['language'] = $languages[$langcode]; + } $options[$node->nid] = array( 'title' => array( 'data' => array( '#type' => 'link', '#title' => $node->title, - '#href' => 'node/' . $node->nid, - '#options' => $l_options, + '#href' => $uri['path'], + '#options' => $uri['options'], '#suffix' => ' ' . theme('mark', array('type' => node_mark($node->nid, $node->changed))), ), ), diff -Naur drupal-7.41/modules/node/node.info drupal-7.66/modules/node/node.info --- drupal-7.41/modules/node/node.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/node/node.info 2019-04-17 22:39:36.000000000 +0200 @@ -9,8 +9,7 @@ configure = admin/structure/types stylesheets[all][] = node.css -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/node/node.install drupal-7.66/modules/node/node.install --- drupal-7.41/modules/node/node.install 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/node/node.install 2019-04-17 22:20:46.000000000 +0200 @@ -410,6 +410,7 @@ 'nid' => array( 'description' => 'The {node}.nid that was read.', 'type' => 'int', + 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, ), @@ -944,5 +945,22 @@ } /** + * Change {history}.nid to an unsigned int in order to match {node}.nid. + */ +function node_update_7016() { + db_drop_primary_key('history'); + db_drop_index('history', 'nid'); + db_change_field('history', 'nid', 'nid', array( + 'description' => 'The {node}.nid that was read.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + )); + db_add_primary_key('history', array('uid', 'nid')); + db_add_index('history', 'nid', array('nid')); +} + +/** * @} End of "addtogroup updates-7.x-extra". */ diff -Naur drupal-7.41/modules/node/node.module drupal-7.66/modules/node/node.module --- drupal-7.41/modules/node/node.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/node/node.module 2019-04-17 22:20:46.000000000 +0200 @@ -2953,7 +2953,10 @@ * system. When adding a node listing to your module, be sure to use a dynamic * query created by db_select() and add a tag of "node_access". This will allow * modules dealing with node access to ensure only nodes to which the user has - * access are retrieved, through the use of hook_query_TAG_alter(). + * access are retrieved, through the use of hook_query_TAG_alter(). Tagging a + * query with "node_access" does not check the published/unpublished status of + * nodes, so the base query is responsible for ensuring that unpublished nodes + * are not displayed to inappropriate users. * * Note: Even a single module returning NODE_ACCESS_DENY from hook_node_access() * will block access to the node. Therefore, implementers should take care to @@ -3685,7 +3688,7 @@ // Initiate multistep processing. $context['sandbox']['progress'] = 0; $context['sandbox']['current_node'] = 0; - $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField(); + $context['sandbox']['max'] = db_query('SELECT COUNT(nid) FROM {node}')->fetchField(); } // Process the next 20 nodes. diff -Naur drupal-7.41/modules/node/node.pages.inc drupal-7.66/modules/node/node.pages.inc --- drupal-7.41/modules/node/node.pages.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/node/node.pages.inc 2019-04-17 22:20:46.000000000 +0200 @@ -396,7 +396,6 @@ $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()) { diff -Naur drupal-7.41/modules/node/node.test drupal-7.66/modules/node/node.test --- drupal-7.41/modules/node/node.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/node/node.test 2019-04-17 22:20:46.000000000 +0200 @@ -457,10 +457,70 @@ } function setUp() { - parent::setUp(); + parent::setUp(array('taxonomy', 'node')); $web_user = $this->drupalCreateUser(array('edit own page content', 'create page content')); $this->drupalLogin($web_user); + + // Add a vocabulary so we can test different view modes. + $vocabulary = (object) array( + 'name' => $this->randomName(), + 'description' => $this->randomName(), + 'machine_name' => drupal_strtolower($this->randomName()), + 'help' => '', + 'nodes' => array('page' => 'page'), + ); + taxonomy_vocabulary_save($vocabulary); + + $this->vocabulary = $vocabulary; + + // Add a term to the vocabulary. + $term = (object) array( + 'name' => $this->randomName(), + 'description' => $this->randomName(), + // Use the first available text format. + 'format' => db_query_range('SELECT format FROM {filter_format}', 0, 1)->fetchField(), + 'vid' => $this->vocabulary->vid, + 'vocabulary_machine_name' => $vocabulary->machine_name, + ); + taxonomy_term_save($term); + + $this->term = $term; + + // Set up a field and instance. + $this->field_name = drupal_strtolower($this->randomName()); + $this->field = array( + 'field_name' => $this->field_name, + 'type' => 'taxonomy_term_reference', + 'settings' => array( + 'allowed_values' => array( + array( + 'vocabulary' => $this->vocabulary->machine_name, + 'parent' => '0', + ), + ), + ) + ); + + field_create_field($this->field); + $this->instance = array( + 'field_name' => $this->field_name, + 'entity_type' => 'node', + 'bundle' => 'page', + 'widget' => array( + 'type' => 'options_select', + ), + // Hide on full display but render on teaser. + 'display' => array( + 'default' => array( + 'type' => 'hidden', + ), + 'teaser' => array( + 'type' => 'taxonomy_term_reference_link', + ), + ), + ); + field_create_instance($this->instance); } /** @@ -470,21 +530,26 @@ $langcode = LANGUAGE_NONE; $title_key = "title"; $body_key = "body[$langcode][0][value]"; + $term_key = "{$this->field_name}[$langcode]"; // Fill in node creation form and preview node. $edit = array(); $edit[$title_key] = $this->randomName(8); $edit[$body_key] = $this->randomName(16); + $edit[$term_key] = $this->term->tid; $this->drupalPost('node/add/page', $edit, t('Preview')); - // Check that the preview is displaying the title and body. + // Check that the preview is displaying the title, body, and term. $this->assertTitle(t('Preview | Drupal'), 'Basic page title is preview.'); $this->assertText($edit[$title_key], 'Title displayed.'); $this->assertText($edit[$body_key], 'Body displayed.'); + $this->assertText($this->term->name, 'Term displayed.'); - // Check that the title and body fields are displayed with the correct values. + // Check that the title, body, and term fields are displayed with the + // correct values. $this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.'); $this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.'); + $this->assertFieldByName($term_key, $edit[$term_key], 'Term field displayed.'); } /** @@ -494,6 +559,7 @@ $langcode = LANGUAGE_NONE; $title_key = "title"; $body_key = "body[$langcode][0][value]"; + $term_key = "{$this->field_name}[$langcode]"; // Force revision on "Basic page" content. variable_set('node_options_page', array('status', 'revision')); @@ -501,17 +567,21 @@ $edit = array(); $edit[$title_key] = $this->randomName(8); $edit[$body_key] = $this->randomName(16); + $edit[$term_key] = $this->term->tid; $edit['log'] = $this->randomName(32); $this->drupalPost('node/add/page', $edit, t('Preview')); - // Check that the preview is displaying the title and body. + // Check that the preview is displaying the title, body, and term. $this->assertTitle(t('Preview | Drupal'), 'Basic page title is preview.'); $this->assertText($edit[$title_key], 'Title displayed.'); $this->assertText($edit[$body_key], 'Body displayed.'); + $this->assertText($this->term->name, 'Term displayed.'); - // Check that the title and body fields are displayed with the correct values. + // Check that the title, body, and term fields are displayed with the + // correct values. $this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.'); $this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.'); + $this->assertFieldByName($term_key, $edit[$term_key], 'Term field displayed.'); // Check that the log field has the correct value. $this->assertFieldByName('log', $edit['log'], 'Log field displayed.'); @@ -1448,7 +1518,7 @@ * Tests editing a node type using the UI. */ function testNodeTypeEditing() { - $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types')); + $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types', 'administer fields')); $this->drupalLogin($web_user); $instance = field_info_instance('node', 'body', 'page'); @@ -2698,8 +2768,8 @@ node_access_rebuild(); // Create some users. - $this->admin_user = $this->drupalCreateUser(array('access content', 'bypass node access')); - $this->content_admin_user = $this->drupalCreateUser(array('access content', 'administer content types')); + $this->admin_user = $this->drupalCreateUser(array('access content', 'bypass node access', 'administer fields')); + $this->content_admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer fields')); // Add a custom field to the page content type. $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); @@ -2916,3 +2986,36 @@ $this->assertResponse(404); } } + +/** + * Tests that multi-byte UTF-8 characters are stored and retrieved correctly. + */ +class NodeMultiByteUtf8Test extends NodeWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Multi-byte UTF-8', + 'description' => 'Test that multi-byte UTF-8 characters are stored and retrieved correctly.', + 'group' => 'Node', + ); + } + + /** + * Tests that multi-byte UTF-8 characters are stored and retrieved correctly. + */ + public function testMultiByteUtf8() { + $connection = Database::getConnection(); + // On MySQL, this test will only run if 'charset' is set to 'utf8mb4' in + // settings.php. + if (!($connection->utf8mb4IsSupported() && $connection->utf8mb4IsActive())) { + return; + } + $title = '🐙'; + $this->assertTrue(drupal_strlen($title, 'utf-8') < strlen($title), 'Title has multi-byte characters.'); + $node = $this->drupalCreateNode(array('title' => $title)); + $this->drupalGet('node/' . $node->nid); + $result = $this->xpath('//h1[@id="page-title"]'); + $this->assertEqual(trim((string) $result[0]), $title, 'The passed title was returned.'); + } + +} diff -Naur drupal-7.41/modules/node/tests/node_access_test.info drupal-7.66/modules/node/tests/node_access_test.info --- drupal-7.41/modules/node/tests/node_access_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/node/tests/node_access_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/node/tests/node_test.info drupal-7.66/modules/node/tests/node_test.info --- drupal-7.41/modules/node/tests/node_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/node/tests/node_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/node/tests/node_test_exception.info drupal-7.66/modules/node/tests/node_test_exception.info --- drupal-7.41/modules/node/tests/node_test_exception.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/node/tests/node_test_exception.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/openid/openid.info drupal-7.66/modules/openid/openid.info --- drupal-7.41/modules/openid/openid.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/openid/openid.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x files[] = openid.test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/openid/openid.test drupal-7.66/modules/openid/openid.test --- drupal-7.41/modules/openid/openid.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/openid/openid.test 2019-04-17 22:20:46.000000000 +0200 @@ -680,11 +680,11 @@ * Test _openid_dh_XXX_to_XXX() functions. */ function testConversion() { - $this->assertEqual(_openid_dh_long_to_base64('12345678901234567890123456789012345678901234567890'), 'CHJ/Y2mq+DyhUCZ0evjH8ZbOPwrS', '_openid_dh_long_to_base64() returned expected result.'); - $this->assertEqual(_openid_dh_base64_to_long('BsH/g8Nrpn2dtBSdu/sr1y8hxwyx'), '09876543210987654321098765432109876543210987654321', '_openid_dh_base64_to_long() returned expected result.'); + $this->assertIdentical(_openid_dh_long_to_base64('12345678901234567890123456789012345678901234567890'), 'CHJ/Y2mq+DyhUCZ0evjH8ZbOPwrS', '_openid_dh_long_to_base64() returned expected result.'); + $this->assertIdentical(_openid_dh_base64_to_long('BsH/g8Nrpn2dtBSdu/sr1y8hxwyx'), '9876543210987654321098765432109876543210987654321', '_openid_dh_base64_to_long() returned expected result.'); - $this->assertEqual(_openid_dh_long_to_binary('12345678901234567890123456789012345678901234567890'), "\x08r\x7fci\xaa\xf8<\xa1P&tz\xf8\xc7\xf1\x96\xce?\x0a\xd2", '_openid_dh_long_to_binary() returned expected result.'); - $this->assertEqual(_openid_dh_binary_to_long("\x06\xc1\xff\x83\xc3k\xa6}\x9d\xb4\x14\x9d\xbb\xfb+\xd7/!\xc7\x0c\xb1"), '09876543210987654321098765432109876543210987654321', '_openid_dh_binary_to_long() returned expected result.'); + $this->assertIdentical(_openid_dh_long_to_binary('12345678901234567890123456789012345678901234567890'), "\x08r\x7fci\xaa\xf8<\xa1P&tz\xf8\xc7\xf1\x96\xce?\x0a\xd2", '_openid_dh_long_to_binary() returned expected result.'); + $this->assertIdentical(_openid_dh_binary_to_long("\x06\xc1\xff\x83\xc3k\xa6}\x9d\xb4\x14\x9d\xbb\xfb+\xd7/!\xc7\x0c\xb1"), '9876543210987654321098765432109876543210987654321', '_openid_dh_binary_to_long() returned expected result.'); } /** diff -Naur drupal-7.41/modules/openid/tests/openid_test.info drupal-7.66/modules/openid/tests/openid_test.info --- drupal-7.41/modules/openid/tests/openid_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/openid/tests/openid_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ dependencies[] = openid hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/overlay/overlay.info drupal-7.66/modules/overlay/overlay.info --- drupal-7.41/modules/overlay/overlay.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/overlay/overlay.info 2019-04-17 22:39:36.000000000 +0200 @@ -4,8 +4,7 @@ version = VERSION core = 7.x -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/overlay/overlay.module drupal-7.66/modules/overlay/overlay.module --- drupal-7.41/modules/overlay/overlay.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/overlay/overlay.module 2019-04-17 22:20:46.000000000 +0200 @@ -79,6 +79,20 @@ } /** + * Implements hook_form_alter(). + */ +function overlay_form_alter(&$form, &$form_state) { + // Add a hidden element to prevent dropping out of the overlay when a form is + // submitted inside the overlay using a GET method. + if (isset($form['#method']) && $form['#method'] == 'get' && isset($_REQUEST['render']) && $_REQUEST['render'] == 'overlay' && !isset($form['render'])) { + $form['render'] = array( + '#type' => 'hidden', + '#value' => 'overlay', + ); + } +} + +/** * Implements hook_form_FORM_ID_alter(). */ function overlay_form_user_profile_form_alter(&$form, &$form_state) { diff -Naur drupal-7.41/modules/path/path.info drupal-7.66/modules/path/path.info --- drupal-7.41/modules/path/path.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/path/path.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ files[] = path.test configure = admin/config/search/path -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/path/path.test drupal-7.66/modules/path/path.test --- drupal-7.41/modules/path/path.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/path/path.test 2019-04-17 22:20:46.000000000 +0200 @@ -21,7 +21,7 @@ parent::setUp('path'); // Create test user and login. - $web_user = $this->drupalCreateUser(array('create page content', 'edit own page content', 'administer url aliases', 'create url aliases')); + $web_user = $this->drupalCreateUser(array('create page content', 'edit own page content', 'administer url aliases', 'create url aliases', 'access content overview')); $this->drupalLogin($web_user); } @@ -160,6 +160,34 @@ $this->drupalGet($edit['path[alias]']); $this->assertNoText($node1->title, 'Alias was successfully deleted.'); $this->assertResponse(404); + + // Create third test node. + $node3 = $this->drupalCreateNode(); + + // Create an invalid alias with a leading slash and verify that the slash + // is removed when the link is generated. This ensures that URL aliases + // cannot be used to inject external URLs. + // @todo The user interface should either display an error message or + // automatically trim these invalid aliases, rather than allowing them to + // be silently created, at which point the functional aspects of this + // test will need to be moved elsewhere and switch to using a + // programmatically-created alias instead. + $alias = $this->randomName(8); + $edit = array('path[alias]' => '/' . $alias); + $this->drupalPost('node/' . $node3->nid . '/edit', $edit, t('Save')); + $this->drupalGet('admin/content'); + // This checks the link href before clicking it, rather than using + // DrupalWebTestCase::assertUrl() after clicking it, because the test + // browser does not always preserve the correct number of slashes in the + // URL when it visits internal links; using DrupalWebTestCase::assertUrl() + // would actually make the test pass unconditionally on the testbot (or + // anywhere else where Drupal is installed in a subdirectory). + $link_xpath = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $node3->title)); + $link_href = (string) $link_xpath[0]['href']; + $link_prefix = base_path() . (variable_get('clean_url', 0) ? '' : '?q='); + $this->assertEqual($link_href, $link_prefix . $alias); + $this->clickLink($node3->title); + $this->assertResponse(404); } /** diff -Naur drupal-7.41/modules/php/php.info drupal-7.66/modules/php/php.info --- drupal-7.41/modules/php/php.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/php/php.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x files[] = php.test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/poll/poll.info drupal-7.66/modules/poll/poll.info --- drupal-7.41/modules/poll/poll.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/poll/poll.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ files[] = poll.test stylesheets[all][] = poll.css -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/poll/poll.module drupal-7.66/modules/poll/poll.module --- drupal-7.41/modules/poll/poll.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/poll/poll.module 2019-04-17 22:20:46.000000000 +0200 @@ -631,9 +631,6 @@ * The node object to load. */ function poll_block_latest_poll_view($node) { - global $user; - $output = ''; - // This is necessary for shared objects because PHP doesn't copy objects, but // passes them by reference. So when the objects are cached it can result in // the wrong output being displayed on subsequent calls. The cloning and @@ -674,9 +671,6 @@ * Implements hook_view(). */ function poll_view($node, $view_mode) { - global $user; - $output = ''; - if (!empty($node->allowvotes) && empty($node->show_results)) { $node->content['poll_view_voting'] = drupal_get_form('poll_view_voting', $node); } @@ -694,7 +688,7 @@ function poll_teaser($node) { $teaser = NULL; if (is_array($node->choice)) { - foreach ($node->choice as $k => $choice) { + foreach ($node->choice as $choice) { if ($choice['chtext'] != '') { $teaser .= '* ' . check_plain($choice['chtext']) . "\n"; } diff -Naur drupal-7.41/modules/profile/profile.info drupal-7.66/modules/profile/profile.info --- drupal-7.41/modules/profile/profile.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/profile/profile.info 2019-04-17 22:39:36.000000000 +0200 @@ -11,8 +11,7 @@ ; See user_system_info_alter(). hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/profile/profile.test drupal-7.66/modules/profile/profile.test --- drupal-7.41/modules/profile/profile.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/profile/profile.test 2019-04-17 22:20:46.000000000 +0200 @@ -342,7 +342,7 @@ // Autocomplete always uses non-clean URLs. $current_clean_url = isset($GLOBALS['conf']['clean_url']) ? $GLOBALS['conf']['clean_url'] : NULL; $GLOBALS['conf']['clean_url'] = 0; - $autocomplete_url = url('profile/autocomplete/' . $field['fid'], array('absolute' => TRUE)); + $autocomplete_url = url('profile/autocomplete/' . $field['fid'], array('absolute' => TRUE, 'script' => 'index.php')); $GLOBALS['conf']['clean_url'] = $current_clean_url; $autocomplete_id = drupal_html_id('edit-' . $field['form_name'] . '-autocomplete'); $autocomplete_html = ''; diff -Naur drupal-7.41/modules/rdf/rdf.info drupal-7.66/modules/rdf/rdf.info --- drupal-7.41/modules/rdf/rdf.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/rdf/rdf.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x files[] = rdf.test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/rdf/tests/rdf_test.info drupal-7.66/modules/rdf/tests/rdf_test.info --- drupal-7.41/modules/rdf/tests/rdf_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/rdf/tests/rdf_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -4,9 +4,9 @@ version = VERSION core = 7.x hidden = TRUE +dependencies[] = blog -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/search/search.admin.inc drupal-7.66/modules/search/search.admin.inc --- drupal-7.41/modules/search/search.admin.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/search/search.admin.inc 2019-04-17 22:20:46.000000000 +0200 @@ -125,6 +125,16 @@ '#options' => $module_options, '#description' => t('Choose which search module is the default.') ); + $form['logging'] = array( + '#type' => 'fieldset', + '#title' => t('Logging') + ); + $form['logging']['search_logging'] = array( + '#type' => 'checkbox', + '#title' => t('Log searches'), + '#default_value' => variable_get('search_logging', 1), + '#description' => t('If checked, all searches will be logged. Uncheck to skip logging. Logging may affect performance.'), + ); $form['#validate'][] = 'search_admin_settings_validate'; $form['#submit'][] = 'search_admin_settings_submit'; diff -Naur drupal-7.41/modules/search/search.api.php drupal-7.66/modules/search/search.api.php --- drupal-7.41/modules/search/search.api.php 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/search/search.api.php 2019-04-17 22:20:46.000000000 +0200 @@ -30,8 +30,9 @@ * * @return * Array with optional keys: - * - title: Title for the tab on the search page for this module. Defaults - * to the module name if not given. + * - title: Title for the tab on the search page for this module. Title must + * be untranslated. Outside of this return array, pass the title through the + * t() function to register it as a translatable string. * - path: Path component after 'search/' for searching with this module. * Defaults to the module name if not given. * - conditions_callback: An implementation of callback_search_conditions(). @@ -39,6 +40,9 @@ * @ingroup search */ function hook_search_info() { + // Make the title translatable. + t('Content'); + return array( 'title' => 'Content', 'path' => 'node', diff -Naur drupal-7.41/modules/search/search.info drupal-7.66/modules/search/search.info --- drupal-7.41/modules/search/search.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/search/search.info 2019-04-17 22:39:36.000000000 +0200 @@ -8,8 +8,7 @@ configure = admin/config/search/settings stylesheets[all][] = search.css -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/search/search.install drupal-7.66/modules/search/search.install --- drupal-7.41/modules/search/search.install 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/search/search.install 2019-04-17 22:20:46.000000000 +0200 @@ -12,6 +12,7 @@ variable_del('minimum_word_size'); variable_del('overlap_cjk'); variable_del('search_cron_limit'); + variable_del('search_logging'); } /** diff -Naur drupal-7.41/modules/search/search.pages.inc drupal-7.66/modules/search/search.pages.inc --- drupal-7.41/modules/search/search.pages.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/search/search.pages.inc 2019-04-17 22:20:46.000000000 +0200 @@ -49,7 +49,7 @@ // which will get us back to this page callback. In other words, the search // form submits with POST but redirects to GET. This way we can keep // the search query URL clean as a whistle. - if (empty($_POST['form_id']) || $_POST['form_id'] != 'search_form') { + if (empty($_POST['form_id']) || ($_POST['form_id'] != 'search_form' && $_POST['form_id'] != 'search_block_form')) { $conditions = NULL; if (isset($info['conditions_callback']) && function_exists($info['conditions_callback'])) { // Build an optional array of more search conditions. @@ -57,9 +57,10 @@ } // Only search if there are keywords or non-empty conditions. if ($keys || !empty($conditions)) { - // Log the search keys. - watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $info['title']), WATCHDOG_NOTICE, l(t('results'), 'search/' . $info['path'] . '/' . $keys)); - + if (variable_get('search_logging', TRUE)) { + // Log the search keys. + watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $info['title']), WATCHDOG_NOTICE, l(t('results'), 'search/' . $info['path'] . '/' . $keys)); + } // Collect the search results. $results = search_data($keys, $info['module'], $conditions); } diff -Naur drupal-7.41/modules/search/search.test drupal-7.66/modules/search/search.test --- drupal-7.41/modules/search/search.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/search/search.test 2019-04-17 22:20:46.000000000 +0200 @@ -666,6 +666,24 @@ url('search/node/', array('absolute' => TRUE)), 'Redirected to correct url.' ); + + // Test that after entering a too-short keyword in the form, you can then + // search again with a longer keyword. First test using the block form. + $terms = array('search_block_form' => 'a'); + $this->drupalPost('node', $terms, t('Search')); + $this->assertText('You must include at least one positive keyword with 3 characters or more'); + $terms = array('search_block_form' => 'foo'); + $this->drupalPost(NULL, $terms, t('Search')); + $this->assertNoText('You must include at least one positive keyword with 3 characters or more'); + $this->assertText('Your search yielded no results'); + + // Same test again, using the search page form for the second search this time. + $terms = array('search_block_form' => 'a'); + $this->drupalPost('node', $terms, t('Search')); + $terms = array('keys' => 'foo'); + $this->drupalPost(NULL, $terms, t('Search')); + $this->assertNoText('You must include at least one positive keyword with 3 characters or more'); + $this->assertText('Your search yielded no results'); } } @@ -1435,7 +1453,7 @@ parent::setUp('search', 'search_extra_type'); // Login as a user that can create and search content. - $this->search_user = $this->drupalCreateUser(array('search content', 'administer search', 'administer nodes', 'bypass node access', 'access user profiles', 'administer users', 'administer blocks')); + $this->search_user = $this->drupalCreateUser(array('search content', 'administer search', 'administer nodes', 'bypass node access', 'access user profiles', 'administer users', 'administer blocks', 'access site reports')); $this->drupalLogin($this->search_user); // Add a single piece of content and index it. @@ -1484,6 +1502,19 @@ ); $this->drupalPost('admin/config/search/settings', $edit, t('Save configuration')); $this->assertNoText(t('The configuration options have been saved.'), 'Form does not save with an invalid word length.'); + + // Test logging setting. It should be on by default. + $text = $this->randomName(5); + $this->drupalPost('search/node', array('keys' => $text), t('Search')); + $this->drupalGet('admin/reports/dblog'); + $this->assertLink('Searched Content for ' . $text . '.', 0, 'Search was logged'); + + // Turn off logging. + variable_set('search_logging', FALSE); + $text = $this->randomName(5); + $this->drupalPost('search/node', array('keys' => $text), t('Search')); + $this->drupalGet('admin/reports/dblog'); + $this->assertNoLink('Searched Content for ' . $text . '.', 'Search was not logged'); } /** diff -Naur drupal-7.41/modules/search/tests/search_embedded_form.info drupal-7.66/modules/search/tests/search_embedded_form.info --- drupal-7.41/modules/search/tests/search_embedded_form.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/search/tests/search_embedded_form.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/search/tests/search_extra_type.info drupal-7.66/modules/search/tests/search_extra_type.info --- drupal-7.41/modules/search/tests/search_extra_type.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/search/tests/search_extra_type.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/search/tests/search_node_tags.info drupal-7.66/modules/search/tests/search_node_tags.info --- drupal-7.41/modules/search/tests/search_node_tags.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/search/tests/search_node_tags.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/shortcut/shortcut.info drupal-7.66/modules/shortcut/shortcut.info --- drupal-7.41/modules/shortcut/shortcut.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/shortcut/shortcut.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ files[] = shortcut.test configure = admin/config/user-interface/shortcut -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/drupal_web_test_case.php drupal-7.66/modules/simpletest/drupal_web_test_case.php --- drupal-7.41/modules/simpletest/drupal_web_test_case.php 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/drupal_web_test_case.php 2019-04-17 22:20:46.000000000 +0200 @@ -40,6 +40,13 @@ protected $originalFileDirectory = NULL; /** + * URL to the verbose output file directory. + * + * @var string + */ + protected $verboseDirectoryUrl; + + /** * Time limit for the test. */ protected $timeLimit = 500; @@ -461,8 +468,11 @@ protected function verbose($message) { if ($id = simpletest_verbose($message)) { $class_safe = str_replace('\\', '_', get_class($this)); - $url = file_create_url($this->originalFileDirectory . '/simpletest/verbose/' . $class_safe . '-' . $id . '.html'); - $this->error(l(t('Verbose message'), $url, array('attributes' => array('target' => '_blank'))), 'User notice'); + $url = $this->verboseDirectoryUrl . '/' . $class_safe . '-' . $id . '.html'; + // Not using l() to avoid invoking the theme system, so that unit tests + // can use verbose() as well. + $link = '' . t('Verbose message') . ''; + $this->error($link, 'User notice'); } } @@ -719,10 +729,17 @@ * method. */ protected function setUp() { - global $conf; + global $conf, $language; // Store necessary current values before switching to the test environment. $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); + $this->verboseDirectoryUrl = file_create_url($this->originalFileDirectory . '/simpletest/verbose'); + + // Set up English language. + $this->originalLanguage = $language; + $this->originalLanguageDefault = variable_get('language_default'); + unset($conf['language_default']); + $language = language_default(); // Reset all statics so that test is performed with a clean environment. drupal_static_reset(); @@ -764,7 +781,7 @@ } protected function tearDown() { - global $conf; + global $conf, $language; // Get back to the original connection. Database::removeConnection('default'); @@ -775,6 +792,12 @@ if (isset($this->originalModuleList)) { module_list(TRUE, FALSE, FALSE, $this->originalModuleList); } + + // Reset language. + $language = $this->originalLanguage; + if ($this->originalLanguageDefault) { + $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault; + } } } @@ -854,6 +877,13 @@ protected $cookieFile = NULL; /** + * The cookies of the page currently loaded in the internal browser. + * + * @var array + */ + protected $cookies = array(); + + /** * Additional cURL options. * * DrupalWebTestCase itself never sets this but always obeys what is set. @@ -942,7 +972,6 @@ protected function drupalCreateNode($settings = array()) { // Populate defaults array. $settings += array( - 'body' => array(LANGUAGE_NONE => array(array())), 'title' => $this->randomName(8), 'comment' => 2, 'changed' => REQUEST_TIME, @@ -957,6 +986,12 @@ 'language' => LANGUAGE_NONE, ); + // Add the body after the language is defined so that it may be set + // properly. + $settings += array( + 'body' => array($settings['language'] => array(array())), + ); + // Use the original node's created time for existing nodes. if (isset($settings['created']) && !isset($settings['date'])) { $settings['date'] = format_date($settings['created'], 'custom', 'Y-m-d H:i:s O'); @@ -1362,12 +1397,14 @@ * @see DrupalWebTestCase::tearDown() */ protected function prepareEnvironment() { - global $user, $language, $conf; + global $user, $language, $language_url, $conf; // Store necessary current values before switching to prefixed database. $this->originalLanguage = $language; + $this->originalLanguageUrl = $language_url; $this->originalLanguageDefault = variable_get('language_default'); $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); + $this->verboseDirectoryUrl = file_create_url($this->originalFileDirectory . '/simpletest/verbose'); $this->originalProfile = drupal_get_profile(); $this->originalCleanUrl = variable_get('clean_url', 0); $this->originalUser = $user; @@ -1375,7 +1412,7 @@ // Set to English to prevent exceptions from utf8_truncate() from t() // during install if the current language is not 'en'. // The following array/object conversion is copied from language_default(). - $language = (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => ''); + $language_url = $language = (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => ''); // Save and clean the shutdown callbacks array because it is static cached // and will be changed by the test run. Otherwise it will contain callbacks @@ -1433,7 +1470,7 @@ * @see DrupalWebTestCase::prepareEnvironment() */ protected function setUp() { - global $user, $language, $conf; + global $user, $language, $language_url, $conf; // Create the database prefix for this test. $this->prepareDatabasePrefix(); @@ -1530,7 +1567,7 @@ // Set up English language. unset($conf['language_default']); - $language = language_default(); + $language_url = $language = language_default(); // Use the test mail class instead of the default mail handler class. variable_set('mail_system', array('default-system' => 'TestingMailSystem')); @@ -1624,7 +1661,7 @@ * and reset the database prefix. */ protected function tearDown() { - global $user, $language; + global $user, $language, $language_url; // In case a fatal error occurred that was not in the test process read the // log to pick up any fatal errors. @@ -1689,12 +1726,15 @@ // Reset language. $language = $this->originalLanguage; + $language_url = $this->originalLanguageUrl; if ($this->originalLanguageDefault) { $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault; } - // Close the CURL handler. + // Close the CURL handler and reset the cookies array so test classes + // containing multiple tests are not polluted. $this->curlClose(); + $this->cookies = array(); } /** @@ -2584,6 +2624,11 @@ * * @param $xpath * The xpath string to use in the search. + * @param array $arguments + * An array of arguments with keys in the form ':name' matching the + * placeholders in the query. The values may be either strings or numeric + * values. + * * @return * The return value of the xpath search. For details on the xpath string * format and return values see the SimpleXML documentation, @@ -2755,7 +2800,7 @@ $path = substr($path, $length); } // Ensure that we have an absolute path. - if ($path[0] !== '/') { + if (empty($path) || $path[0] !== '/') { $path = '/' . $path; } // Finally, prepend the $base_url. @@ -2967,7 +3012,7 @@ if (!$message) { $message = t('Raw "@raw" found', array('@raw' => $raw)); } - return $this->assert(strpos($this->drupalGetContent(), $raw) !== FALSE, $message, $group); + return $this->assert(strpos($this->drupalGetContent(), (string) $raw) !== FALSE, $message, $group); } /** @@ -2987,7 +3032,7 @@ if (!$message) { $message = t('Raw "@raw" not found', array('@raw' => $raw)); } - return $this->assert(strpos($this->drupalGetContent(), $raw) === FALSE, $message, $group); + return $this->assert(strpos($this->drupalGetContent(), (string) $raw) === FALSE, $message, $group); } /** diff -Naur drupal-7.41/modules/simpletest/files/image-test-no-transparency.gif drupal-7.66/modules/simpletest/files/image-test-no-transparency.gif --- drupal-7.41/modules/simpletest/files/image-test-no-transparency.gif 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/modules/simpletest/files/image-test-no-transparency.gif 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1 @@ +GIF89a(,(8*L(C.HCV"ČC1b(S\YŌI%B0Oh N2w^icΠk|x̤<:$@*V5Q]fulح_{U&[iϲ-nBsݚ=7l^,+wHx 8bƅ?6 ; \ No newline at end of file diff -Naur drupal-7.41/modules/simpletest/files/phar-1.phar drupal-7.66/modules/simpletest/files/phar-1.phar --- drupal-7.41/modules/simpletest/files/phar-1.phar 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/modules/simpletest/files/phar-1.phar 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,301 @@ + 2, +'c' => 'text/plain', +'cc' => 'text/plain', +'cpp' => 'text/plain', +'c++' => 'text/plain', +'dtd' => 'text/plain', +'h' => 'text/plain', +'log' => 'text/plain', +'rng' => 'text/plain', +'txt' => 'text/plain', +'xsd' => 'text/plain', +'php' => 1, +'inc' => 1, +'avi' => 'video/avi', +'bmp' => 'image/bmp', +'css' => 'text/css', +'gif' => 'image/gif', +'htm' => 'text/html', +'html' => 'text/html', +'htmls' => 'text/html', +'ico' => 'image/x-ico', +'jpe' => 'image/jpeg', +'jpg' => 'image/jpeg', +'jpeg' => 'image/jpeg', +'js' => 'application/x-javascript', +'midi' => 'audio/midi', +'mid' => 'audio/midi', +'mod' => 'audio/mod', +'mov' => 'movie/quicktime', +'mp3' => 'audio/mp3', +'mpg' => 'video/mpeg', +'mpeg' => 'video/mpeg', +'pdf' => 'application/pdf', +'png' => 'image/png', +'swf' => 'application/shockwave-flash', +'tif' => 'image/tiff', +'tiff' => 'image/tiff', +'wav' => 'audio/wav', +'xbm' => 'image/xbm', +'xml' => 'text/xml', +); + +header("Cache-Control: no-cache, must-revalidate"); +header("Pragma: no-cache"); + +$basename = basename(__FILE__); +if (!strpos($_SERVER['REQUEST_URI'], $basename)) { +chdir(Extract_Phar::$temp); +include $web; +return; +} +$pt = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], $basename) + strlen($basename)); +if (!$pt || $pt == '/') { +$pt = $web; +header('HTTP/1.1 301 Moved Permanently'); +header('Location: ' . $_SERVER['REQUEST_URI'] . '/' . $pt); +exit; +} +$a = realpath(Extract_Phar::$temp . DIRECTORY_SEPARATOR . $pt); +if (!$a || strlen(dirname($a)) < strlen(Extract_Phar::$temp)) { +header('HTTP/1.0 404 Not Found'); +echo "\n \n File Not Found<title>\n </head>\n <body>\n <h1>404 - File Not Found</h1>\n </body>\n</html>"; +exit; +} +$b = pathinfo($a); +if (!isset($b['extension'])) { +header('Content-Type: text/plain'); +header('Content-Length: ' . filesize($a)); +readfile($a); +exit; +} +if (isset($mimes[$b['extension']])) { +if ($mimes[$b['extension']] === 1) { +include $a; +exit; +} +if ($mimes[$b['extension']] === 2) { +highlight_file($a); +exit; +} +header('Content-Type: ' .$mimes[$b['extension']]); +header('Content-Length: ' . filesize($a)); +readfile($a); +exit; +} +} + +class Extract_Phar +{ +static $temp; +static $origdir; +const GZ = 0x1000; +const BZ2 = 0x2000; +const MASK = 0x3000; +const START = 'index.php'; +const LEN = 6643; + +static function go($return = false) +{ +$fp = fopen(__FILE__, 'rb'); +fseek($fp, self::LEN); +$L = unpack('V', $a = fread($fp, 4)); +$m = ''; + +do { +$read = 8192; +if ($L[1] - strlen($m) < 8192) { +$read = $L[1] - strlen($m); +} +$last = fread($fp, $read); +$m .= $last; +} while (strlen($last) && strlen($m) < $L[1]); + +if (strlen($m) < $L[1]) { +die('ERROR: manifest length read was "' . +strlen($m) .'" should be "' . +$L[1] . '"'); +} + +$info = self::_unpack($m); +$f = $info['c']; + +if ($f & self::GZ) { +if (!function_exists('gzinflate')) { +die('Error: zlib extension is not enabled -' . +' gzinflate() function needed for zlib-compressed .phars'); +} +} + +if ($f & self::BZ2) { +if (!function_exists('bzdecompress')) { +die('Error: bzip2 extension is not enabled -' . +' bzdecompress() function needed for bz2-compressed .phars'); +} +} + +$temp = self::tmpdir(); + +if (!$temp || !is_writable($temp)) { +$sessionpath = session_save_path(); +if (strpos ($sessionpath, ";") !== false) +$sessionpath = substr ($sessionpath, strpos ($sessionpath, ";")+1); +if (!file_exists($sessionpath) || !is_dir($sessionpath)) { +die('Could not locate temporary directory to extract phar'); +} +$temp = $sessionpath; +} + +$temp .= '/pharextract/'.basename(__FILE__, '.phar'); +self::$temp = $temp; +self::$origdir = getcwd(); +@mkdir($temp, 0777, true); +$temp = realpath($temp); + +if (!file_exists($temp . DIRECTORY_SEPARATOR . md5_file(__FILE__))) { +self::_removeTmpFiles($temp, getcwd()); +@mkdir($temp, 0777, true); +@file_put_contents($temp . '/' . md5_file(__FILE__), ''); + +foreach ($info['m'] as $path => $file) { +$a = !file_exists(dirname($temp . '/' . $path)); +@mkdir(dirname($temp . '/' . $path), 0777, true); +clearstatcache(); + +if ($path[strlen($path) - 1] == '/') { +@mkdir($temp . '/' . $path, 0777); +} else { +file_put_contents($temp . '/' . $path, self::extractFile($path, $file, $fp)); +@chmod($temp . '/' . $path, 0666); +} +} +} + +chdir($temp); + +if (!$return) { +include self::START; +} +} + +static function tmpdir() +{ +if (strpos(PHP_OS, 'WIN') !== false) { +if ($var = getenv('TMP') ? getenv('TMP') : getenv('TEMP')) { +return $var; +} +if (is_dir('/temp') || mkdir('/temp')) { +return realpath('/temp'); +} +return false; +} +if ($var = getenv('TMPDIR')) { +return $var; +} +return realpath('/tmp'); +} + +static function _unpack($m) +{ +$info = unpack('V', substr($m, 0, 4)); + $l = unpack('V', substr($m, 10, 4)); +$m = substr($m, 14 + $l[1]); +$s = unpack('V', substr($m, 0, 4)); +$o = 0; +$start = 4 + $s[1]; +$ret['c'] = 0; + +for ($i = 0; $i < $info[1]; $i++) { + $len = unpack('V', substr($m, $start, 4)); +$start += 4; + $savepath = substr($m, $start, $len[1]); +$start += $len[1]; + $ret['m'][$savepath] = array_values(unpack('Va/Vb/Vc/Vd/Ve/Vf', substr($m, $start, 24))); +$ret['m'][$savepath][3] = sprintf('%u', $ret['m'][$savepath][3] +& 0xffffffff); +$ret['m'][$savepath][7] = $o; +$o += $ret['m'][$savepath][2]; +$start += 24 + $ret['m'][$savepath][5]; +$ret['c'] |= $ret['m'][$savepath][4] & self::MASK; +} +return $ret; +} + +static function extractFile($path, $entry, $fp) +{ +$data = ''; +$c = $entry[2]; + +while ($c) { +if ($c < 8192) { +$data .= @fread($fp, $c); +$c = 0; +} else { +$c -= 8192; +$data .= @fread($fp, 8192); +} +} + +if ($entry[4] & self::GZ) { +$data = gzinflate($data); +} elseif ($entry[4] & self::BZ2) { +$data = bzdecompress($data); +} + +if (strlen($data) != $entry[0]) { +die("Invalid internal .phar file (size error " . strlen($data) . " != " . +$stat[7] . ")"); +} + +if ($entry[3] != sprintf("%u", crc32($data) & 0xffffffff)) { +die("Invalid internal .phar file (checksum error)"); +} + +return $data; +} + +static function _removeTmpFiles($temp, $origdir) +{ +chdir($temp); + +foreach (glob('*') as $f) { +if (file_exists($f)) { +is_dir($f) ? @rmdir($f) : @unlink($f); +if (file_exists($f) && is_dir($f)) { +self::_removeTmpFiles($f, getcwd()); +} +} +} + +@rmdir($temp); +clearstatcache(); +chdir($origdir); +} +} + +Extract_Phar::go(); +__HALT_COMPILER(); ?>7������������������ ���index.php���8![���u‰������<?php +/** + * @file + * This test file is used to test Drupal's phar stream wrapper functionality. + * + * @see \Drupal\KernelTests\Core\File\PharWrapperTest + */ + +echo 'Hello, world!'; +1qV5['y RCA���GBMB \ No newline at end of file diff -Naur drupal-7.41/modules/simpletest/simpletest.info drupal-7.66/modules/simpletest/simpletest.info --- drupal-7.41/modules/simpletest/simpletest.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/simpletest.info 2019-04-17 22:39:36.000000000 +0200 @@ -57,8 +57,7 @@ files[] = tests/upgrade/update.field.test files[] = tests/upgrade/update.user.test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/simpletest.module drupal-7.66/modules/simpletest/simpletest.module --- drupal-7.41/modules/simpletest/simpletest.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/simpletest.module 2019-04-17 22:20:46.000000000 +0200 @@ -374,7 +374,10 @@ // If this test class requires a non-existing module, skip it. if (!empty($info['dependencies'])) { foreach ($info['dependencies'] as $module) { - if (!drupal_get_filename('module', $module)) { + // Pass FALSE as fourth argument so no error gets created for + // the missing file. + $found_module = drupal_get_filename('module', $module, NULL, FALSE); + if (!$found_module) { continue 2; } } diff -Naur drupal-7.41/modules/simpletest/simpletest.test drupal-7.66/modules/simpletest/simpletest.test --- drupal-7.41/modules/simpletest/simpletest.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/simpletest.test 2019-04-17 22:20:46.000000000 +0200 @@ -322,6 +322,14 @@ * Test internal testing framework browser. */ class SimpleTestBrowserTestCase extends DrupalWebTestCase { + + /** + * A flag indicating whether a cookie has been set in a test. + * + * @var bool + */ + protected static $cookieSet = FALSE; + public static function getInfo() { return array( 'name' => 'SimpleTest browser', @@ -380,6 +388,46 @@ $urls = $this->xpath('//a[text()=:text]', array(':text' => 'A second "even more weird" link, in memory of George O\'Malley')); $this->assertEqual($urls[0]['href'], 'link2', 'Match with mixed single and double quotes.'); } + + /** + * Tests that cookies set during a request are available for testing. + */ + public function testCookies() { + // Check that the $this->cookies property is populated when a user logs in. + $user = $this->drupalCreateUser(); + $edit = array('name' => $user->name, 'pass' => $user->pass_raw); + $this->drupalPost('<front>', $edit, t('Log in')); + $this->assertEqual(count($this->cookies), 1, 'A cookie is set when the user logs in.'); + + // Check that the name and value of the cookie match the request data. + $cookie_header = $this->drupalGetHeader('set-cookie', TRUE); + + // The name and value are located at the start of the string, separated by + // an equals sign and ending in a semicolon. + preg_match('/^([^=]+)=([^;]+)/', $cookie_header, $matches); + $name = $matches[1]; + $value = $matches[2]; + + $this->assertTrue(array_key_exists($name, $this->cookies), 'The cookie name is correct.'); + $this->assertEqual($value, $this->cookies[$name]['value'], 'The cookie value is correct.'); + + // Set a flag indicating that a cookie has been set in this test. + // @see SimpleTestBrowserTestCase::testCookieDoesNotBleed(). + self::$cookieSet = TRUE; + } + + /** + * Tests that the cookies from a previous test do not bleed into a new test. + * + * @see SimpleTestBrowserTestCase::testCookies(). + */ + public function testCookieDoesNotBleed() { + // In order for this test to be effective it should always run after the + // testCookies() test. + $this->assertTrue(self::$cookieSet, 'Tests have been executed in the expected order.'); + $this->assertEqual(count($this->cookies), 0, 'No cookies are present at the start of a new test.'); + } + } class SimpleTestMailCaptureTestCase extends DrupalWebTestCase { diff -Naur drupal-7.41/modules/simpletest/tests/actions_loop_test.info drupal-7.66/modules/simpletest/tests/actions_loop_test.info --- drupal-7.41/modules/simpletest/tests/actions_loop_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/actions_loop_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/ajax_forms_test.info drupal-7.66/modules/simpletest/tests/ajax_forms_test.info --- drupal-7.41/modules/simpletest/tests/ajax_forms_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/ajax_forms_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/ajax_test.info drupal-7.66/modules/simpletest/tests/ajax_test.info --- drupal-7.41/modules/simpletest/tests/ajax_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/ajax_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/batch_test.info drupal-7.66/modules/simpletest/tests/batch_test.info --- drupal-7.41/modules/simpletest/tests/batch_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/batch_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/boot_test_1.info drupal-7.66/modules/simpletest/tests/boot_test_1.info --- drupal-7.41/modules/simpletest/tests/boot_test_1.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/boot_test_1.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/boot_test_2.info drupal-7.66/modules/simpletest/tests/boot_test_2.info --- drupal-7.41/modules/simpletest/tests/boot_test_2.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/boot_test_2.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/bootstrap.test drupal-7.66/modules/simpletest/tests/bootstrap.test --- drupal-7.41/modules/simpletest/tests/bootstrap.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/bootstrap.test 2019-04-17 22:20:46.000000000 +0200 @@ -70,6 +70,15 @@ 'Proxy forwarding with trusted proxy got forwarded IP address.' ); + // Proxy forwarding on and proxy address trusted and visiting from proxy. + $_SERVER['REMOTE_ADDR'] = $this->proxy_ip; + $_SERVER['HTTP_X_FORWARDED_FOR'] = $this->proxy_ip; + drupal_static_reset('ip_address'); + $this->assertTrue( + ip_address() == $this->proxy_ip, + 'Visiting from trusted proxy got proxy IP address.' + ); + // Multi-tier architecture with comma separated values in header. $_SERVER['REMOTE_ADDR'] = $this->proxy_ip; $_SERVER['HTTP_X_FORWARDED_FOR'] = implode(', ', array($this->untrusted_ip, $this->forwarded_ip, $this->proxy2_ip)); @@ -152,7 +161,7 @@ $this->drupalLogin($user); $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('X-Drupal-Cache'), 'Absence 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.'); } @@ -191,7 +200,7 @@ $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar'))); $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.'); $this->assertTrue(strpos($this->drupalGetHeader('Vary'), 'Cookie') === FALSE, 'Vary: Cookie header was not sent.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate, post-check=0, pre-check=0', 'Cache-Control header was sent.'); + $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate', 'Cache-Control header was sent.'); $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.'); $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.'); @@ -379,13 +388,20 @@ public static function getInfo() { return array( - 'name' => 'Get filename test', - 'description' => 'Test that drupal_get_filename() works correctly when the file is not found in the database.', + 'name' => 'Get filename test (without the system table)', + 'description' => 'Test that drupal_get_filename() works correctly when the database is not available.', 'group' => 'Bootstrap', ); } /** + * The last file-related error message triggered by the filename test. + * + * Used by BootstrapGetFilenameTestCase::testDrupalGetFilename(). + */ + protected $getFilenameTestTriggeredError; + + /** * Test that drupal_get_filename() works correctly when the file is not found in the database. */ function testDrupalGetFilename() { @@ -414,6 +430,203 @@ // automatically check there for 'script' files, just as it does for (e.g.) // 'module' files in modules. $this->assertIdentical(drupal_get_filename('script', 'test'), 'scripts/test.script', t('Retrieve test script location.')); + + // When searching for a module that does not exist, drupal_get_filename() + // should return NULL and trigger an appropriate error message. + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $non_existing_module = $this->randomName(); + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for an item that does not exist triggers the correct error.'); + restore_error_handler(); + + // Check that the result is stored in the file system scan cache. + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files static variable.'); + + // Performing the search again in the same request still should not find + // the file, but the error message should not be repeated (therefore we do + // not override the error handler here). + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL during the second search.'); + } + + /** + * Skips handling of "file not found" errors. + */ + public function fileNotFoundErrorHandler($error_level, $message, $filename, $line, $context) { + // Skip error handling if this is a "file not found" error. + if (strpos($message, 'is missing from the file system:') !== FALSE || strpos($message, 'has moved within the file system:') !== FALSE) { + $this->getFilenameTestTriggeredError = $message; + return; + } + _drupal_error_handler($error_level, $message, $filename, $line, $context); + } +} + +/** + * Test drupal_get_filename() in the context of a full Drupal installation. + */ +class BootstrapGetFilenameWebTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Get filename test (full installation)', + 'description' => 'Test that drupal_get_filename() works correctly in the context of a full Drupal installation.', + 'group' => 'Bootstrap', + ); + } + + function setUp() { + parent::setUp('system_test'); + } + + /** + * The last file-related error message triggered by the filename test. + * + * Used by BootstrapGetFilenameWebTestCase::testDrupalGetFilename(). + */ + protected $getFilenameTestTriggeredError; + + /** + * Test that drupal_get_filename() works correctly with a full Drupal site. + */ + function testDrupalGetFilename() { + // Search for a module that exists in the file system and the {system} + // table and make sure that it is found. + $this->assertIdentical(drupal_get_filename('module', 'node'), 'modules/node/node.module', 'Module found at expected location.'); + + // Search for a module that does not exist in either the file system or the + // {system} table. Make sure that an appropriate error is triggered and + // that the module winds up in the static and persistent cache. + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $non_existing_module = $this->randomName(); + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for a module that does not exist triggers the correct error.'); + restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files static variable.'); + drupal_file_scan_write_cache(); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files persistent cache.'); + + // Simulate moving a module to a location that does not match the location + // in the {system} table and perform similar tests as above. + db_update('system') + ->fields(array('filename' => 'modules/simpletest/tests/fake_location/module_test.module')) + ->condition('name', 'module_test') + ->condition('type', 'module') + ->execute(); + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $this->assertIdentical(drupal_get_filename('module', 'module_test'), 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved finds the module at its new location.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module has moved within the file system: %name', array('%name' => 'module_test'))) === 0, 'Searching for a module that has moved triggers the correct error.'); + restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module']['module_test'], 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved creates a record in the missing and moved files static variable.'); + drupal_file_scan_write_cache(); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module']['module_test'], 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved creates a record in the missing and moved files persistent cache.'); + + // Simulate a module that exists in the {system} table but does not exist + // in the file system and perform similar tests as above. + $non_existing_module = $this->randomName(); + db_update('system') + ->fields(array('name' => $non_existing_module)) + ->condition('name', 'module_test') + ->condition('type', 'module') + ->execute(); + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that exists in the system table but not in the file system returns NULL.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for a module that exists in the system table but not in the file system triggers the correct error.'); + restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that exists in the system table but not in the file system creates a record in the missing and moved files static variable.'); + drupal_file_scan_write_cache(); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module'][$non_existing_module], FALSE, 'Searching for a module that exists in the system table but not in the file system creates a record in the missing and moved files persistent cache.'); + + // Simulate a module that exists in the file system but not in the {system} + // table and perform similar tests as above. + db_delete('system') + ->condition('name', 'common_test') + ->condition('type', 'module') + ->execute(); + system_list_reset(); + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $this->assertIdentical(drupal_get_filename('module', 'common_test'), 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table finds the module at its actual location.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module has moved within the file system: %name', array('%name' => 'common_test'))) === 0, 'Searching for a module that does not exist in the system table triggers the correct error.'); + restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module']['common_test'], 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table creates a record in the missing and moved files static variable.'); + drupal_file_scan_write_cache(); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module']['common_test'], 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table creates a record in the missing and moved files persistent cache.'); + } + + /** + * Skips handling of "file not found" errors. + */ + public function fileNotFoundErrorHandler($error_level, $message, $filename, $line, $context) { + // Skip error handling if this is a "file not found" error. + if (strpos($message, 'is missing from the file system:') !== FALSE || strpos($message, 'has moved within the file system:') !== FALSE) { + $this->getFilenameTestTriggeredError = $message; + return; + } + _drupal_error_handler($error_level, $message, $filename, $line, $context); + } + + /** + * Test that watchdog messages about missing files are correctly recorded. + */ + public function testWatchdog() { + // Search for a module that does not exist in either the file system or the + // {system} table. Make sure that an appropriate warning is recorded in the + // logs. + $non_existing_module = $this->randomName(); + $query_parameters = array( + ':type' => 'php', + ':severity' => WATCHDOG_WARNING, + ); + $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND severity = :severity', $query_parameters)->fetchField(), 0, 'No warning message appears in the logs before searching for a module that does not exist.'); + // Trigger the drupal_get_filename() call. This must be done via a request + // to a separate URL since the watchdog() will happen in a shutdown + // function, and so that SimpleTest can be told to ignore (and not fail as + // a result of) the expected PHP warnings generated during this process. + variable_set('system_test_drupal_get_filename_test_module_name', $non_existing_module); + $this->drupalGet('system-test/drupal-get-filename'); + $message_variables = db_query('SELECT variables FROM {watchdog} WHERE type = :type AND severity = :severity', $query_parameters)->fetchCol(); + $this->assertEqual(count($message_variables), 1, 'A single warning message appears in the logs after searching for a module that does not exist.'); + $variables = reset($message_variables); + $variables = unserialize($variables); + $this->assertTrue(isset($variables['!message']) && strpos($variables['!message'], format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) !== FALSE, 'The warning message that appears in the logs after searching for a module that does not exist contains the expected text.'); + } + + /** + * Test that drupal_get_filename() does not break recursive rebuilds. + */ + public function testRecursiveRebuilds() { + // Ensure that the drupal_get_filename() call due to a missing module does + // not break the data returned by an attempted recursive rebuild. The code + // path which is tested is as follows: + // - Call drupal_get_schema(). + // - Within a hook_schema() implementation, trigger a drupal_get_filename() + // search for a nonexistent module. + // - In the watchdog() call that results from that, trigger + // drupal_get_schema() again. + // Without some kind of recursion protection, this could cause the second + // drupal_get_schema() call to return incomplete results. This test ensures + // that does not happen. + $non_existing_module = $this->randomName(); + variable_set('system_test_drupal_get_filename_test_module_name', $non_existing_module); + $this->drupalGet('system-test/drupal-get-filename-with-schema-rebuild'); + $original_drupal_get_schema_tables = variable_get('system_test_drupal_get_filename_with_schema_rebuild_original_tables'); + $final_drupal_get_schema_tables = variable_get('system_test_drupal_get_filename_with_schema_rebuild_final_tables'); + $this->assertTrue(!empty($original_drupal_get_schema_tables)); + $this->assertTrue(!empty($final_drupal_get_schema_tables)); + $this->assertEqual($original_drupal_get_schema_tables, $final_drupal_get_schema_tables); } } @@ -516,16 +729,12 @@ * Tests that the drupal_check_memory_limit() function works as expected. */ function testCheckMemoryLimit() { - $memory_limit = ini_get('memory_limit'); // Test that a very reasonable amount of memory is available. $this->assertTrue(drupal_check_memory_limit('30MB'), '30MB of memory tested available.'); - // Get the available memory and multiply it by two to make it unreasonably - // high. - $twice_avail_memory = ($memory_limit * 2) . 'MB'; - + // Test an unlimited memory limit. // The function should always return true if the memory limit is set to -1. - $this->assertTrue(drupal_check_memory_limit($twice_avail_memory, -1), 'drupal_check_memory_limit() returns TRUE when a limit of -1 (none) is supplied'); + $this->assertTrue(drupal_check_memory_limit('9999999999YB', -1), 'drupal_check_memory_limit() returns TRUE when a limit of -1 (none) is supplied'); // Test that even though we have 30MB of memory available - the function // returns FALSE when given an upper limit for how much memory can be used. diff -Naur drupal-7.41/modules/simpletest/tests/common.test drupal-7.66/modules/simpletest/tests/common.test --- drupal-7.41/modules/simpletest/tests/common.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/common.test 2019-04-17 22:20:46.000000000 +0200 @@ -76,7 +76,7 @@ class CommonURLUnitTest extends DrupalWebTestCase { public static function getInfo() { return array( - 'name' => 'URL generation tests', + 'name' => 'URL generation unit tests', 'description' => 'Confirm that url(), drupal_get_query_parameters(), drupal_http_build_query(), and l() work correctly with various input.', 'group' => 'System', ); @@ -169,7 +169,7 @@ $this->assertEqual(drupal_http_build_query(array('a' => ' &#//+%20@۞')), 'a=%20%26%23//%2B%2520%40%DB%9E', 'Value was properly encoded.'); $this->assertEqual(drupal_http_build_query(array(' &#//+%20@۞' => 'a')), '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', 'Key was properly encoded.'); $this->assertEqual(drupal_http_build_query(array('a' => '1', 'b' => '2', 'c' => '3')), 'a=1&b=2&c=3', 'Multiple values were properly concatenated.'); - $this->assertEqual(drupal_http_build_query(array('a' => array('b' => '2', 'c' => '3'), 'd' => 'foo')), 'a[b]=2&a[c]=3&d=foo', 'Nested array was properly encoded.'); + $this->assertEqual(drupal_http_build_query(array('a' => array('b' => '2', 'c' => '3'), 'd' => 'foo')), 'a%5Bb%5D=2&a%5Bc%5D=3&d=foo', 'Nested array was properly encoded.'); } /** @@ -373,6 +373,97 @@ } /** + * Web tests for URL generation functions. + */ +class CommonURLWebTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'URL generation web tests', + 'description' => 'Confirm that URL-generating functions work correctly on specific site paths.', + 'group' => 'System', + ); + } + + function setUp() { + parent::setUp('common_test'); + } + + /** + * Tests the url() function on internal paths which mimic external URLs. + */ + function testInternalPathMimicsExternal() { + // Ensure that calling url(current_path()) on "/http://example.com" (an + // internal path which mimics an external URL) always links to the internal + // path, not the external URL. This helps protect against external URL link + // injection vulnerabilities. + variable_set('common_test_link_to_current_path', TRUE); + $this->drupalGet('/http://example.com'); + $this->clickLink('link which should point to the current path'); + $this->assertUrl('/http://example.com'); + $this->assertText('link which should point to the current path'); + } +} + +/** + * Tests url_is_external(). + */ +class UrlIsExternalUnitTest extends DrupalUnitTestCase { + + public static function getInfo() { + return array( + 'name' => 'External URL checking', + 'description' => 'Performs tests on url_is_external().', + 'group' => 'System', + ); + } + + /** + * Tests if each URL is external or not. + */ + function testUrlIsExternal() { + foreach ($this->examples() as $path => $expected) { + $this->assertIdentical(url_is_external($path), $expected, $path); + } + } + + /** + * Provides data for testUrlIsExternal(). + * + * @return array + * An array of test data, keyed by a path, with the expected value where + * TRUE is external, and FALSE is not external. + */ + protected function examples() { + return array( + // Simple external URLs. + 'http://example.com' => TRUE, + 'https://example.com' => TRUE, + 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo' => TRUE, + '//drupal.org' => TRUE, + // Some browsers ignore or strip leading control characters. + "\x00//www.example.com" => TRUE, + "\x08//www.example.com" => TRUE, + "\x1F//www.example.com" => TRUE, + "\n//www.example.com" => TRUE, + // JSON supports decoding directly from UTF-8 code points. + json_decode('"\u00AD"') . "//www.example.com" => TRUE, + json_decode('"\u200E"') . "//www.example.com" => TRUE, + json_decode('"\uE0020"') . "//www.example.com" => TRUE, + json_decode('"\uE000"') . "//www.example.com" => TRUE, + // Backslashes should be normalized to forward. + '\\\\example.com' => TRUE, + // Local URLs. + 'node' => FALSE, + '/system/ajax' => FALSE, + '?q=foo:bar' => FALSE, + 'node/edit:me' => FALSE, + '/drupal.org' => FALSE, + '<front>' => FALSE, + ); + } +} + +/** * Tests for check_plain(), filter_xss(), format_string(), and check_url(). */ class CommonXssUnitTest extends DrupalUnitTestCase { @@ -888,6 +979,31 @@ // Verify that invalid characters (including non-breaking space) are stripped from the identifier. $this->assertIdentical(drupal_clean_css_identifier('invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ identifier', array()), 'invalididentifier', 'Strip invalid characters.'); + + // Verify that double underscores are replaced in the identifier by default. + $identifier = 'css__identifier__with__double__underscores'; + $expected = 'css--identifier--with--double--underscores'; + $this->assertIdentical(drupal_clean_css_identifier($identifier), $expected, 'Verify double underscores are replaced with double hyphens by default.'); + + // Verify that double underscores are preserved in the identifier if the + // variable allow_css_double_underscores is set to TRUE. + $this->setAllowCSSDoubleUnderscores(TRUE); + $this->assertIdentical(drupal_clean_css_identifier($identifier), $identifier, 'Verify double underscores are preserved if the allow_css_double_underscores set to TRUE.'); + + // To avoid affecting other test cases, set the variable + // allow_css_double_underscores to FALSE which is the default value. + $this->setAllowCSSDoubleUnderscores(FALSE); + } + + /** + * Set the variable allow_css_double_underscores and reset the cache. + * + * @param $value bool + * A new value to be set to allow_css_double_underscores. + */ + function setAllowCSSDoubleUnderscores($value) { + $GLOBALS['conf']['allow_css_double_underscores'] = $value; + drupal_static_reset('drupal_clean_css_identifier:allow_css_double_underscores'); } /** @@ -1195,7 +1311,7 @@ function testRegions() { global $theme_key; - $block_regions = array_keys(system_region_list($theme_key)); + $block_regions = system_region_list($theme_key, REGIONS_ALL, FALSE); $delimiter = $this->randomName(32); $values = array(); // Set some random content for each region available. @@ -1256,6 +1372,15 @@ $this->assertText('drupal_goto', 'Drupal goto redirect succeeded.'); $this->assertEqual($this->getUrl(), url('common-test/drupal_goto', array('query' => array('foo' => '123'), 'absolute' => TRUE)), 'Drupal goto redirected to expected URL.'); + // Test that calling drupal_goto() on the current path is not dangerous. + variable_set('common_test_redirect_current_path', TRUE); + $this->drupalGet('', array('query' => array('q' => 'http://www.example.com/'))); + $headers = $this->drupalGetHeaders(TRUE); + list(, $status) = explode(' ', $headers[0][':status'], 3); + $this->assertEqual($status, 302, 'Expected response code was sent.'); + $this->assertNotEqual($this->getUrl(), 'http://www.example.com/', 'Drupal goto did not redirect to external URL.'); + $this->assertTrue(strpos($this->getUrl(), url('<front>', array('absolute' => TRUE))) === 0, 'Drupal redirected to itself.'); + variable_del('common_test_redirect_current_path'); // Test that drupal_goto() respects ?destination=xxx. Use an complicated URL // to test that the path is encoded and decoded properly. $destination = 'common-test/drupal_goto/destination?foo=%2525&bar=123'; diff -Naur drupal-7.41/modules/simpletest/tests/common_test.info drupal-7.66/modules/simpletest/tests/common_test.info --- drupal-7.41/modules/simpletest/tests/common_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/common_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -7,8 +7,7 @@ stylesheets[print][] = common_test.print.css hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/common_test.module drupal-7.66/modules/simpletest/tests/common_test.module --- drupal-7.41/modules/simpletest/tests/common_test.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/common_test.module 2019-04-17 22:20:46.000000000 +0200 @@ -93,6 +93,18 @@ } /** + * Implements hook_init(). + */ +function common_test_init() { + if (variable_get('common_test_redirect_current_path', FALSE)) { + drupal_goto(current_path()); + } + if (variable_get('common_test_link_to_current_path', FALSE)) { + drupal_set_message(l('link which should point to the current path', current_path())); + } +} + +/** * Print destination query parameter. */ function common_test_destination() { diff -Naur drupal-7.41/modules/simpletest/tests/common_test_cron_helper.info drupal-7.66/modules/simpletest/tests/common_test_cron_helper.info --- drupal-7.41/modules/simpletest/tests/common_test_cron_helper.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/common_test_cron_helper.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/database_test.info drupal-7.66/modules/simpletest/tests/database_test.info --- drupal-7.41/modules/simpletest/tests/database_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/database_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info drupal-7.66/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info --- drupal-7.41/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -7,8 +7,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info drupal-7.66/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info --- drupal-7.41/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info drupal-7.66/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info --- drupal-7.41/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/entity_cache_test.info drupal-7.66/modules/simpletest/tests/entity_cache_test.info --- drupal-7.41/modules/simpletest/tests/entity_cache_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/entity_cache_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ dependencies[] = entity_cache_test_dependency hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/entity_cache_test_dependency.info drupal-7.66/modules/simpletest/tests/entity_cache_test_dependency.info --- drupal-7.41/modules/simpletest/tests/entity_cache_test_dependency.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/entity_cache_test_dependency.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/entity_crud_hook_test.info drupal-7.66/modules/simpletest/tests/entity_crud_hook_test.info --- drupal-7.41/modules/simpletest/tests/entity_crud_hook_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/entity_crud_hook_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/entity_query_access_test.info drupal-7.66/modules/simpletest/tests/entity_query_access_test.info --- drupal-7.41/modules/simpletest/tests/entity_query_access_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/entity_query_access_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/error_test.info drupal-7.66/modules/simpletest/tests/error_test.info --- drupal-7.41/modules/simpletest/tests/error_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/error_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/file.test drupal-7.66/modules/simpletest/tests/file.test --- drupal-7.41/modules/simpletest/tests/file.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/file.test 2019-04-17 22:20:46.000000000 +0200 @@ -957,6 +957,15 @@ $path = file_create_filename($basename, $directory); $this->assertEqual($path, $expected, format_string('Creating a new filepath from %original equals %new.', array('%new' => $path, '%original' => $original)), 'File'); + try { + $filename = "a\xFFtest\x80€.txt"; + file_create_filename($filename, $directory); + $this->fail('Expected exception not thrown'); + } + catch (RuntimeException $e) { + $this->assertEqual("Invalid filename '$filename'", $e->getMessage()); + } + // @TODO: Finally we copy a file into a directory several times, to ensure a properly iterating filename suffix. } @@ -989,6 +998,14 @@ $this->assertNotEqual($path, $destination, 'A new filepath destination is created when filepath destination already exists with FILE_EXISTS_RENAME.', 'File'); $path = file_destination($destination, FILE_EXISTS_ERROR); $this->assertEqual($path, FALSE, 'An error is returned when filepath destination already exists with FILE_EXISTS_ERROR.', 'File'); + + try { + file_destination("core/misc/a\xFFtest\x80€.txt", FILE_EXISTS_REPLACE); + $this->fail('Expected exception not thrown'); + } + catch (RuntimeException $e) { + $this->assertEqual("Invalid filename 'a\xFFtest\x80€.txt'", $e->getMessage()); + } } /** @@ -2766,4 +2783,64 @@ $this->assertTrue(file_stream_wrapper_valid_scheme(file_uri_scheme('public://asdf')), 'Got a valid stream scheme from public://asdf'); $this->assertFalse(file_stream_wrapper_valid_scheme(file_uri_scheme('foo://asdf')), 'Did not get a valid stream scheme from foo://asdf'); } + + /** + * Tests that phar stream wrapper is registered as expected. + * + * @see file_get_stream_wrappers() + */ + public function testPharStreamWrapperRegistration() { + if (!class_exists('Phar', FALSE)) { + $this->assertFalse(in_array('phar', stream_get_wrappers(), TRUE), 'PHP is compiled without phar support. Therefore, no phar stream wrapper is registered.'); + } + elseif (version_compare(PHP_VERSION, '5.3.3', '<')) { + $this->assertFalse(in_array('phar', stream_get_wrappers(), TRUE), 'The PHP version is <5.3.3. The built-in phar stream wrapper has been unregistered and not replaced.'); + } + else { + $this->assertTrue(in_array('phar', stream_get_wrappers(), TRUE), 'A phar stream wrapper is registered.'); + $this->assertFalse(file_stream_wrapper_valid_scheme('phar'), 'The phar scheme is not a valid scheme for Drupal File API usage.'); + } + + // Ensure that calling file_get_stream_wrappers() multiple times, both + // without and with a drupal_static_reset() in between, does not create + // errors due to the PharStreamWrapperManager singleton. + file_get_stream_wrappers(); + file_get_stream_wrappers(); + drupal_static_reset('file_get_stream_wrappers'); + file_get_stream_wrappers(); + } + + /** + * Tests that only valid phar files can be used. + */ + public function testPharFile() { + if (!in_array('phar', stream_get_wrappers(), TRUE)) { + $this->pass('There is no phar stream wrapper registered.'); + // Nothing else in this test is relevant when there's no phar stream + // wrapper. testPharStreamWrapperRegistration() is sufficient for testing + // the conditions of when the stream wrapper should or should not be + // registered. + return; + } + + $base = dirname(dirname(__FILE__)) . '/files'; + + // Ensure that file operations via the phar:// stream wrapper work for phar + // files with the .phar extension. + $this->assertFalse(file_exists("phar://$base/phar-1.phar/no-such-file.php")); + $this->assertTrue(file_exists("phar://$base/phar-1.phar/index.php")); + $file_contents = file_get_contents("phar://$base/phar-1.phar/index.php"); + $expected_hash = 'c7e7904ea573c5ebea3ef00bb08c1f86af1a45961fbfbeb1892ff4a98fd73ad5'; + $this->assertIdentical($expected_hash, hash('sha256', $file_contents)); + + // Ensure that file operations via the phar:// stream wrapper throw an + // exception for files without the .phar extension. + try { + file_exists("phar://$base/image-2.jpg/index.php"); + $this->fail('Expected exception failed to be thrown when accessing an invalid phar file.'); + } + catch (Exception $e) { + $this->assertEqual(get_class($e), 'TYPO3\PharStreamWrapper\Exception', 'Expected exception thrown when accessing an invalid phar file.'); + } + } } diff -Naur drupal-7.41/modules/simpletest/tests/file_test.info drupal-7.66/modules/simpletest/tests/file_test.info --- drupal-7.41/modules/simpletest/tests/file_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/file_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ files[] = file_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/filter_test.info drupal-7.66/modules/simpletest/tests/filter_test.info --- drupal-7.41/modules/simpletest/tests/filter_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/filter_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/form.test drupal-7.66/modules/simpletest/tests/form.test --- drupal-7.41/modules/simpletest/tests/form.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/form.test 2019-04-17 22:20:46.000000000 +0200 @@ -691,6 +691,14 @@ } /** + * Tests that a form with a disabled CSRF token can be validated. + */ + function testDisabledToken() { + $this->drupalPost('form-test/validate-no-token', array(), 'Save'); + $this->assertText('The form_test_validate_no_token form has been submitted successfully.'); + } + + /** * Tests partial form validation through #limit_validation_errors. */ function testValidateLimitErrors() { @@ -994,6 +1002,26 @@ $this->assertTrue(isset($errors['tableselect']), 'Option checker disallows invalid values for radio buttons.'); } + /** + * Test presence of ajax functionality + */ + function testAjax() { + $rows = array('row1', 'row2', 'row3'); + // Test checkboxes (#multiple == TRUE). + foreach ($rows as $row) { + $element = 'tableselect[' . $row . ']'; + $edit = array($element => TRUE); + $result = $this->drupalPostAJAX('form_test/tableselect/multiple-true', $edit, $element); + $this->assertFalse(empty($result), t('Ajax triggers on checkbox for @row.', array('@row' => $row))); + } + // Test radios (#multiple == FALSE). + $element = 'tableselect'; + foreach ($rows as $row) { + $edit = array($element => $row); + $result = $this->drupalPostAjax('form_test/tableselect/multiple-false', $edit, $element); + $this->assertFalse(empty($result), t('Ajax triggers on radio for @row.', array('@row' => $row))); + } + } /** * Helper function for the option check test to submit a form while collecting errors. @@ -1393,6 +1421,59 @@ } /** + * Test cache_form. + */ +class FormsFormCacheTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Form caching', + 'description' => 'Tests storage and retrieval of forms from cache.', + 'group' => 'Form API', + ); + } + + function setUp() { + parent::setUp('form_test'); + } + + /** + * Tests storing and retrieving the form from cache. + */ + function testCacheForm() { + $form = drupal_get_form('form_test_cache_form'); + $form_state = array('foo' => 'bar', 'build_info' => array('baz')); + form_set_cache($form['#build_id'], $form, $form_state); + + $cached_form_state = array(); + $cached_form = form_get_cache($form['#build_id'], $cached_form_state); + + $this->assertEqual($cached_form['#build_id'], $form['#build_id'], 'Form retrieved from cache_form successfully.'); + $this->assertEqual($cached_form_state['foo'], 'bar', 'Data retrieved from cache_form successfully.'); + } + + /** + * Tests changing form_cache_expiration. + */ + function testCacheFormCustomExpiration() { + variable_set('form_cache_expiration', -1 * (24 * 60 * 60)); + + $form = drupal_get_form('form_test_cache_form'); + $form_state = array('foo' => 'bar', 'build_info' => array('baz')); + form_set_cache($form['#build_id'], $form, $form_state); + + // Clear expired entries from cache_form, which should include the entry we + // just stored. Without this, the form will still be retrieved from cache. + cache_clear_all(NULL, 'cache_form'); + + $cached_form_state = array(); + $cached_form = form_get_cache($form['#build_id'], $cached_form_state); + + $this->assertNull($cached_form, 'Expired form was not returned from cache.'); + $this->assertTrue(empty($cached_form_state), 'No data retrieved from cache for expired form.'); + } +} + +/** * Test wrapper form callbacks. */ class FormsFormWrapperTestCase extends DrupalWebTestCase { @@ -2099,3 +2180,36 @@ $this->assertNoDuplicateIds('There are no duplicate IDs'); } } + +/** + * Tests for form textarea. + */ +class FormTextareaTestCase extends DrupalUnitTestCase { + + public static function getInfo() { + return array( + 'name' => 'Form textarea', + 'description' => 'Tests form textarea related functions.', + 'group' => 'Form API', + ); + } + + /** + * Tests that textarea value is properly set. + */ + public function testValueCallback() { + $element = array(); + $form_state = array(); + $test_cases = array( + array(NULL, FALSE), + array(NULL, NULL), + array('', array('test')), + array('test', 'test'), + array('123', 123), + ); + foreach ($test_cases as $test_case) { + list($expected, $input) = $test_case; + $this->assertIdentical($expected, form_type_textarea_value($element, $input, $form_state)); + } + } +} diff -Naur drupal-7.41/modules/simpletest/tests/form_test.info drupal-7.66/modules/simpletest/tests/form_test.info --- drupal-7.41/modules/simpletest/tests/form_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/form_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/form_test.module drupal-7.66/modules/simpletest/tests/form_test.module --- drupal-7.41/modules/simpletest/tests/form_test.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/form_test.module 2019-04-17 22:20:46.000000000 +0200 @@ -37,6 +37,13 @@ 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); + $items['form-test/validate-no-token'] = array( + 'title' => 'Form validation without a CSRF token', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_test_validate_no_token'), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); $items['form-test/limit-validation-errors'] = array( 'title' => 'Form validation with some error suppression', 'page callback' => 'drupal_get_form', @@ -455,6 +462,27 @@ } /** + * Form builder for testing submission of a form without a CSRF token. + */ +function form_test_validate_no_token($form, &$form_state) { + $form['submit'] = array( + '#type' => 'submit', + '#value' => 'Save', + ); + + $form['#token'] = FALSE; + + return $form; +} + +/** + * Form submission handler for form_test_validate_no_token(). + */ +function form_test_validate_no_token_submit($form, &$form_state) { + drupal_set_message('The form_test_validate_no_token form has been submitted successfully.'); +} + +/** * Builds a simple form with a button triggering partial validation. */ function form_test_limit_validation_errors_form($form, &$form_state) { @@ -589,11 +617,17 @@ $form['tableselect'] = $element_properties; $form['tableselect'] += array( + '#prefix' => '<div id="tableselect-wrapper">', + '#suffix' => '</div>', '#type' => 'tableselect', '#header' => $header, '#options' => $options, '#multiple' => FALSE, '#empty' => t('Empty text.'), + '#ajax' => array( + 'callback' => '_form_test_tableselect_ajax_callback', + 'wrapper' => 'tableselect-wrapper', + ), ); $form['submit'] = array( @@ -698,6 +732,13 @@ } /** +* Ajax callback that returns the form element. +*/ +function _form_test_tableselect_ajax_callback($form, &$form_state) { + return $form['tableselect']; +} + +/** * A multistep form for testing the form storage. * * It uses two steps for editing a virtual "thing". Any changes to it are saved @@ -878,6 +919,24 @@ } /** + * A simple form for testing form caching. + */ +function form_test_cache_form($form, &$form_state) { + $form['title'] = array( + '#type' => 'textfield', + '#title' => 'Title', + '#required' => TRUE, + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => 'Save', + ); + + return $form; +} + +/** * A form for testing form labels and required marks. */ function form_label_test_form() { diff -Naur drupal-7.41/modules/simpletest/tests/image.test drupal-7.66/modules/simpletest/tests/image.test --- drupal-7.41/modules/simpletest/tests/image.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/image.test 2019-04-17 22:20:46.000000000 +0200 @@ -207,9 +207,11 @@ protected $green = array(0, 255, 0, 0); protected $blue = array(0, 0, 255, 0); protected $yellow = array(255, 255, 0, 0); - protected $fuchsia = array(255, 0, 255, 0); // Used as background colors. - protected $transparent = array(0, 0, 0, 127); protected $white = array(255, 255, 255, 0); + protected $transparent = array(0, 0, 0, 127); + // Used as rotate background colors. + protected $fuchsia = array(255, 0, 255, 0); + protected $rotate_transparent = array(255, 255, 255, 127); protected $width = 40; protected $height = 20; @@ -275,6 +277,7 @@ $files = array( 'image-test.png', 'image-test.gif', + 'image-test-no-transparency.gif', 'image-test.jpg', ); @@ -332,15 +335,10 @@ ); // Systems using non-bundled GD2 don't have imagerotate. Test if available. - if (function_exists('imagerotate')) { + // @todo Remove the version check once https://www.drupal.org/node/2918570 + // is resolved. + if (function_exists('imagerotate') && (version_compare(PHP_VERSION, '7.0.26', '<') || (version_compare(PHP_VERSION, '7.1', '>=') && version_compare(PHP_VERSION, '7.1.12', '<')))) { $operations += array( - 'rotate_5' => array( - 'function' => 'rotate', - 'arguments' => array(5, 0xFF00FF), // Fuchsia background. - 'width' => 42, - 'height' => 24, - 'corners' => array_fill(0, 4, $this->fuchsia), - ), 'rotate_90' => array( 'function' => 'rotate', 'arguments' => array(90, 0xFF00FF), // Fuchsia background. @@ -348,13 +346,6 @@ 'height' => 40, 'corners' => array($this->fuchsia, $this->red, $this->green, $this->blue), ), - 'rotate_transparent_5' => array( - 'function' => 'rotate', - 'arguments' => array(5), - 'width' => 42, - 'height' => 24, - 'corners' => array_fill(0, 4, $this->transparent), - ), 'rotate_transparent_90' => array( 'function' => 'rotate', 'arguments' => array(90), @@ -363,6 +354,49 @@ 'corners' => array($this->transparent, $this->red, $this->green, $this->blue), ), ); + // As of PHP version 5.5, GD uses a different algorithm to rotate images + // than version 5.4 and below, resulting in different dimensions. + // See https://bugs.php.net/bug.php?id=65148. + // For the 40x20 test images, the dimensions resulting from rotation will + // be 1 pixel smaller in both width and height in PHP 5.5 and above. + // @todo: The PHP bug was fixed in PHP 7.0.26 and 7.1.12. Change the code + // below to reflect that in https://www.drupal.org/node/2918570. + if (version_compare(PHP_VERSION, '5.5', '>=')) { + $operations += array( + 'rotate_5' => array( + 'function' => 'rotate', + 'arguments' => array(5, 0xFF00FF), // Fuchsia background. + 'width' => 41, + 'height' => 23, + 'corners' => array_fill(0, 4, $this->fuchsia), + ), + 'rotate_transparent_5' => array( + 'function' => 'rotate', + 'arguments' => array(5), + 'width' => 41, + 'height' => 23, + 'corners' => array_fill(0, 4, $this->rotate_transparent), + ), + ); + } + else { + $operations += array( + 'rotate_5' => array( + 'function' => 'rotate', + 'arguments' => array(5, 0xFF00FF), // Fuchsia background. + 'width' => 42, + 'height' => 24, + 'corners' => array_fill(0, 4, $this->fuchsia), + ), + 'rotate_transparent_5' => array( + 'function' => 'rotate', + 'arguments' => array(5), + 'width' => 42, + 'height' => 24, + 'corners' => array_fill(0, 4, $this->rotate_transparent), + ), + ); + } } // Systems using non-bundled GD2 don't have imagefilter. Test if available. @@ -430,6 +464,11 @@ } // Now check each of the corners to ensure color correctness. foreach ($values['corners'] as $key => $corner) { + // The test gif that does not have transparency has yellow where the + // others have transparent. + if ($file === 'image-test-no-transparency.gif' && $corner === $this->transparent) { + $corner = $this->yellow; + } // Get the location of the corner. switch ($key) { case 0: diff -Naur drupal-7.41/modules/simpletest/tests/image_test.info drupal-7.66/modules/simpletest/tests/image_test.info --- drupal-7.41/modules/simpletest/tests/image_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/image_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/mail.test drupal-7.66/modules/simpletest/tests/mail.test --- drupal-7.41/modules/simpletest/tests/mail.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/mail.test 2019-04-17 22:20:46.000000000 +0200 @@ -441,7 +441,7 @@ * <CRLF> is 1000 characters." */ function testVeryLongLineWrap() { - $input = 'Drupal<br /><p>' . str_repeat('x', 2100) . '</><br />Drupal'; + $input = 'Drupal<br /><p>' . str_repeat('x', 2100) . '</p><br />Drupal'; $output = drupal_html_to_text($input); // This awkward construct comes from includes/mail.inc lines 8-13. $eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS); @@ -455,7 +455,6 @@ $maximum_line_length = max($maximum_line_length, strlen($line . $eol)); } $verbose = 'Maximum line length found was ' . $maximum_line_length . ' octets.'; - // @todo This should assert that $maximum_line_length <= 1000. - $this->pass($verbose); + $this->assertTrue($maximum_line_length <= 1000, $verbose); } } diff -Naur drupal-7.41/modules/simpletest/tests/menu_test.info drupal-7.66/modules/simpletest/tests/menu_test.info --- drupal-7.41/modules/simpletest/tests/menu_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/menu_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/module_test.info drupal-7.66/modules/simpletest/tests/module_test.info --- drupal-7.41/modules/simpletest/tests/module_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/module_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/path_test.info drupal-7.66/modules/simpletest/tests/path_test.info --- drupal-7.41/modules/simpletest/tests/path_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/path_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/psr_0_test/psr_0_test.info drupal-7.66/modules/simpletest/tests/psr_0_test/psr_0_test.info --- drupal-7.41/modules/simpletest/tests/psr_0_test/psr_0_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/psr_0_test/psr_0_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ hidden = TRUE package = Testing -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/psr_4_test/psr_4_test.info drupal-7.66/modules/simpletest/tests/psr_4_test/psr_4_test.info --- drupal-7.41/modules/simpletest/tests/psr_4_test/psr_4_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/psr_4_test/psr_4_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ hidden = TRUE package = Testing -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/requirements1_test.info drupal-7.66/modules/simpletest/tests/requirements1_test.info --- drupal-7.41/modules/simpletest/tests/requirements1_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/requirements1_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/requirements2_test.info drupal-7.66/modules/simpletest/tests/requirements2_test.info --- drupal-7.41/modules/simpletest/tests/requirements2_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/requirements2_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -7,8 +7,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/session_test.info drupal-7.66/modules/simpletest/tests/session_test.info --- drupal-7.41/modules/simpletest/tests/session_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/session_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/system_dependencies_test.info drupal-7.66/modules/simpletest/tests/system_dependencies_test.info --- drupal-7.41/modules/simpletest/tests/system_dependencies_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/system_dependencies_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ hidden = TRUE dependencies[] = _missing_dependency -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info drupal-7.66/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info --- drupal-7.41/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ hidden = TRUE dependencies[] = system_incompatible_core_version_test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/system_incompatible_core_version_test.info drupal-7.66/modules/simpletest/tests/system_incompatible_core_version_test.info --- drupal-7.41/modules/simpletest/tests/system_incompatible_core_version_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/system_incompatible_core_version_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 5.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info drupal-7.66/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info --- drupal-7.41/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -7,8 +7,7 @@ ; 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 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/system_incompatible_module_version_test.info drupal-7.66/modules/simpletest/tests/system_incompatible_module_version_test.info --- drupal-7.41/modules/simpletest/tests/system_incompatible_module_version_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/system_incompatible_module_version_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/system_project_namespace_test.info drupal-7.66/modules/simpletest/tests/system_project_namespace_test.info --- drupal-7.41/modules/simpletest/tests/system_project_namespace_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/system_project_namespace_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ hidden = TRUE dependencies[] = drupal:filter -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/system_test.info drupal-7.66/modules/simpletest/tests/system_test.info --- drupal-7.41/modules/simpletest/tests/system_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/system_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ files[] = system_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/system_test.install drupal-7.66/modules/simpletest/tests/system_test.install --- drupal-7.41/modules/simpletest/tests/system_test.install 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/modules/simpletest/tests/system_test.install 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,20 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for the system_test module. + */ + +/** + * Implements hook_schema(). + */ +function system_test_schema() { + // Trigger a search for a module in the filesystem when requested by + // system_test_drupal_get_filename_with_schema_rebuild(). + if (variable_get('system_test_drupal_get_filename_attempt_recursive_rebuild')) { + $module_name = variable_get('system_test_drupal_get_filename_test_module_name'); + drupal_get_filename('module', $module_name); + } + + return array(); +} diff -Naur drupal-7.41/modules/simpletest/tests/system_test.module drupal-7.66/modules/simpletest/tests/system_test.module --- drupal-7.41/modules/simpletest/tests/system_test.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/system_test.module 2019-04-17 22:20:46.000000000 +0200 @@ -127,6 +127,20 @@ 'type' => MENU_CALLBACK, ); + $items['system-test/drupal-get-filename'] = array( + 'title' => 'Test drupal_get_filename()', + 'page callback' => 'system_test_drupal_get_filename', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + + $items['system-test/drupal-get-filename-with-schema-rebuild'] = array( + 'title' => 'Test drupal_get_filename() with a schema rebuild', + 'page callback' => 'system_test_drupal_get_filename_with_schema_rebuild', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + return $items; } @@ -482,3 +496,76 @@ // information. exit; } + +/** + * Page callback to run drupal_get_filename() on a particular module. + */ +function system_test_drupal_get_filename() { + // Prevent SimpleTest from failing as a result of the expected PHP warnings + // this function causes. Any warnings will be recorded in the database logs + // for examination by the tests. + define('SIMPLETEST_COLLECT_ERRORS', FALSE); + + $module_name = variable_get('system_test_drupal_get_filename_test_module_name'); + drupal_get_filename('module', $module_name); + + return ''; +} + +/** + * Page callback to run drupal_get_filename() and do a schema rebuild. + */ +function system_test_drupal_get_filename_with_schema_rebuild() { + // Prevent SimpleTest from failing as a result of the expected PHP warnings + // this function causes. + define('SIMPLETEST_COLLECT_ERRORS', FALSE); + + // Record the original database tables from drupal_get_schema(). + variable_set('system_test_drupal_get_filename_with_schema_rebuild_original_tables', array_keys(drupal_get_schema(NULL, TRUE))); + + // Trigger system_test_schema() and system_test_watchdog() to perform an + // attempted recursive rebuild when drupal_get_schema() is called. See + // BootstrapGetFilenameWebTestCase::testRecursiveRebuilds(). + variable_set('system_test_drupal_get_filename_attempt_recursive_rebuild', TRUE); + drupal_get_schema(NULL, TRUE); + + return ''; +} + +/** + * Implements hook_watchdog(). + */ +function system_test_watchdog($log_entry) { + // If an attempted recursive schema rebuild has been triggered by + // system_test_drupal_get_filename_with_schema_rebuild(), perform the rebuild + // in response to the missing file message triggered by system_test_schema(). + if (!variable_get('system_test_drupal_get_filename_attempt_recursive_rebuild')) { + return; + } + if ($log_entry['type'] != 'php' || $log_entry['severity'] != WATCHDOG_WARNING) { + return; + } + $module_name = variable_get('system_test_drupal_get_filename_test_module_name'); + if (!isset($log_entry['variables']['!message']) || strpos($log_entry['variables']['!message'], format_string('The following module is missing from the file system: %name', array('%name' => $module_name))) === FALSE) { + return; + } + variable_set('system_test_drupal_get_filename_with_schema_rebuild_final_tables', array_keys(drupal_get_schema())); +} + +/** + * Implements hook_module_implements_alter(). + */ +function system_test_module_implements_alter(&$implementations, $hook) { + // For BootstrapGetFilenameWebTestCase::testRecursiveRebuilds() to work + // correctly, this module's hook_schema() implementation cannot be either the + // first implementation (since that would trigger a potential recursive + // rebuild before anything is in the drupal_get_schema() cache) or the last + // implementation (since that would trigger a potential recursive rebuild + // after the cache is already complete). So put it somewhere in the middle. + if ($hook == 'schema') { + $group = $implementations['system_test']; + unset($implementations['system_test']); + $count = count($implementations); + $implementations = array_merge(array_slice($implementations, 0, $count / 2, TRUE), array('system_test' => $group), array_slice($implementations, $count / 2, NULL, TRUE)); + } +} diff -Naur drupal-7.41/modules/simpletest/tests/taxonomy_test.info drupal-7.66/modules/simpletest/tests/taxonomy_test.info --- drupal-7.41/modules/simpletest/tests/taxonomy_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/taxonomy_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ hidden = TRUE dependencies[] = taxonomy -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/taxonomy_test.module drupal-7.66/modules/simpletest/tests/taxonomy_test.module --- drupal-7.41/modules/simpletest/tests/taxonomy_test.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/taxonomy_test.module 2019-04-17 22:20:46.000000000 +0200 @@ -109,3 +109,33 @@ ->execute() ->fetchField(); } + +/** + * Implements hook_query_alter(). + */ +function taxonomy_test_query_alter(QueryAlterableInterface $query) { + $value = variable_get(__FUNCTION__); + if (isset($value)) { + variable_set(__FUNCTION__, ++$value); + } +} + +/** + * Implements hook_query_TAG_alter(). + */ +function taxonomy_test_query_term_access_alter(QueryAlterableInterface $query) { + $value = variable_get(__FUNCTION__); + if (isset($value)) { + variable_set(__FUNCTION__, ++$value); + } +} + +/** + * Implements hook_query_TAG_alter(). + */ +function taxonomy_test_query_taxonomy_term_access_alter(QueryAlterableInterface $query) { + $value = variable_get(__FUNCTION__); + if (isset($value)) { + variable_set(__FUNCTION__, ++$value); + } +} diff -Naur drupal-7.41/modules/simpletest/tests/theme.test drupal-7.66/modules/simpletest/tests/theme.test --- drupal-7.41/modules/simpletest/tests/theme.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/theme.test 2019-04-17 22:20:46.000000000 +0200 @@ -646,3 +646,34 @@ } } + +/** + * Tests module-provided theme engines. + */ +class ModuleProvidedThemeEngineTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Theme engine test', + 'description' => 'Tests module-provided theme engines.', + 'group' => 'Theme', + ); + } + + function setUp() { + parent::setUp('theme_test'); + theme_enable(array('test_theme', 'test_theme_nyan_cat')); + } + + /** + * Ensures that the module provided theme engine is found and used by core. + */ + function testEngineIsFoundAndWorking() { + variable_set('theme_default', 'test_theme_nyan_cat'); + variable_set('admin_theme', 'test_theme_nyan_cat'); + + $this->drupalGet('theme-test/engine-info-test'); + $this->assertText('Miaou'); + } + +} diff -Naur drupal-7.41/modules/simpletest/tests/theme_test.info drupal-7.66/modules/simpletest/tests/theme_test.info --- drupal-7.41/modules/simpletest/tests/theme_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/theme_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/theme_test.module drupal-7.66/modules/simpletest/tests/theme_test.module --- drupal-7.41/modules/simpletest/tests/theme_test.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/theme_test.module 2019-04-17 22:20:46.000000000 +0200 @@ -27,10 +27,19 @@ $themes['test_theme'] = drupal_get_path('module', 'theme_test') . '/themes/test_theme/test_theme.info'; $themes['test_basetheme'] = drupal_get_path('module', 'theme_test') . '/themes/test_basetheme/test_basetheme.info'; $themes['test_subtheme'] = drupal_get_path('module', 'theme_test') . '/themes/test_subtheme/test_subtheme.info'; + $themes['test_theme_nyan_cat'] = drupal_get_path('module', 'theme_test') . '/themes/test_theme_nyan_cat/test_theme_nyan_cat.info'; return $themes; } /** + * Implements hook_system_theme_engine_info(). + */ +function theme_test_system_theme_engine_info() { + $theme_engines['nyan_cat'] = drupal_get_path('module', 'theme_test') . '/themes/engines/nyan_cat/nyan_cat.engine'; + return $theme_engines; +} + +/** * Implements hook_menu(). */ function theme_test_menu() { @@ -58,6 +67,12 @@ 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); + $items['theme-test/engine-info-test'] = array( + 'description' => "Serves a simple page rendered using a Nyan Cat theme engine template.", + 'page callback' => '_theme_test_engine_info_test', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); return $items; } @@ -140,6 +155,15 @@ } /** + * Serves a simple page renderered using a Nyan Cat theme engine template. + */ +function _theme_test_engine_info_test() { + return array( + '#markup' => theme('theme_test_template_test'), + ); +} + +/** * Theme function for testing theme('theme_test_foo'). */ function theme_theme_test_foo($variables) { diff -Naur drupal-7.41/modules/simpletest/tests/themes/engines/nyan_cat/nyan_cat.engine drupal-7.66/modules/simpletest/tests/themes/engines/nyan_cat/nyan_cat.engine --- drupal-7.41/modules/simpletest/tests/themes/engines/nyan_cat/nyan_cat.engine 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/modules/simpletest/tests/themes/engines/nyan_cat/nyan_cat.engine 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,53 @@ +<?php + +/** + * @file + * Handles integration of Nyan cat templates because we love kittens. + */ + +/** + * Includes .theme file from themes. + */ +function nyan_cat_init($template) { + $file = dirname($template->filename) . '/template.theme'; + if (file_exists($file)) { + include_once DRUPAL_ROOT . '/' . $file; + } +} + +/** + * Implements hook_theme(). + */ +function nyan_cat_theme($existing, $type, $theme, $path) { + $templates = drupal_find_theme_functions($existing, array($theme)); + $templates += drupal_find_theme_templates($existing, '.nyan-cat.html', $path); + return $templates; +} + +/** + * Implements hook_extension(). + */ +function nyan_cat_extension() { + return '.nyan-cat.html'; +} + +/** + * Implements hook_render_template(). + * + * @param string $template_file + * The filename of the template to render. + * @param mixed[] $variables + * A keyed array of variables that will appear in the output. + * + * @return string + * The output generated by the template. + */ +function nyan_cat_render_template($template_file, $variables) { + $output = str_replace('div', 'nyancat', file_get_contents(DRUPAL_ROOT . '/' . $template_file)); + foreach ($variables as $key => $variable) { + if (strpos($output, '9' . $key) !== FALSE) { + $output = str_replace('9' . $key, $variable, $output); + } + } + return $output; +} diff -Naur drupal-7.41/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info drupal-7.66/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info --- drupal-7.41/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ settings[basetheme_only] = base theme value settings[subtheme_override] = base theme value -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info drupal-7.66/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info --- drupal-7.41/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ settings[subtheme_override] = subtheme value -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/themes/test_theme/test_theme.info drupal-7.66/modules/simpletest/tests/themes/test_theme/test_theme.info --- drupal-7.41/modules/simpletest/tests/themes/test_theme/test_theme.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/themes/test_theme/test_theme.info 2019-04-17 22:39:36.000000000 +0200 @@ -17,8 +17,7 @@ settings[theme_test_setting] = default value -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/themes/test_theme/theme-settings.php drupal-7.66/modules/simpletest/tests/themes/test_theme/theme-settings.php --- drupal-7.41/modules/simpletest/tests/themes/test_theme/theme-settings.php 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/modules/simpletest/tests/themes/test_theme/theme-settings.php 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,32 @@ +<?php + +/** + * @file + * Theme setting callbacks for the test_theme theme. + */ + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function test_theme_form_system_theme_settings_alter(&$form, &$form_state) { + $form['test_theme_checkbox'] = array( + '#type' => 'checkbox', + '#title' => 'Test theme checkbox', + '#default_value' => theme_get_setting('test_theme_checkbox'), + ); + + // Force the form to be cached so we can test that this file is properly + // loaded and the custom submit handler is properly called even on a cached + // form build. + $form_state['cache'] = TRUE; + $form['#submit'][] = 'test_theme_form_system_theme_settings_submit'; +} + +/** + * Form submission handler for the test theme settings form. + * + * @see test_theme_form_system_theme_settings_alter() + */ +function test_theme_form_system_theme_settings_submit($form, &$form_state) { + drupal_set_message('The test theme setting was saved.'); +} diff -Naur drupal-7.41/modules/simpletest/tests/themes/test_theme_nyan_cat/templates/theme_test_template_test.nyan-cat.html drupal-7.66/modules/simpletest/tests/themes/test_theme_nyan_cat/templates/theme_test_template_test.nyan-cat.html --- drupal-7.41/modules/simpletest/tests/themes/test_theme_nyan_cat/templates/theme_test_template_test.nyan-cat.html 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/modules/simpletest/tests/themes/test_theme_nyan_cat/templates/theme_test_template_test.nyan-cat.html 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1 @@ +Miaou \ No newline at end of file diff -Naur drupal-7.41/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info drupal-7.66/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info --- drupal-7.41/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info 2019-04-17 22:39:36.000000000 +0200 @@ -0,0 +1,10 @@ +name = Nyan cat engine based test theme +description = Theme for testing the module-provided theme engines. +core = 7.x +hidden = TRUE +engine = nyan_cat + +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" +project = "drupal" +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/update_script_test.info drupal-7.66/modules/simpletest/tests/update_script_test.info --- drupal-7.41/modules/simpletest/tests/update_script_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/update_script_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/update_script_test.install drupal-7.66/modules/simpletest/tests/update_script_test.install --- drupal-7.41/modules/simpletest/tests/update_script_test.install 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/update_script_test.install 2019-04-17 22:20:46.000000000 +0200 @@ -31,6 +31,19 @@ 'severity' => REQUIREMENT_ERROR, ); break; + case REQUIREMENT_INFO: + $requirements['update_script_test_stop'] = array( + 'title' => 'Update script test stop', + 'value' => 'Error', + 'description' => 'This is a requirements error provided by the update_script_test module to stop the page redirect for the info.', + 'severity' => REQUIREMENT_ERROR, + ); + $requirements['update_script_test'] = array( + 'title' => 'Update script test', + 'description' => 'This is a requirements info provided by the update_script_test module.', + 'severity' => REQUIREMENT_INFO, + ); + break; } } diff -Naur drupal-7.41/modules/simpletest/tests/update_test_1.info drupal-7.66/modules/simpletest/tests/update_test_1.info --- drupal-7.41/modules/simpletest/tests/update_test_1.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/update_test_1.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/update_test_2.info drupal-7.66/modules/simpletest/tests/update_test_2.info --- drupal-7.41/modules/simpletest/tests/update_test_2.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/update_test_2.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/update_test_3.info drupal-7.66/modules/simpletest/tests/update_test_3.info --- drupal-7.41/modules/simpletest/tests/update_test_3.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/update_test_3.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/upgrade/drupal-6.filled.database.php drupal-7.66/modules/simpletest/tests/upgrade/drupal-6.filled.database.php --- drupal-7.41/modules/simpletest/tests/upgrade/drupal-6.filled.database.php 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/upgrade/drupal-6.filled.database.php 2019-04-17 22:20:46.000000000 +0200 @@ -19919,7 +19919,7 @@ 'vid' => '1', 'name' => 'vocabulary 1 (i=0)', 'description' => 'description of vocabulary 1 (i=0)', - 'help' => '', + 'help' => 'help for vocabulary 1 (i=0)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '0', @@ -19932,7 +19932,7 @@ 'vid' => '2', 'name' => 'vocabulary 2 (i=1)', 'description' => 'description of vocabulary 2 (i=1)', - 'help' => '', + 'help' => 'help for vocabulary 2 (i=1)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '1', @@ -19945,7 +19945,7 @@ 'vid' => '3', 'name' => 'vocabulary 3 (i=2)', 'description' => 'description of vocabulary 3 (i=2)', - 'help' => '', + 'help' => 'help for vocabulary 3 (i=2)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '0', @@ -19958,7 +19958,7 @@ 'vid' => '4', 'name' => 'vocabulary 4 (i=3)', 'description' => 'description of vocabulary 4 (i=3)', - 'help' => '', + 'help' => 'help for vocabulary 4 (i=3)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '1', @@ -19971,7 +19971,7 @@ 'vid' => '5', 'name' => 'vocabulary 5 (i=4)', 'description' => 'description of vocabulary 5 (i=4)', - 'help' => '', + 'help' => 'help for vocabulary 5 (i=4)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '0', @@ -19984,7 +19984,7 @@ 'vid' => '6', 'name' => 'vocabulary 6 (i=5)', 'description' => 'description of vocabulary 6 (i=5)', - 'help' => '', + 'help' => 'help for vocabulary 6 (i=5)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '1', @@ -19997,7 +19997,7 @@ 'vid' => '7', 'name' => 'vocabulary 7 (i=6)', 'description' => 'description of vocabulary 7 (i=6)', - 'help' => '', + 'help' => 'help for vocabulary 7 (i=6)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '0', @@ -20010,7 +20010,7 @@ 'vid' => '8', 'name' => 'vocabulary 8 (i=7)', 'description' => 'description of vocabulary 8 (i=7)', - 'help' => '', + 'help' => 'help for vocabulary 8 (i=7)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '1', @@ -20023,7 +20023,7 @@ 'vid' => '9', 'name' => 'vocabulary 9 (i=8)', 'description' => 'description of vocabulary 9 (i=8)', - 'help' => '', + 'help' => 'help for vocabulary 9 (i=8)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '0', @@ -20036,7 +20036,7 @@ 'vid' => '10', 'name' => 'vocabulary 10 (i=9)', 'description' => 'description of vocabulary 10 (i=9)', - 'help' => '', + 'help' => 'help for vocabulary 10 (i=9)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '1', @@ -20049,7 +20049,7 @@ 'vid' => '11', 'name' => 'vocabulary 11 (i=10)', 'description' => 'description of vocabulary 11 (i=10)', - 'help' => '', + 'help' => 'help for vocabulary 11 (i=10)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '0', @@ -20062,7 +20062,7 @@ 'vid' => '12', 'name' => 'vocabulary 12 (i=11)', 'description' => 'description of vocabulary 12 (i=11)', - 'help' => '', + 'help' => 'help for vocabulary 12 (i=11)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '1', @@ -20075,7 +20075,7 @@ 'vid' => '13', 'name' => 'vocabulary 13 (i=12)', 'description' => 'description of vocabulary 13 (i=12)', - 'help' => '', + 'help' => 'help for vocabulary 13 (i=12)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '0', @@ -20088,7 +20088,7 @@ 'vid' => '14', 'name' => 'vocabulary 14 (i=13)', 'description' => 'description of vocabulary 14 (i=13)', - 'help' => '', + 'help' => 'help for vocabulary 14 (i=13)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '1', @@ -20101,7 +20101,7 @@ 'vid' => '15', 'name' => 'vocabulary 15 (i=14)', 'description' => 'description of vocabulary 15 (i=14)', - 'help' => '', + 'help' => 'help for vocabulary 15 (i=14)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '0', @@ -20114,7 +20114,7 @@ 'vid' => '16', 'name' => 'vocabulary 16 (i=15)', 'description' => 'description of vocabulary 16 (i=15)', - 'help' => '', + 'help' => 'help for vocabulary 16 (i=15)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '1', @@ -20127,7 +20127,7 @@ 'vid' => '17', 'name' => 'vocabulary 17 (i=16)', 'description' => 'description of vocabulary 17 (i=16)', - 'help' => '', + 'help' => 'help for vocabulary 17 (i=16)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '0', @@ -20140,7 +20140,7 @@ 'vid' => '18', 'name' => 'vocabulary 18 (i=17)', 'description' => 'description of vocabulary 18 (i=17)', - 'help' => '', + 'help' => 'help for vocabulary 18 (i=17)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '1', @@ -20153,7 +20153,7 @@ 'vid' => '19', 'name' => 'vocabulary 19 (i=18)', 'description' => 'description of vocabulary 19 (i=18)', - 'help' => '', + 'help' => 'help for vocabulary 19 (i=18)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '0', @@ -20166,7 +20166,7 @@ 'vid' => '20', 'name' => 'vocabulary 20 (i=19)', 'description' => 'description of vocabulary 20 (i=19)', - 'help' => '', + 'help' => 'help for vocabulary 20 (i=19)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '1', @@ -20179,7 +20179,7 @@ 'vid' => '21', 'name' => 'vocabulary 21 (i=20)', 'description' => 'description of vocabulary 21 (i=20)', - 'help' => '', + 'help' => 'help for vocabulary 21 (i=20)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '0', @@ -20192,7 +20192,7 @@ 'vid' => '22', 'name' => 'vocabulary 22 (i=21)', 'description' => 'description of vocabulary 22 (i=21)', - 'help' => '', + 'help' => 'help for vocabulary 22 (i=21)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '1', @@ -20205,7 +20205,7 @@ 'vid' => '23', 'name' => 'vocabulary 23 (i=22)', 'description' => 'description of vocabulary 23 (i=22)', - 'help' => '', + 'help' => 'help for vocabulary 23 (i=22)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '0', @@ -20218,7 +20218,7 @@ 'vid' => '24', 'name' => 'vocabulary 24 (i=23)', 'description' => 'description of vocabulary 24 (i=23)', - 'help' => '', + 'help' => 'help for vocabulary 24 (i=23)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '1', diff -Naur drupal-7.41/modules/simpletest/tests/upgrade/upgrade.taxonomy.test drupal-7.66/modules/simpletest/tests/upgrade/upgrade.taxonomy.test --- drupal-7.41/modules/simpletest/tests/upgrade/upgrade.taxonomy.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/upgrade/upgrade.taxonomy.test 2019-04-17 22:20:46.000000000 +0200 @@ -74,9 +74,10 @@ $this->assertEqual($voc_keys, $inst_keys, 'Node type page has instances for every vocabulary.'); // Ensure instance variables are getting through. - foreach ($instances as $instance) { - $this->assertTrue(isset($instance['required']), 'The required setting was preserved during the upgrade path.'); - $this->assertTrue($instance['description'], 'The description was preserved during the upgrade path'); + foreach (array_unique($instances) as $instance) { + $field_instance = field_info_instance('node', $instance, 'page'); + $this->assertTrue(isset($field_instance['required']), 'The required setting was preserved during the upgrade path.'); + $this->assertTrue($field_instance['description'], 'The description was preserved during the upgrade path'); } // Node type 'story' was not explicitly in $vocabulary->nodes but diff -Naur drupal-7.41/modules/simpletest/tests/url_alter_test.info drupal-7.66/modules/simpletest/tests/url_alter_test.info --- drupal-7.41/modules/simpletest/tests/url_alter_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/url_alter_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/simpletest/tests/xmlrpc.test drupal-7.66/modules/simpletest/tests/xmlrpc.test --- drupal-7.41/modules/simpletest/tests/xmlrpc.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/xmlrpc.test 2019-04-17 22:20:46.000000000 +0200 @@ -246,4 +246,38 @@ $this->assertEqual($removed, 'system.methodSignature', 'Hiding builting system.methodSignature with hook_xmlrpc_alter works'); } + /** + * Test limits on system.multicall that can prevent brute-force attacks. + */ + function testMulticallLimit() { + $url = url(NULL, array('absolute' => TRUE)) . 'xmlrpc.php'; + $multicall_args = array(); + $num_method_calls = 10; + for ($i = 0; $i < $num_method_calls; $i++) { + $struct = array('i' => $i); + $multicall_args[] = array('methodName' => 'validator1.echoStructTest', 'params' => array($struct)); + } + // Test limits of 1, 5, 9, 13. + for ($limit = 1; $limit < $num_method_calls + 4; $limit += 4) { + variable_set('xmlrpc_multicall_duplicate_method_limit', $limit); + $results = xmlrpc($url, array('system.multicall' => array($multicall_args))); + $this->assertEqual($num_method_calls, count($results)); + for ($i = 0; $i < min($limit, $num_method_calls); $i++) { + $x = array_shift($results); + $this->assertTrue(empty($x->is_error), "Result $i is not an error"); + $this->assertEqual($multicall_args[$i]['params'][0], $x); + } + for (; $i < $num_method_calls; $i++) { + $x = array_shift($results); + $this->assertFalse(empty($x->is_error), "Result $i is an error"); + $this->assertEqual(-156579, $x->code); + } + } + variable_set('xmlrpc_multicall_duplicate_method_limit', -1); + $results = xmlrpc($url, array('system.multicall' => array($multicall_args))); + $this->assertEqual($num_method_calls, count($results)); + foreach ($results as $i => $x) { + $this->assertTrue(empty($x->is_error), "Result $i is not an error"); + } + } } diff -Naur drupal-7.41/modules/simpletest/tests/xmlrpc_test.info drupal-7.66/modules/simpletest/tests/xmlrpc_test.info --- drupal-7.41/modules/simpletest/tests/xmlrpc_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/simpletest/tests/xmlrpc_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/statistics/statistics.info drupal-7.66/modules/statistics/statistics.info --- drupal-7.41/modules/statistics/statistics.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/statistics/statistics.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ files[] = statistics.test configure = admin/config/system/statistics -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/statistics/statistics.module drupal-7.66/modules/statistics/statistics.module --- drupal-7.41/modules/statistics/statistics.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/statistics/statistics.module 2019-04-17 22:20:46.000000000 +0200 @@ -245,7 +245,7 @@ * Implements hook_cron(). */ function statistics_cron() { - $statistics_timestamp = variable_get('statistics_day_timestamp', ''); + $statistics_timestamp = variable_get('statistics_day_timestamp', 0); if ((REQUEST_TIME - $statistics_timestamp) >= 86400) { // Reset day counts. diff -Naur drupal-7.41/modules/statistics/statistics.test drupal-7.66/modules/statistics/statistics.test --- drupal-7.41/modules/statistics/statistics.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/statistics/statistics.test 2019-04-17 22:20:46.000000000 +0200 @@ -35,7 +35,7 @@ 'title' => 'test', 'path' => 'node/1', 'url' => 'http://example.com', - 'hostname' => '192.168.1.1', + 'hostname' => '1.2.3.3', 'uid' => 0, 'sid' => 10, 'timer' => 10, @@ -268,7 +268,7 @@ */ function testIPAddressBlocking() { // IP address for testing. - $test_ip_address = '192.168.1.1'; + $test_ip_address = '1.2.3.3'; // Verify the IP address from accesslog appears on the top visitors page // and that a 'block IP address' link is displayed. diff -Naur drupal-7.41/modules/syslog/syslog.info drupal-7.66/modules/syslog/syslog.info --- drupal-7.41/modules/syslog/syslog.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/syslog/syslog.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ files[] = syslog.test configure = admin/config/development/logging -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/system/image.gd.inc drupal-7.66/modules/system/image.gd.inc --- drupal-7.41/modules/system/image.gd.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/system/image.gd.inc 2019-04-17 22:20:46.000000000 +0200 @@ -116,38 +116,62 @@ return FALSE; } - $width = $image->info['width']; - $height = $image->info['height']; + // PHP 5.5 GD bug: https://bugs.php.net/bug.php?id=65148: To prevent buggy + // behavior on negative multiples of 90 degrees we convert any negative + // angle to a positive one between 0 and 360 degrees. + $degrees -= floor($degrees / 360) * 360; - // Convert the hexadecimal background value to a color index value. + // Convert the hexadecimal background value to a RGBA array. if (isset($background)) { - $rgb = array(); - for ($i = 16; $i >= 0; $i -= 8) { - $rgb[] = (($background >> $i) & 0xFF); - } - $background = imagecolorallocatealpha($image->resource, $rgb[0], $rgb[1], $rgb[2], 0); + $background = array( + 'red' => $background >> 16 & 0xFF, + 'green' => $background >> 8 & 0xFF, + 'blue' => $background & 0xFF, + 'alpha' => 0, + ); } - // Set the background color as transparent if $background is NULL. else { - // Get the current transparent color. - $background = imagecolortransparent($image->resource); - - // If no transparent colors, use white. - if ($background == 0) { - $background = imagecolorallocatealpha($image->resource, 255, 255, 255, 0); - } + // Background color is not specified: use transparent white as background. + $background = array( + 'red' => 255, + 'green' => 255, + 'blue' => 255, + 'alpha' => 127 + ); } + // Store the color index for the background as that is what GD uses. + $background_idx = imagecolorallocatealpha($image->resource, $background['red'], $background['green'], $background['blue'], $background['alpha']); + // Images are assigned a new color palette when rotating, removing any // transparency flags. For GIF images, keep a record of the transparent color. if ($image->info['extension'] == 'gif') { - $transparent_index = imagecolortransparent($image->resource); - if ($transparent_index != 0) { - $transparent_gif_color = imagecolorsforindex($image->resource, $transparent_index); + // GIF does not work with a transparency channel, but can define 1 color + // in its palette to act as transparent. + + // Get the current transparent color, if any. + $gif_transparent_id = imagecolortransparent($image->resource); + if ($gif_transparent_id !== -1) { + // The gif already has a transparent color set: remember it to set it on + // the rotated image as well. + $transparent_gif_color = imagecolorsforindex($image->resource, $gif_transparent_id); + + if ($background['alpha'] >= 127) { + // We want a transparent background: use the color already set to act + // as transparent, as background. + $background_idx = $gif_transparent_id; + } + } + else { + // The gif does not currently have a transparent color set. + if ($background['alpha'] >= 127) { + // But as the background is transparent, it should get one. + $transparent_gif_color = $background; + } } } - $image->resource = imagerotate($image->resource, 360 - $degrees, $background); + $image->resource = imagerotate($image->resource, 360 - $degrees, $background_idx); // GIFs need to reassign the transparent color after performing the rotate. if (isset($transparent_gif_color)) { diff -Naur drupal-7.41/modules/system/system.admin.inc drupal-7.66/modules/system/system.admin.inc --- drupal-7.41/modules/system/system.admin.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/system/system.admin.inc 2019-04-17 22:20:46.000000000 +0200 @@ -572,9 +572,10 @@ // Process the theme and all its base themes. foreach ($theme_keys as $theme) { // Include the theme-settings.php file. - $filename = DRUPAL_ROOT . '/' . str_replace("/$theme.info", '', $themes[$theme]->filename) . '/theme-settings.php'; - if (file_exists($filename)) { - require_once $filename; + $theme_settings_path = drupal_get_path('theme', $theme) . '/theme-settings.php'; + if (file_exists(DRUPAL_ROOT . '/' . $theme_settings_path)) { + require_once DRUPAL_ROOT . '/' . $theme_settings_path; + $form_state['build_info']['files'][] = $theme_settings_path; } // Call theme-specific settings. @@ -1812,7 +1813,7 @@ '#title' => t('Private file system path'), '#default_value' => variable_get('file_private_path', ''), '#maxlength' => 255, - '#description' => t('An existing local file system path for storing private files. It should be writable by Drupal and not accessible over the web. See the online handbook for <a href="@handbook">more information about securing private files</a>.', array('@handbook' => 'http://drupal.org/documentation/modules/file')), + '#description' => t('An existing local file system path for storing private files. It should be writable by Drupal and not accessible over the web. See the online handbook for <a href="@handbook">more information about securing private files</a>.', array('@handbook' => 'https://www.drupal.org/docs/7/core/modules/file/overview')), '#after_build' => array('system_check_directory'), ); @@ -1856,7 +1857,7 @@ if (count($toolkits_available) == 0) { variable_del('image_toolkit'); $form['image_toolkit_help'] = array( - '#markup' => t("No image toolkits were detected. Drupal includes support for <a href='!gd-link'>PHP's built-in image processing functions</a> but they were not detected on this system. You should consult your system administrator to have them enabled, or try using a third party toolkit.", array('gd-link' => url('http://php.net/gd'))), + '#markup' => t("No image toolkits were detected. Drupal includes support for <a href='!gd-link'>PHP's built-in image processing functions</a> but they were not detected on this system. You should consult your system administrator to have them enabled, or try using a third party toolkit.", array('!gd-link' => url('http://php.net/gd'))), ); return $form; } @@ -2202,6 +2203,11 @@ * Return the date for a given format string via Ajax. */ function system_date_time_lookup() { + // This callback is protected with a CSRF token because user input from the + // query string is reflected in the output. + if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'admin/config/regional/date-time/formats/lookup')) { + return MENU_ACCESS_DENIED; + } $result = format_date(REQUEST_TIME, 'custom', $_GET['format']); drupal_json_output($result); } @@ -2560,9 +2566,21 @@ /** * Returns HTML for the status report. * + * This theme function is dependent on install.inc being loaded, because + * that's where the constants are defined. + * * @param $variables * An associative array containing: - * - requirements: An array of requirements. + * - requirements: An array of requirements/status items. Each requirement + * is an associative array containing the following elements: + * - title: The name of the requirement. + * - value: (optional) The current value (version, time, level, etc). + * - description: (optional) The description of the requirement. + * - severity: (optional) The requirement's result/severity level, one of: + * - REQUIREMENT_INFO: Status information. + * - REQUIREMENT_OK: The requirement is satisfied. + * - REQUIREMENT_WARNING: The requirement failed with a warning. + * - REQUIREMENT_ERROR: The requirement failed with an error. * * @ingroup themeable */ @@ -2592,6 +2610,8 @@ if (empty($requirement['#type'])) { $severity = $severities[isset($requirement['severity']) ? (int) $requirement['severity'] : REQUIREMENT_OK]; $severity['icon'] = '<div title="' . $severity['title'] . '"><span class="element-invisible">' . $severity['title'] . '</span></div>'; + // The requirement's 'value' key is optional, provide a default value. + $requirement['value'] = isset($requirement['value']) ? $requirement['value'] : ''; // Output table row(s) if (!empty($requirement['description'])) { @@ -2875,13 +2895,14 @@ * Allow users to add additional date formats. */ function system_configure_date_formats_form($form, &$form_state, $dfid = 0) { + $ajax_path = 'admin/config/regional/date-time/formats/lookup'; $js_settings = array( 'type' => 'setting', 'data' => array( 'dateTime' => array( 'date-format' => array( 'text' => t('Displayed as'), - 'lookup' => url('admin/config/regional/date-time/formats/lookup'), + 'lookup' => url($ajax_path, array('query' => array('token' => drupal_get_token($ajax_path)))), ), ), ), diff -Naur drupal-7.41/modules/system/system.api.php drupal-7.66/modules/system/system.api.php --- drupal-7.41/modules/system/system.api.php 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/system/system.api.php 2019-04-17 22:20:46.000000000 +0200 @@ -1797,6 +1797,8 @@ * the $form_id input matched your module's format for dynamically-generated * form IDs, and if so, act appropriately. * + * Third, forms defined in classes can be defined this way. + * * @param $form_id * The unique string identifying the desired form. * @param $args @@ -1807,19 +1809,22 @@ * @return * An associative array whose keys define form_ids and whose values are an * associative array defining the following keys: - * - callback: The name of the form builder function to invoke. This will be - * used for the base form ID, for example, to target a base form using - * hook_form_BASE_FORM_ID_alter(). + * - callback: The callable returning the form array. If it is the name of + * the form builder function then this will be used for the base + * form ID, for example, to target a base form using + * hook_form_BASE_FORM_ID_alter(). Otherwise use the base_form_id key to + * define the base form ID. * - callback arguments: (optional) Additional arguments to pass to the * function defined in 'callback', which are prepended to $args. - * - wrapper_callback: (optional) The name of a form builder function to - * invoke before the form builder defined in 'callback' is invoked. This - * wrapper callback may prepopulate the $form array with form elements, - * which will then be already contained in the $form that is passed on to - * the form builder defined in 'callback'. For example, a wrapper callback - * could setup wizard-alike form buttons that are the same for a variety of - * forms that belong to the wizard, which all share the same wrapper - * callback. + * - base_form_id: The base form ID can be specified explicitly. This is + * required when callback is not the name of a function. + * - wrapper_callback: (optional) Any callable to invoke before the form + * builder defined in 'callback' is invoked. This wrapper callback may + * prepopulate the $form array with form elements, which will then be + * already contained in the $form that is passed on to the form builder + * defined in 'callback'. For example, a wrapper callback could setup + * wizard-like form buttons that are the same for a variety of forms that + * belong to the wizard, which all share the same wrapper callback. */ function hook_forms($form_id, $args) { // Simply reroute the (non-existing) $form_id 'mymodule_first_form' to @@ -1843,6 +1848,15 @@ 'wrapper_callback' => 'mymodule_main_form_wrapper', ); + // Build a form with a static class callback. + $forms['mymodule_class_generated_form'] = array( + // This will call: MyClass::generateMainForm(). + 'callback' => array('MyClass', 'generateMainForm'), + // The base_form_id is required when the callback is a static function in + // a class. This can also be used to keep newer code backwards compatible. + 'base_form_id' => 'mymodule_main_form', + ); + return $forms; } @@ -1874,8 +1888,8 @@ * * This hook is not run on cached pages. * - * To add CSS or JS that should be present on all pages, modules should not - * implement this hook, but declare these files in their .info file. + * To add CSS or JS files that should be present on all pages, modules should + * not implement this hook, but declare these files in their .info file. * * @see hook_boot() */ @@ -2037,6 +2051,22 @@ } /** + * Return additional theme engines provided by modules. + * + * This hook is invoked from _system_rebuild_theme_data() and allows modules to + * register additional theme engines outside of the regular 'themes/engines' + * directories of a Drupal installation. + * + * @return + * An associative array. Each key is the system name of a theme engine and + * each value is the corresponding path to the theme engine's .engine file. + */ +function hook_system_theme_engine_info() { + $theme_engines['izumi'] = drupal_get_path('module', 'mymodule') . '/izumi/izumi.engine'; + return $theme_engines; +} + +/** * Alter the information parsed from module and theme .info files * * This hook is invoked in _system_rebuild_module_data() and in diff -Naur drupal-7.41/modules/system/system.info drupal-7.66/modules/system/system.info --- drupal-7.41/modules/system/system.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/system/system.info 2019-04-17 22:39:36.000000000 +0200 @@ -12,8 +12,7 @@ required = TRUE configure = admin/config/system -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/system/system.install drupal-7.66/modules/system/system.install --- drupal-7.41/modules/system/system.install 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/system/system.install 2019-04-17 22:20:46.000000000 +0200 @@ -160,7 +160,7 @@ if (empty($drivers)) { $database_ok = FALSE; $pdo_message = $t('Your web server does not appear to support any common PDO database extensions. Check with your hosting provider to see if they support PDO (PHP Data Objects) and offer any databases that <a href="@drupal-databases">Drupal supports</a>.', array( - '@drupal-databases' => 'http://drupal.org/node/270#database', + '@drupal-databases' => 'https://www.drupal.org/requirements/database', )); } // Make sure the native PDO extension is available, not the older PEAR @@ -196,6 +196,12 @@ ); } + // Test database-specific multi-byte UTF-8 related requirements. + $charset_requirements = _system_check_db_utf8mb4_requirements($phase); + if (!empty($charset_requirements)) { + $requirements['database_charset'] = $charset_requirements; + } + // Test PHP memory_limit $memory_limit = ini_get('memory_limit'); $requirements['php_memory_limit'] = array( @@ -518,6 +524,75 @@ } /** + * Checks whether the requirements for multi-byte UTF-8 support are met. + * + * @param string $phase + * The hook_requirements() stage. + * + * @return array + * A requirements array with the result of the charset check. + */ +function _system_check_db_utf8mb4_requirements($phase) { + global $install_state; + // In the requirements check of the installer, skip the utf8mb4 check unless + // the database connection info has been preconfigured by hand with valid + // information before running the installer, as otherwise we cannot get a + // valid database connection object. + if (isset($install_state['settings_verified']) && !$install_state['settings_verified']) { + return array(); + } + + $connection = Database::getConnection(); + $t = get_t(); + $requirements['title'] = $t('Database 4 byte UTF-8 support'); + + $utf8mb4_configurable = $connection->utf8mb4IsConfigurable(); + $utf8mb4_active = $connection->utf8mb4IsActive(); + $utf8mb4_supported = $connection->utf8mb4IsSupported(); + $driver = $connection->driver(); + $documentation_url = 'https://www.drupal.org/node/2754539'; + + if ($utf8mb4_active) { + if ($utf8mb4_supported) { + if ($phase != 'install' && $utf8mb4_configurable && !variable_get('drupal_all_databases_are_utf8mb4', FALSE)) { + // Supported, active, and configurable, but not all database tables + // have been converted yet. + $requirements['value'] = $t('Enabled, but database tables need conversion'); + $requirements['description'] = $t('Please convert all database tables to utf8mb4 prior to enabling it in settings.php. See the <a href="@url">documentation on adding 4 byte UTF-8 support</a> for more information.', array('@url' => $documentation_url)); + $requirements['severity'] = REQUIREMENT_ERROR; + } + else { + // Supported, active. + $requirements['value'] = $t('Enabled'); + $requirements['description'] = $t('4 byte UTF-8 for @driver is enabled.', array('@driver' => $driver)); + $requirements['severity'] = REQUIREMENT_OK; + } + } + else { + // Not supported, active. + $requirements['value'] = $t('Not supported'); + $requirements['description'] = $t('4 byte UTF-8 for @driver is activated, but not supported on your system. Please turn this off in settings.php, or ensure that all database-related requirements are met. See the <a href="@url">documentation on adding 4 byte UTF-8 support</a> for more information.', array('@driver' => $driver, '@url' => $documentation_url)); + $requirements['severity'] = REQUIREMENT_ERROR; + } + } + else { + if ($utf8mb4_supported) { + // Supported, not active. + $requirements['value'] = $t('Not enabled'); + $requirements['description'] = $t('4 byte UTF-8 for @driver is not activated, but it is supported on your system. It is recommended that you enable this to allow 4-byte UTF-8 input such as emojis, Asian symbols and mathematical symbols to be stored correctly. See the <a href="@url">documentation on adding 4 byte UTF-8 support</a> for more information.', array('@driver' => $driver, '@url' => $documentation_url)); + $requirements['severity'] = REQUIREMENT_INFO; + } + else { + // Not supported, not active. + $requirements['value'] = $t('Disabled'); + $requirements['description'] = $t('4 byte UTF-8 for @driver is disabled. See the <a href="@url">documentation on adding 4 byte UTF-8 support</a> for more information.', array('@driver' => $driver, '@url' => $documentation_url)); + $requirements['severity'] = REQUIREMENT_INFO; + } + } + return $requirements; +} + +/** * Implements hook_install(). */ function system_install() { @@ -532,6 +607,9 @@ module_list(TRUE); module_implements('', FALSE, TRUE); + // Ensure the schema versions are not based on a previous module list. + drupal_static_reset('drupal_get_schema_versions'); + // Load system theme data appropriately. system_rebuild_theme_data(); @@ -2792,7 +2870,7 @@ if (!db_table_exists('system_update_7061')) { $table = array( 'description' => t('Stores temporary data for system_update_7061.'), - 'fields' => array('vid' => array('type' => 'int')), + 'fields' => array('vid' => array('type' => 'int', 'not null' => TRUE)), 'primary key' => array('vid'), ); db_create_table('system_update_7061', $table); @@ -3193,6 +3271,28 @@ } /** + * Remove the Drupal 6 default install profile if it is still in the database. + */ +function system_update_7081() { + // Sites which used the default install profile in Drupal 6 and then updated + // to Drupal 7.44 or earlier will still have a record of this install profile + // in the database that needs to be deleted. + db_delete('system') + ->condition('filename', 'profiles/default/default.profile') + ->condition('type', 'module') + ->condition('status', 0) + ->condition('schema_version', 0) + ->execute(); +} + +/** + * Add 'jquery-extend-3.4.0.js' to the 'jquery' library. + */ +function system_update_7082() { + // Empty update to force a rebuild of hook_library() and JS aggregates. +} + +/** * @} End of "defgroup updates-7.x-extra". * The next series of updates should start at 8000. */ diff -Naur drupal-7.41/modules/system/system.js drupal-7.66/modules/system/system.js --- drupal-7.41/modules/system/system.js 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/system/system.js 2019-04-17 22:20:46.000000000 +0200 @@ -105,7 +105,7 @@ // Attach keyup handler to custom format inputs. $('input' + source, context).once('date-time').keyup(function () { var input = $(this); - var url = fieldSettings.lookup + (/\?q=/.test(fieldSettings.lookup) ? '&format=' : '?format=') + encodeURIComponent(input.val()); + var url = fieldSettings.lookup + (/\?/.test(fieldSettings.lookup) ? '&format=' : '?format=') + encodeURIComponent(input.val()); $.getJSON(url, function (data) { $(suffix).empty().append(' ' + fieldSettings.text + ': <em>' + data + '</em>'); }); diff -Naur drupal-7.41/modules/system/system.mail.inc drupal-7.66/modules/system/system.mail.inc --- drupal-7.41/modules/system/system.mail.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/system/system.mail.inc 2019-04-17 22:20:46.000000000 +0200 @@ -70,7 +70,9 @@ // hosts. The return value of this method will still indicate whether mail // was sent successfully. if (!isset($_SERVER['WINDIR']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Win32') === FALSE) { - if (isset($message['Return-Path']) && !ini_get('safe_mode')) { + // We validate the return path, unless it is equal to the site mail, which + // we assume to be safe. + if (isset($message['Return-Path']) && !ini_get('safe_mode') && (variable_get('site_mail', ini_get('sendmail_from')) === $message['Return-Path'] || self::_isShellSafe($message['Return-Path']))) { // On most non-Windows systems, the "-f" option to the sendmail command // is used to set the Return-Path. There is no space between -f and // the value of the return path. @@ -109,6 +111,36 @@ } return $mail_result; } + + /** + * Disallows potentially unsafe shell characters. + * + * Functionally similar to PHPMailer::isShellSafe() which resulted from + * CVE-2016-10045. Note that escapeshellarg and escapeshellcmd are inadequate + * for this purpose. + * + * @param string $string + * The string to be validated. + * + * @return bool + * True if the string is shell-safe. + * + * @see https://github.com/PHPMailer/PHPMailer/issues/924 + * @see https://github.com/PHPMailer/PHPMailer/blob/v5.2.21/class.phpmailer.php#L1430 + * + * @todo Rename to ::isShellSafe() and/or discuss whether this is the correct + * location for this helper. + */ + protected static function _isShellSafe($string) { + if (escapeshellcmd($string) !== $string || !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))) { + return FALSE; + } + if (preg_match('/[^a-zA-Z0-9@_\-.]/', $string) !== 0) { + return FALSE; + } + return TRUE; + } + } /** diff -Naur drupal-7.41/modules/system/system.module drupal-7.66/modules/system/system.module --- drupal-7.41/modules/system/system.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/system/system.module 2019-04-17 22:20:46.000000000 +0200 @@ -1182,6 +1182,9 @@ 'version' => '1.4.4', 'js' => array( 'misc/jquery.js' => array('group' => JS_LIBRARY, 'weight' => -20), + // This includes a security fix, so assign a weight that makes this load + // as soon after jquery.js is loaded as possible. + 'misc/jquery-extend-3.4.0.js' => array('group' => JS_LIBRARY, 'weight' => -19), ), ); @@ -2521,6 +2524,16 @@ // Find theme engines $engines = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.engine$/', 'themes/engines'); + // Allow modules to add further theme engines. + if ($module_engines = module_invoke_all('system_theme_engine_info')) { + foreach ($module_engines as $name => $theme_engine_path) { + $engines[$name] = (object) array( + 'uri' => $theme_engine_path, + 'filename' => basename($theme_engine_path), + 'name' => $name, + ); + } + } // Set defaults for theme info. $defaults = array( @@ -2705,10 +2718,17 @@ * @param $show * Possible values: REGIONS_ALL or REGIONS_VISIBLE. Visible excludes hidden * regions. - * @return - * An array of regions in the form $region['name'] = 'description'. + * @param bool $labels + * (optional) Boolean to specify whether the human readable machine names + * should be returned or not. Defaults to TRUE, but calling code can set + * this to FALSE for better performance, if it only needs machine names. + * + * @return array + * An associative array of regions in the form $region['name'] = 'description' + * if $labels is set to TRUE, or $region['name'] = 'name', if $labels is set + * to FALSE. */ -function system_region_list($theme_key, $show = REGIONS_ALL) { +function system_region_list($theme_key, $show = REGIONS_ALL, $labels = TRUE) { $themes = list_themes(); if (!isset($themes[$theme_key])) { return array(); @@ -2719,10 +2739,14 @@ // If requested, suppress hidden regions. See block_admin_display_form(). foreach ($info['regions'] as $name => $label) { if ($show == REGIONS_ALL || !isset($info['regions_hidden']) || !in_array($name, $info['regions_hidden'])) { - $list[$name] = t($label); + if ($labels) { + $list[$name] = t($label); + } + else { + $list[$name] = $name; + } } } - return $list; } @@ -2743,12 +2767,13 @@ * * @param $theme * The name of a theme. + * * @return * A string that is the region name. */ function system_default_region($theme) { - $regions = array_keys(system_region_list($theme, REGIONS_VISIBLE)); - return isset($regions[0]) ? $regions[0] : ''; + $regions = system_region_list($theme, REGIONS_VISIBLE, FALSE); + return $regions ? reset($regions) : ''; } /** @@ -2871,7 +2896,7 @@ // Prepare cancel link. if (isset($_GET['destination'])) { - $options = drupal_parse_url(urldecode($_GET['destination'])); + $options = drupal_parse_url($_GET['destination']); } elseif (is_array($path)) { $options = $path; @@ -3056,8 +3081,20 @@ } } - $core = array('cache', 'cache_path', 'cache_filter', 'cache_page', 'cache_form', 'cache_menu'); - $cache_tables = array_merge(module_invoke_all('flush_caches'), $core); + // Delete expired cache entries. + // Avoid invoking hook_flush_cashes() on every cron run because some modules + // use this hook to perform expensive rebuilding operations (which are only + // designed to happen on full cache clears), rather than just returning a + // list of cache tables to be cleared. + $cache_object = cache_get('system_cache_tables'); + if (empty($cache_object)) { + $core = array('cache', 'cache_path', 'cache_filter', 'cache_page', 'cache_form', 'cache_menu'); + $cache_tables = array_merge(module_invoke_all('flush_caches'), $core); + cache_set('system_cache_tables', $cache_tables); + } + else { + $cache_tables = $cache_object->data; + } foreach ($cache_tables as $table) { cache_clear_all(NULL, $table); } @@ -3305,7 +3342,7 @@ $form['url'] = array( '#type' => 'textfield', '#title' => t('URL'), - '#description' => t('The URL to which the user should be redirected. This can be an internal URL like node/1234 or an external URL like http://drupal.org.'), + '#description' => t('The URL to which the user should be redirected. This can be an internal path like node/1234 or an external URL like http://example.com.'), '#default_value' => isset($context['url']) ? $context['url'] : '', '#required' => TRUE, ); @@ -3342,7 +3379,8 @@ */ function system_block_ip_action() { $ip = ip_address(); - db_insert('blocked_ips') + db_merge('blocked_ips') + ->key(array('ip' => $ip)) ->fields(array('ip' => $ip)) ->execute(); watchdog('action', 'Banned IP address %ip', array('%ip' => $ip)); @@ -3504,8 +3542,7 @@ function system_page_alter(&$page) { // Find all non-empty page regions, and add a theme wrapper function that // allows them to be consistently themed. - $regions = system_region_list($GLOBALS['theme']); - foreach (array_keys($regions) as $region) { + foreach (system_region_list($GLOBALS['theme'], REGIONS_ALL, FALSE) as $region) { if (!empty($page[$region])) { $page[$region]['#theme_wrappers'][] = 'region'; $page[$region]['#region'] = $region; diff -Naur drupal-7.41/modules/system/system.queue.inc drupal-7.66/modules/system/system.queue.inc --- drupal-7.41/modules/system/system.queue.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/system/system.queue.inc 2019-04-17 22:20:46.000000000 +0200 @@ -326,6 +326,7 @@ $item->created = time(); $item->expire = 0; $this->queue[$item->item_id] = $item; + return TRUE; } public function numberOfItems() { diff -Naur drupal-7.41/modules/system/system.tar.inc drupal-7.66/modules/system/system.tar.inc --- drupal-7.41/modules/system/system.tar.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/system/system.tar.inc 2019-04-17 22:20:46.000000000 +0200 @@ -30,81 +30,148 @@ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * - * @category File_Formats - * @package Archive_Tar - * @author Vincent Blavet <vincent@phpconcept.net> - * @copyright 1997-2008 The Authors - * @license http://www.opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: Id: Tar.php,v 1.43 2008/10/30 17:58:42 dufuz Exp - * @link http://pear.php.net/package/Archive_Tar + * @category File_Formats + * @package Archive_Tar + * @author Vincent Blavet <vincent@phpconcept.net> + * @copyright 1997-2010 The Authors + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/Archive_Tar + */ + + /** + * Note on Drupal 8 porting. + * This file origin is Tar.php, release 1.4.5 (stable) with some code + * from PEAR.php, release 1.10.5 (stable) both at http://pear.php.net. + * To simplify future porting from pear of this file, you should not + * do cosmetic or other non significant changes to this file. + * The following changes have been done: + * Added namespace Drupal\Core\Archiver. + * Removed require_once 'PEAR.php'. + * Added defintion of OS_WINDOWS taken from PEAR.php. + * Renamed class to ArchiveTar. + * Removed extends PEAR from class. + * Removed call parent:: __construct(). + * Changed PEAR::loadExtension($extname) to this->loadExtension($extname). + * Added function loadExtension() taken from PEAR.php. + * Changed all calls of unlink() to drupal_unlink(). + * Changed $this->error_object = &$this->raiseError($p_message) + * to throw new \Exception($p_message). */ -//require_once 'PEAR.php'; -// -// -define ('ARCHIVE_TAR_ATT_SEPARATOR', 90001); -define ('ARCHIVE_TAR_END_BLOCK', pack("a512", '')); + /** + * Note on Drupal 7 backporting from Drupal 8. + * File origin is core/lib/Drupal/Core/Archiver/ArchiveTar.php from Drupal 8. + * The following changes have been done: + * Removed namespace Drupal\Core\Archiver. + * Renamed class to Archive_Tar. + * Changed \Exception to Exception. + */ + + +// Drupal removal require_once 'PEAR.php'. + +// Drupal addition OS_WINDOWS as defined in PEAR.php. +if (substr(PHP_OS, 0, 3) == 'WIN') { + define('OS_WINDOWS', true); +} else { + define('OS_WINDOWS', false); +} + +define('ARCHIVE_TAR_ATT_SEPARATOR', 90001); +define('ARCHIVE_TAR_END_BLOCK', pack("a512", '')); + +if (!function_exists('gzopen') && function_exists('gzopen64')) { + function gzopen($filename, $mode, $use_include_path = 0) + { + return gzopen64($filename, $mode, $use_include_path); + } +} + +if (!function_exists('gztell') && function_exists('gztell64')) { + function gztell($zp) + { + return gztell64($zp); + } +} + +if (!function_exists('gzseek') && function_exists('gzseek64')) { + function gzseek($zp, $offset, $whence = SEEK_SET) + { + return gzseek64($zp, $offset, $whence); + } +} /** -* Creates a (compressed) Tar archive -* -* @author Vincent Blavet <vincent@phpconcept.net> -* @version Revision: 1.43 -* @license http://www.opensource.org/licenses/bsd-license.php New BSD License -* @package Archive_Tar -*/ -class Archive_Tar // extends PEAR + * Creates a (compressed) Tar archive + * + * @package Archive_Tar + * @author Vincent Blavet <vincent@phpconcept.net> + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version $Revision$ + */ +// Drupal change class Archive_Tar extends PEAR. +class Archive_Tar { /** - * @var string Name of the Tar - */ - var $_tarname=''; + * @var string Name of the Tar + */ + public $_tarname = ''; /** - * @var boolean if true, the Tar file will be gzipped - */ - var $_compress=false; + * @var boolean if true, the Tar file will be gzipped + */ + public $_compress = false; /** - * @var string Type of compression : 'none', 'gz' or 'bz2' - */ - var $_compress_type='none'; + * @var string Type of compression : 'none', 'gz', 'bz2' or 'lzma2' + */ + public $_compress_type = 'none'; /** - * @var string Explode separator - */ - var $_separator=' '; + * @var string Explode separator + */ + public $_separator = ' '; /** - * @var file descriptor - */ - var $_file=0; + * @var file descriptor + */ + public $_file = 0; /** - * @var string Local Tar name of a remote Tar (http:// or ftp://) - */ - var $_temp_tarname=''; + * @var string Local Tar name of a remote Tar (http:// or ftp://) + */ + public $_temp_tarname = ''; - // {{{ constructor /** - * Archive_Tar Class constructor. This flavour of the constructor only - * declare a new Archive_Tar object, identifying it by the name of the - * tar file. - * If the compress argument is set the tar will be read or created as a - * gzip or bz2 compressed TAR file. - * - * @param string $p_tarname The name of the tar archive to create - * @param string $p_compress can be null, 'gz' or 'bz2'. This - * parameter indicates if gzip or bz2 compression - * is required. For compatibility reason the - * boolean value 'true' means 'gz'. - * @access public - */ -// function Archive_Tar($p_tarname, $p_compress = null) - function __construct($p_tarname, $p_compress = null) + * @var string regular expression for ignoring files or directories + */ + public $_ignore_regexp = ''; + + /** + * @var object PEAR_Error object + */ + public $error_object = null; + + /** + * Archive_Tar Class constructor. This flavour of the constructor only + * declare a new Archive_Tar object, identifying it by the name of the + * tar file. + * If the compress argument is set the tar will be read or created as a + * gzip or bz2 compressed TAR file. + * + * @param string $p_tarname The name of the tar archive to create + * @param string $p_compress can be null, 'gz', 'bz2' or 'lzma2'. This + * parameter indicates if gzip, bz2 or lzma2 compression + * is required. For compatibility reason the + * boolean value 'true' means 'gz'. + * + * @return bool + */ + public function __construct($p_tarname, $p_compress = null) { -// $this->PEAR(); + // Drupal removal parent::__construct(). + $this->_compress = false; $this->_compress_type = 'none'; if (($p_compress === null) || ($p_compress == '')) { @@ -116,10 +183,13 @@ if ($data == "\37\213") { $this->_compress = true; $this->_compress_type = 'gz'; - // No sure it's enought for a magic code .... + // No sure it's enought for a magic code .... } elseif ($data == "BZ") { $this->_compress = true; $this->_compress_type = 'bz2'; + } elseif (file_get_contents($p_tarname, false, null, 1, 4) == '7zXZ') { + $this->_compress = true; + $this->_compress_type = 'lzma2'; } } } else { @@ -129,151 +199,189 @@ $this->_compress = true; $this->_compress_type = 'gz'; } elseif ((substr($p_tarname, -3) == 'bz2') || - (substr($p_tarname, -2) == 'bz')) { + (substr($p_tarname, -2) == 'bz') + ) { $this->_compress = true; $this->_compress_type = 'bz2'; + } else { + if (substr($p_tarname, -2) == 'xz') { + $this->_compress = true; + $this->_compress_type = 'lzma2'; + } } } } else { if (($p_compress === true) || ($p_compress == 'gz')) { $this->_compress = true; $this->_compress_type = 'gz'; - } else if ($p_compress == 'bz2') { - $this->_compress = true; - $this->_compress_type = 'bz2'; } else { - die("Unsupported compression type '$p_compress'\n". - "Supported types are 'gz' and 'bz2'.\n"); - return false; + if ($p_compress == 'bz2') { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } else { + if ($p_compress == 'lzma2') { + $this->_compress = true; + $this->_compress_type = 'lzma2'; + } else { + $this->_error( + "Unsupported compression type '$p_compress'\n" . + "Supported types are 'gz', 'bz2' and 'lzma2'.\n" + ); + return false; + } + } } } $this->_tarname = $p_tarname; - if ($this->_compress) { // assert zlib or bz2 extension support - if ($this->_compress_type == 'gz') + if ($this->_compress) { // assert zlib or bz2 or xz extension support + if ($this->_compress_type == 'gz') { $extname = 'zlib'; - else if ($this->_compress_type == 'bz2') - $extname = 'bz2'; + } else { + if ($this->_compress_type == 'bz2') { + $extname = 'bz2'; + } else { + if ($this->_compress_type == 'lzma2') { + $extname = 'xz'; + } + } + } if (!extension_loaded($extname)) { -// PEAR::loadExtension($extname); + // Drupal change PEAR::loadExtension($extname). $this->loadExtension($extname); } if (!extension_loaded($extname)) { - die("The extension '$extname' couldn't be found.\n". - "Please make sure your version of PHP was built ". - "with '$extname' support.\n"); + $this->_error( + "The extension '$extname' couldn't be found.\n" . + "Please make sure your version of PHP was built " . + "with '$extname' support.\n" + ); return false; } } + + + if (version_compare(PHP_VERSION, "5.5.0-dev") < 0) { + $this->_fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" . + "a8checksum/a1typeflag/a100link/a6magic/a2version/" . + "a32uname/a32gname/a8devmajor/a8devminor/a131prefix"; + } else { + $this->_fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" . + "Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" . + "Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix"; + } + + } - // }}} + public function __destruct() + { + $this->_close(); + // ----- Look for a local copy to delete + if ($this->_temp_tarname != '') { + @drupal_unlink($this->_temp_tarname); + } + } + + // Drupal addition from PEAR.php. /** * OS independent PHP extension load. Remember to take care * on the correct extension name for case sensitive OSes. - * The function is the copy of PEAR::loadExtension(). * * @param string $ext The extension name * @return bool Success or not on the dl() call */ - function loadExtension($ext) + public static function loadExtension($ext) { - if (!extension_loaded($ext)) { - // if either returns true dl() will produce a FATAL error, stop that - if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) { - return false; - } + if (extension_loaded($ext)) { + return true; + } - if (OS_WINDOWS) { - $suffix = '.dll'; - } elseif (PHP_OS == 'HP-UX') { - $suffix = '.sl'; - } elseif (PHP_OS == 'AIX') { - $suffix = '.a'; - } elseif (PHP_OS == 'OSX') { - $suffix = '.bundle'; - } else { - $suffix = '.so'; - } + // if either returns true dl() will produce a FATAL error, stop that + if ( + function_exists('dl') === false || + ini_get('enable_dl') != 1 + ) { + return false; + } - return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + if (OS_WINDOWS) { + $suffix = '.dll'; + } elseif (PHP_OS == 'HP-UX') { + $suffix = '.sl'; + } elseif (PHP_OS == 'AIX') { + $suffix = '.a'; + } elseif (PHP_OS == 'OSX') { + $suffix = '.bundle'; + } else { + $suffix = '.so'; } - return true; + return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); } - // {{{ destructor -// function _Archive_Tar() - function __destruct() - { - $this->_close(); - // ----- Look for a local copy to delete - if ($this->_temp_tarname != '') - @drupal_unlink($this->_temp_tarname); -// $this->_PEAR(); - } - // }}} - - // {{{ create() /** - * This method creates the archive file and add the files / directories - * that are listed in $p_filelist. - * If a file with the same name exist and is writable, it is replaced - * by the new tar. - * The method return false and a PEAR error text. - * The $p_filelist parameter can be an array of string, each string - * representing a filename or a directory name with their path if - * needed. It can also be a single string with names separated by a - * single blank. - * For each directory added in the archive, the files and - * sub-directories are also added. - * See also createModify() method for more details. - * - * @param array $p_filelist An array of filenames and directory names, or a - * single string with names separated by a single - * blank space. - * @return true on success, false on error. - * @see createModify() - * @access public - */ - function create($p_filelist) + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If a file with the same name exist and is writable, it is replaced + * by the new tar. + * The method return false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * For each directory added in the archive, the files and + * sub-directories are also added. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * + * @return true on success, false on error. + * @see createModify() + */ + public function create($p_filelist) { return $this->createModify($p_filelist, '', ''); } - // }}} - // {{{ add() /** - * This method add the files / directories that are listed in $p_filelist in - * the archive. If the archive does not exist it is created. - * The method return false and a PEAR error text. - * The files and directories listed are only added at the end of the archive, - * even if a file with the same name is already archived. - * See also createModify() method for more details. - * - * @param array $p_filelist An array of filenames and directory names, or a - * single string with names separated by a single - * blank space. - * @return true on success, false on error. - * @see createModify() - * @access public - */ - function add($p_filelist) + * This method add the files / directories that are listed in $p_filelist in + * the archive. If the archive does not exist it is created. + * The method return false and a PEAR error text. + * The files and directories listed are only added at the end of the archive, + * even if a file with the same name is already archived. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * + * @return true on success, false on error. + * @see createModify() + * @access public + */ + public function add($p_filelist) { return $this->addModify($p_filelist, '', ''); } - // }}} - // {{{ extract() - function extract($p_path='') + /** + * @param string $p_path + * @param bool $p_preserve + * @return bool + */ + public function extract($p_path = '', $p_preserve = false) { - return $this->extractModify($p_path, ''); + return $this->extractModify($p_path, '', $p_preserve); } - // }}} - // {{{ listContent() - function listContent() + /** + * @return array|int + */ + public function listContent() { $v_list_detail = array(); @@ -287,57 +395,56 @@ return $v_list_detail; } - // }}} - // {{{ createModify() /** - * This method creates the archive file and add the files / directories - * that are listed in $p_filelist. - * If the file already exists and is writable, it is replaced by the - * new tar. It is a create and not an add. If the file exists and is - * read-only or is a directory it is not replaced. The method return - * false and a PEAR error text. - * The $p_filelist parameter can be an array of string, each string - * representing a filename or a directory name with their path if - * needed. It can also be a single string with names separated by a - * single blank. - * The path indicated in $p_remove_dir will be removed from the - * memorized path of each file / directory listed when this path - * exists. By default nothing is removed (empty path '') - * The path indicated in $p_add_dir will be added at the beginning of - * the memorized path of each file / directory listed. However it can - * be set to empty ''. The adding of a path is done after the removing - * of path. - * The path add/remove ability enables the user to prepare an archive - * for extraction in a different path than the origin files are. - * See also addModify() method for file adding properties. - * - * @param array $p_filelist An array of filenames and directory names, - * or a single string with names separated by - * a single blank space. - * @param string $p_add_dir A string which contains a path to be added - * to the memorized path of each element in - * the list. - * @param string $p_remove_dir A string which contains a path to be - * removed from the memorized path of each - * element in the list, when relevant. - * @return boolean true on success, false on error. - * @access public - * @see addModify() - */ - function createModify($p_filelist, $p_add_dir, $p_remove_dir='') + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If the file already exists and is writable, it is replaced by the + * new tar. It is a create and not an add. If the file exists and is + * read-only or is a directory it is not replaced. The method return + * false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * See also addModify() method for file adding properties. + * + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated by + * a single blank space. + * @param string $p_add_dir A string which contains a path to be added + * to the memorized path of each element in + * the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of each + * element in the list, when relevant. + * + * @return boolean true on success, false on error. + * @see addModify() + */ + public function createModify($p_filelist, $p_add_dir, $p_remove_dir = '') { $v_result = true; - if (!$this->_openWrite()) + if (!$this->_openWrite()) { return false; + } if ($p_filelist != '') { - if (is_array($p_filelist)) + if (is_array($p_filelist)) { $v_list = $p_filelist; - elseif (is_string($p_filelist)) + } elseif (is_string($p_filelist)) { $v_list = explode($this->_separator, $p_filelist); - else { + } else { $this->_cleanFile(); $this->_error('Invalid file list'); return false; @@ -349,67 +456,69 @@ if ($v_result) { $this->_writeFooter(); $this->_close(); - } else + } else { $this->_cleanFile(); + } return $v_result; } - // }}} - // {{{ addModify() /** - * This method add the files / directories listed in $p_filelist at the - * end of the existing archive. If the archive does not yet exists it - * is created. - * The $p_filelist parameter can be an array of string, each string - * representing a filename or a directory name with their path if - * needed. It can also be a single string with names separated by a - * single blank. - * The path indicated in $p_remove_dir will be removed from the - * memorized path of each file / directory listed when this path - * exists. By default nothing is removed (empty path '') - * The path indicated in $p_add_dir will be added at the beginning of - * the memorized path of each file / directory listed. However it can - * be set to empty ''. The adding of a path is done after the removing - * of path. - * The path add/remove ability enables the user to prepare an archive - * for extraction in a different path than the origin files are. - * If a file/dir is already in the archive it will only be added at the - * end of the archive. There is no update of the existing archived - * file/dir. However while extracting the archive, the last file will - * replace the first one. This results in a none optimization of the - * archive size. - * If a file/dir does not exist the file/dir is ignored. However an - * error text is send to PEAR error. - * If a file/dir is not readable the file/dir is ignored. However an - * error text is send to PEAR error. - * - * @param array $p_filelist An array of filenames and directory - * names, or a single string with names - * separated by a single blank space. - * @param string $p_add_dir A string which contains a path to be - * added to the memorized path of each - * element in the list. - * @param string $p_remove_dir A string which contains a path to be - * removed from the memorized path of - * each element in the list, when - * relevant. - * @return true on success, false on error. - * @access public - */ - function addModify($p_filelist, $p_add_dir, $p_remove_dir='') + * This method add the files / directories listed in $p_filelist at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * If a file/dir is already in the archive it will only be added at the + * end of the archive. There is no update of the existing archived + * file/dir. However while extracting the archive, the last file will + * replace the first one. This results in a none optimization of the + * archive size. + * If a file/dir does not exist the file/dir is ignored. However an + * error text is send to PEAR error. + * If a file/dir is not readable the file/dir is ignored. However an + * error text is send to PEAR error. + * + * @param array $p_filelist An array of filenames and directory + * names, or a single string with names + * separated by a single blank space. + * @param string $p_add_dir A string which contains a path to be + * added to the memorized path of each + * element in the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of + * each element in the list, when + * relevant. + * + * @return true on success, false on error. + */ + public function addModify($p_filelist, $p_add_dir, $p_remove_dir = '') { $v_result = true; - if (!$this->_isArchive()) - $v_result = $this->createModify($p_filelist, $p_add_dir, - $p_remove_dir); - else { - if (is_array($p_filelist)) + if (!$this->_isArchive()) { + $v_result = $this->createModify( + $p_filelist, + $p_add_dir, + $p_remove_dir + ); + } else { + if (is_array($p_filelist)) { $v_list = $p_filelist; - elseif (is_string($p_filelist)) + } elseif (is_string($p_filelist)) { $v_list = explode($this->_separator, $p_filelist); - else { + } else { $this->_error('Invalid file list'); return false; } @@ -419,24 +528,41 @@ return $v_result; } - // }}} - // {{{ addString() /** - * This method add a single string as a file at the - * end of the existing archive. If the archive does not yet exists it - * is created. - * - * @param string $p_filename A string which contains the full - * filename path that will be associated - * with the string. - * @param string $p_string The content of the file added in - * the archive. - * @return true on success, false on error. - * @access public - */ - function addString($p_filename, $p_string) + * This method add a single string as a file at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * + * @param string $p_filename A string which contains the full + * filename path that will be associated + * with the string. + * @param string $p_string The content of the file added in + * the archive. + * @param bool|int $p_datetime A custom date/time (unix timestamp) + * for the file (optional). + * @param array $p_params An array of optional params: + * stamp => the datetime (replaces + * datetime above if it exists) + * mode => the permissions on the + * file (600 by default) + * type => is this a link? See the + * tar specification for details. + * (default = regular file) + * uid => the user ID of the file + * (default = 0 = root) + * gid => the group ID of the file + * (default = 0 = root) + * + * @return true on success, false on error. + */ + public function addString($p_filename, $p_string, $p_datetime = false, $p_params = array()) { + $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time()); + $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600; + $p_type = @$p_params["type"] ? $p_params["type"] : ""; + $p_uid = @$p_params["uid"] ? $p_params["uid"] : ""; + $p_gid = @$p_params["gid"] ? $p_params["gid"] : ""; $v_result = true; if (!$this->_isArchive()) { @@ -446,11 +572,12 @@ $this->_close(); } - if (!$this->_openAppend()) + if (!$this->_openAppend()) { return false; + } // Need to check the get back to the temporary file ? .... - $v_result = $this->_addString($p_filename, $p_string); + $v_result = $this->_addString($p_filename, $p_string, $p_datetime, $p_params); $this->_writeFooter(); @@ -458,131 +585,138 @@ return $v_result; } - // }}} - // {{{ extractModify() /** - * This method extract all the content of the archive in the directory - * indicated by $p_path. When relevant the memorized path of the - * files/dir can be modified by removing the $p_remove_path path at the - * beginning of the file/dir path. - * While extracting a file, if the directory path does not exists it is - * created. - * While extracting a file, if the file already exists it is replaced - * without looking for last modification date. - * While extracting a file, if the file already exists and is write - * protected, the extraction is aborted. - * While extracting a file, if a directory with the same name already - * exists, the extraction is aborted. - * While extracting a directory, if a file with the same name already - * exists, the extraction is aborted. - * While extracting a file/directory if the destination directory exist - * and is write protected, or does not exist but can not be created, - * the extraction is aborted. - * If after extraction an extracted file does not show the correct - * stored file size, the extraction is aborted. - * When the extraction is aborted, a PEAR error text is set and false - * is returned. However the result can be a partial extraction that may - * need to be manually cleaned. - * - * @param string $p_path The path of the directory where the - * files/dir need to by extracted. - * @param string $p_remove_path Part of the memorized path that can be - * removed if present at the beginning of - * the file/dir path. - * @return boolean true on success, false on error. - * @access public - * @see extractList() - */ - function extractModify($p_path, $p_remove_path) + * This method extract all the content of the archive in the directory + * indicated by $p_path. When relevant the memorized path of the + * files/dir can be modified by removing the $p_remove_path path at the + * beginning of the file/dir path. + * While extracting a file, if the directory path does not exists it is + * created. + * While extracting a file, if the file already exists it is replaced + * without looking for last modification date. + * While extracting a file, if the file already exists and is write + * protected, the extraction is aborted. + * While extracting a file, if a directory with the same name already + * exists, the extraction is aborted. + * While extracting a directory, if a file with the same name already + * exists, the extraction is aborted. + * While extracting a file/directory if the destination directory exist + * and is write protected, or does not exist but can not be created, + * the extraction is aborted. + * If after extraction an extracted file does not show the correct + * stored file size, the extraction is aborted. + * When the extraction is aborted, a PEAR error text is set and false + * is returned. However the result can be a partial extraction that may + * need to be manually cleaned. + * + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @param boolean $p_preserve Preserve user/group ownership of files + * + * @return boolean true on success, false on error. + * @see extractList() + */ + public function extractModify($p_path, $p_remove_path, $p_preserve = false) { $v_result = true; $v_list_detail = array(); if ($v_result = $this->_openRead()) { - $v_result = $this->_extractList($p_path, $v_list_detail, - "complete", 0, $p_remove_path); + $v_result = $this->_extractList( + $p_path, + $v_list_detail, + "complete", + 0, + $p_remove_path, + $p_preserve + ); $this->_close(); } return $v_result; } - // }}} - // {{{ extractInString() /** - * This method extract from the archive one file identified by $p_filename. - * The return value is a string with the file content, or NULL on error. - * @param string $p_filename The path of the file to extract in a string. - * @return a string with the file content or NULL. - * @access public - */ - function extractInString($p_filename) + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or NULL on error. + * + * @param string $p_filename The path of the file to extract in a string. + * + * @return a string with the file content or NULL. + */ + public function extractInString($p_filename) { if ($this->_openRead()) { $v_result = $this->_extractInString($p_filename); $this->_close(); } else { - $v_result = NULL; + $v_result = null; } return $v_result; } - // }}} - // {{{ extractList() /** - * This method extract from the archive only the files indicated in the - * $p_filelist. These files are extracted in the current directory or - * in the directory indicated by the optional $p_path parameter. - * If indicated the $p_remove_path can be used in the same way as it is - * used in extractModify() method. - * @param array $p_filelist An array of filenames and directory names, - * or a single string with names separated - * by a single blank space. - * @param string $p_path The path of the directory where the - * files/dir need to by extracted. - * @param string $p_remove_path Part of the memorized path that can be - * removed if present at the beginning of - * the file/dir path. - * @return true on success, false on error. - * @access public - * @see extractModify() - */ - function extractList($p_filelist, $p_path='', $p_remove_path='') + * This method extract from the archive only the files indicated in the + * $p_filelist. These files are extracted in the current directory or + * in the directory indicated by the optional $p_path parameter. + * If indicated the $p_remove_path can be used in the same way as it is + * used in extractModify() method. + * + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated + * by a single blank space. + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @param boolean $p_preserve Preserve user/group ownership of files + * + * @return true on success, false on error. + * @see extractModify() + */ + public function extractList($p_filelist, $p_path = '', $p_remove_path = '', $p_preserve = false) { $v_result = true; $v_list_detail = array(); - if (is_array($p_filelist)) + if (is_array($p_filelist)) { $v_list = $p_filelist; - elseif (is_string($p_filelist)) + } elseif (is_string($p_filelist)) { $v_list = explode($this->_separator, $p_filelist); - else { + } else { $this->_error('Invalid string list'); return false; } if ($v_result = $this->_openRead()) { - $v_result = $this->_extractList($p_path, $v_list_detail, "partial", - $v_list, $p_remove_path); + $v_result = $this->_extractList( + $p_path, + $v_list_detail, + "partial", + $v_list, + $p_remove_path, + $p_preserve + ); $this->_close(); } return $v_result; } - // }}} - // {{{ setAttribute() /** - * This method set specific attributes of the archive. It uses a variable - * list of parameters, in the format attribute code + attribute values : - * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ','); - * @param mixed $argv variable list of attributes and values - * @return true on success, false on error. - * @access public - */ - function setAttribute() + * This method set specific attributes of the archive. It uses a variable + * list of parameters, in the format attribute code + attribute values : + * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ','); + * + * @return true on success, false on error. + */ + public function setAttribute() { $v_result = true; @@ -592,30 +726,32 @@ } // ----- Get the arguments - $v_att_list = &func_get_args(); + $v_att_list = func_get_args(); // ----- Read the attributes - $i=0; - while ($i<$v_size) { + $i = 0; + while ($i < $v_size) { // ----- Look for next option switch ($v_att_list[$i]) { // ----- Look for options that request a string value case ARCHIVE_TAR_ATT_SEPARATOR : // ----- Check the number of parameters - if (($i+1) >= $v_size) { - $this->_error('Invalid number of parameters for ' - .'attribute ARCHIVE_TAR_ATT_SEPARATOR'); + if (($i + 1) >= $v_size) { + $this->_error( + 'Invalid number of parameters for ' + . 'attribute ARCHIVE_TAR_ATT_SEPARATOR' + ); return false; } // ----- Get the value - $this->_separator = $v_att_list[$i+1]; + $this->_separator = $v_att_list[$i + 1]; $i++; - break; + break; default : - $this->_error('Unknow attribute code '.$v_att_list[$i].''); + $this->_error('Unknown attribute code ' . $v_att_list[$i] . ''); return false; } @@ -625,151 +761,248 @@ return $v_result; } - // }}} - // {{{ _error() - function _error($p_message) + /** + * This method sets the regular expression for ignoring files and directories + * at import, for example: + * $arch->setIgnoreRegexp("#CVS|\.svn#"); + * + * @param string $regexp regular expression defining which files or directories to ignore + */ + public function setIgnoreRegexp($regexp) + { + $this->_ignore_regexp = $regexp; + } + + /** + * This method sets the regular expression for ignoring all files and directories + * matching the filenames in the array list at import, for example: + * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool')); + * + * @param array $list a list of file or directory names to ignore + * + * @access public + */ + public function setIgnoreList($list) + { + $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list); + $regexp = '#/' . join('$|/', $list) . '#'; + $this->setIgnoreRegexp($regexp); + } + + /** + * @param string $p_message + */ + public function _error($p_message) { - // ----- To be completed -// $this->raiseError($p_message); + // Drupal change $this->error_object = $this->raiseError($p_message). throw new Exception($p_message); } - // }}} - // {{{ _warning() - function _warning($p_message) + /** + * @param string $p_message + */ + public function _warning($p_message) { - // ----- To be completed -// $this->raiseError($p_message); + // Drupal change $this->error_object = $this->raiseError($p_message). throw new Exception($p_message); } - // }}} - // {{{ _isArchive() - function _isArchive($p_filename=NULL) + /** + * @param string $p_filename + * @return bool + */ + public function _isArchive($p_filename = null) { - if ($p_filename == NULL) { + if ($p_filename == null) { $p_filename = $this->_tarname; } clearstatcache(); return @is_file($p_filename) && !@is_link($p_filename); } - // }}} - // {{{ _openWrite() - function _openWrite() + /** + * @return bool + */ + public function _openWrite() { - if ($this->_compress_type == 'gz') + if ($this->_compress_type == 'gz' && function_exists('gzopen')) { $this->_file = @gzopen($this->_tarname, "wb9"); - else if ($this->_compress_type == 'bz2') - $this->_file = @bzopen($this->_tarname, "w"); - else if ($this->_compress_type == 'none') - $this->_file = @fopen($this->_tarname, "wb"); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'bz2' && function_exists('bzopen')) { + $this->_file = @bzopen($this->_tarname, "w"); + } else { + if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) { + $this->_file = @xzopen($this->_tarname, 'w'); + } else { + if ($this->_compress_type == 'none') { + $this->_file = @fopen($this->_tarname, "wb"); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + return false; + } + } + } + } if ($this->_file == 0) { - $this->_error('Unable to open in write mode \'' - .$this->_tarname.'\''); + $this->_error( + 'Unable to open in write mode \'' + . $this->_tarname . '\'' + ); return false; } return true; } - // }}} - // {{{ _openRead() - function _openRead() + /** + * @return bool + */ + public function _openRead() { if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') { - // ----- Look if a local copy need to be done - if ($this->_temp_tarname == '') { - $this->_temp_tarname = uniqid('tar').'.tmp'; - if (!$v_file_from = @fopen($this->_tarname, 'rb')) { - $this->_error('Unable to open in read mode \'' - .$this->_tarname.'\''); - $this->_temp_tarname = ''; - return false; - } - if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) { - $this->_error('Unable to open in write mode \'' - .$this->_temp_tarname.'\''); - $this->_temp_tarname = ''; - return false; - } - while ($v_data = @fread($v_file_from, 1024)) - @fwrite($v_file_to, $v_data); - @fclose($v_file_from); - @fclose($v_file_to); - } - - // ----- File to open if the local copy - $v_filename = $this->_temp_tarname; - - } else - // ----- File to open if the normal Tar file - $v_filename = $this->_tarname; + // ----- Look if a local copy need to be done + if ($this->_temp_tarname == '') { + $this->_temp_tarname = uniqid('tar') . '.tmp'; + if (!$v_file_from = @fopen($this->_tarname, 'rb')) { + $this->_error( + 'Unable to open in read mode \'' + . $this->_tarname . '\'' + ); + $this->_temp_tarname = ''; + return false; + } + if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) { + $this->_error( + 'Unable to open in write mode \'' + . $this->_temp_tarname . '\'' + ); + $this->_temp_tarname = ''; + return false; + } + while ($v_data = @fread($v_file_from, 1024)) { + @fwrite($v_file_to, $v_data); + } + @fclose($v_file_from); + @fclose($v_file_to); + } - if ($this->_compress_type == 'gz') + // ----- File to open if the local copy + $v_filename = $this->_temp_tarname; + } else { + // ----- File to open if the normal Tar file + + $v_filename = $this->_tarname; + } + + if ($this->_compress_type == 'gz' && function_exists('gzopen')) { $this->_file = @gzopen($v_filename, "rb"); - else if ($this->_compress_type == 'bz2') - $this->_file = @bzopen($v_filename, "r"); - else if ($this->_compress_type == 'none') - $this->_file = @fopen($v_filename, "rb"); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'bz2' && function_exists('bzopen')) { + $this->_file = @bzopen($v_filename, "r"); + } else { + if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) { + $this->_file = @xzopen($v_filename, "r"); + } else { + if ($this->_compress_type == 'none') { + $this->_file = @fopen($v_filename, "rb"); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + return false; + } + } + } + } if ($this->_file == 0) { - $this->_error('Unable to open in read mode \''.$v_filename.'\''); + $this->_error('Unable to open in read mode \'' . $v_filename . '\''); return false; } return true; } - // }}} - // {{{ _openReadWrite() - function _openReadWrite() + /** + * @return bool + */ + public function _openReadWrite() { - if ($this->_compress_type == 'gz') + if ($this->_compress_type == 'gz') { $this->_file = @gzopen($this->_tarname, "r+b"); - else if ($this->_compress_type == 'bz2') { - $this->_error('Unable to open bz2 in read/write mode \'' - .$this->_tarname.'\' (limitation of bz2 extension)'); - return false; - } else if ($this->_compress_type == 'none') - $this->_file = @fopen($this->_tarname, "r+b"); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'bz2') { + $this->_error( + 'Unable to open bz2 in read/write mode \'' + . $this->_tarname . '\' (limitation of bz2 extension)' + ); + return false; + } else { + if ($this->_compress_type == 'lzma2') { + $this->_error( + 'Unable to open lzma2 in read/write mode \'' + . $this->_tarname . '\' (limitation of lzma2 extension)' + ); + return false; + } else { + if ($this->_compress_type == 'none') { + $this->_file = @fopen($this->_tarname, "r+b"); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + return false; + } + } + } + } if ($this->_file == 0) { - $this->_error('Unable to open in read/write mode \'' - .$this->_tarname.'\''); + $this->_error( + 'Unable to open in read/write mode \'' + . $this->_tarname . '\'' + ); return false; } return true; } - // }}} - // {{{ _close() - function _close() + /** + * @return bool + */ + public function _close() { //if (isset($this->_file)) { if (is_resource($this->_file)) { - if ($this->_compress_type == 'gz') + if ($this->_compress_type == 'gz') { @gzclose($this->_file); - else if ($this->_compress_type == 'bz2') - @bzclose($this->_file); - else if ($this->_compress_type == 'none') - @fclose($this->_file); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'bz2') { + @bzclose($this->_file); + } else { + if ($this->_compress_type == 'lzma2') { + @xzclose($this->_file); + } else { + if ($this->_compress_type == 'none') { + @fclose($this->_file); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + } + } + } + } $this->_file = 0; } @@ -783,348 +1016,495 @@ return true; } - // }}} - // {{{ _cleanFile() - function _cleanFile() + /** + * @return bool + */ + public function _cleanFile() + { + $this->_close(); + + // ----- Look for a local copy + if ($this->_temp_tarname != '') { + // ----- Remove the local copy but not the remote tarname + @drupal_unlink($this->_temp_tarname); + $this->_temp_tarname = ''; + } else { + // ----- Remove the local tarname file + @drupal_unlink($this->_tarname); + } + $this->_tarname = ''; + + return true; + } + + /** + * @param mixed $p_binary_data + * @param integer $p_len + * @return bool + */ + public function _writeBlock($p_binary_data, $p_len = null) + { + if (is_resource($this->_file)) { + if ($p_len === null) { + if ($this->_compress_type == 'gz') { + @gzputs($this->_file, $p_binary_data); + } else { + if ($this->_compress_type == 'bz2') { + @bzwrite($this->_file, $p_binary_data); + } else { + if ($this->_compress_type == 'lzma2') { + @xzwrite($this->_file, $p_binary_data); + } else { + if ($this->_compress_type == 'none') { + @fputs($this->_file, $p_binary_data); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + } + } + } + } + } else { + if ($this->_compress_type == 'gz') { + @gzputs($this->_file, $p_binary_data, $p_len); + } else { + if ($this->_compress_type == 'bz2') { + @bzwrite($this->_file, $p_binary_data, $p_len); + } else { + if ($this->_compress_type == 'lzma2') { + @xzwrite($this->_file, $p_binary_data, $p_len); + } else { + if ($this->_compress_type == 'none') { + @fputs($this->_file, $p_binary_data, $p_len); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + } + } + } + } + } + } + return true; + } + + /** + * @return null|string + */ + public function _readBlock() + { + $v_block = null; + if (is_resource($this->_file)) { + if ($this->_compress_type == 'gz') { + $v_block = @gzread($this->_file, 512); + } else { + if ($this->_compress_type == 'bz2') { + $v_block = @bzread($this->_file, 512); + } else { + if ($this->_compress_type == 'lzma2') { + $v_block = @xzread($this->_file, 512); + } else { + if ($this->_compress_type == 'none') { + $v_block = @fread($this->_file, 512); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + } + } + } + } + } + return $v_block; + } + + /** + * @param null $p_len + * @return bool + */ + public function _jumpBlock($p_len = null) + { + if (is_resource($this->_file)) { + if ($p_len === null) { + $p_len = 1; + } + + if ($this->_compress_type == 'gz') { + @gzseek($this->_file, gztell($this->_file) + ($p_len * 512)); + } else { + if ($this->_compress_type == 'bz2') { + // ----- Replace missing bztell() and bzseek() + for ($i = 0; $i < $p_len; $i++) { + $this->_readBlock(); + } + } else { + if ($this->_compress_type == 'lzma2') { + // ----- Replace missing xztell() and xzseek() + for ($i = 0; $i < $p_len; $i++) { + $this->_readBlock(); + } + } else { + if ($this->_compress_type == 'none') { + @fseek($this->_file, $p_len * 512, SEEK_CUR); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + } + } + } + } + } + return true; + } + + /** + * @return bool + */ + public function _writeFooter() + { + if (is_resource($this->_file)) { + // ----- Write the last 0 filled block for end of archive + $v_binary_data = pack('a1024', ''); + $this->_writeBlock($v_binary_data); + } + return true; + } + + /** + * @param array $p_list + * @param string $p_add_dir + * @param string $p_remove_dir + * @return bool + */ + public function _addList($p_list, $p_add_dir, $p_remove_dir) + { + $v_result = true; + $v_header = array(); + + // ----- Remove potential windows directory separator + $p_add_dir = $this->_translateWinPath($p_add_dir); + $p_remove_dir = $this->_translateWinPath($p_remove_dir, false); + + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if (sizeof($p_list) == 0) { + return true; + } + + foreach ($p_list as $v_filename) { + if (!$v_result) { + break; + } + + // ----- Skip the current tar name + if ($v_filename == $this->_tarname) { + continue; + } + + if ($v_filename == '') { + continue; + } + + // ----- ignore files and directories matching the ignore regular expression + if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/' . $v_filename)) { + $this->_warning("File '$v_filename' ignored"); + continue; + } + + if (!file_exists($v_filename) && !is_link($v_filename)) { + $this->_warning("File '$v_filename' does not exist"); + continue; + } + + // ----- Add the file or directory header + if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) { + return false; + } + + if (@is_dir($v_filename) && !@is_link($v_filename)) { + if (!($p_hdir = opendir($v_filename))) { + $this->_warning("Directory '$v_filename' can not be read"); + continue; + } + while (false !== ($p_hitem = readdir($p_hdir))) { + if (($p_hitem != '.') && ($p_hitem != '..')) { + if ($v_filename != ".") { + $p_temp_list[0] = $v_filename . '/' . $p_hitem; + } else { + $p_temp_list[0] = $p_hitem; + } + + $v_result = $this->_addList( + $p_temp_list, + $p_add_dir, + $p_remove_dir + ); + } + } + + unset($p_temp_list); + unset($p_hdir); + unset($p_hitem); + } + } + + return $v_result; + } + + /** + * @param string $p_filename + * @param mixed $p_header + * @param string $p_add_dir + * @param string $p_remove_dir + * @param null $v_stored_filename + * @return bool + */ + public function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir, $v_stored_filename = null) + { + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } + + if (is_null($v_stored_filename)) { + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false); + $v_stored_filename = $p_filename; + + if (strcmp($p_filename, $p_remove_dir) == 0) { + return true; + } + + if ($p_remove_dir != '') { + if (substr($p_remove_dir, -1) != '/') { + $p_remove_dir .= '/'; + } + + if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) { + $v_stored_filename = substr($p_filename, strlen($p_remove_dir)); + } + } + + $v_stored_filename = $this->_translateWinPath($v_stored_filename); + if ($p_add_dir != '') { + if (substr($p_add_dir, -1) == '/') { + $v_stored_filename = $p_add_dir . $v_stored_filename; + } else { + $v_stored_filename = $p_add_dir . '/' . $v_stored_filename; + } + } + + $v_stored_filename = $this->_pathReduction($v_stored_filename); + } + + if ($this->_isArchive($p_filename)) { + if (($v_file = @fopen($p_filename, "rb")) == 0) { + $this->_warning( + "Unable to open file '" . $p_filename + . "' in binary read mode" + ); + return true; + } + + if (!$this->_writeHeader($p_filename, $v_stored_filename)) { + return false; + } + + while (($v_buffer = fread($v_file, 512)) != '') { + $v_binary_data = pack("a512", "$v_buffer"); + $this->_writeBlock($v_binary_data); + } + + fclose($v_file); + } else { + // ----- Only header for dir + if (!$this->_writeHeader($p_filename, $v_stored_filename)) { + return false; + } + } + + return true; + } + + /** + * @param string $p_filename + * @param string $p_string + * @param bool $p_datetime + * @param array $p_params + * @return bool + */ + public function _addString($p_filename, $p_string, $p_datetime = false, $p_params = array()) { - $this->_close(); + $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time()); + $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600; + $p_type = @$p_params["type"] ? $p_params["type"] : ""; + $p_uid = @$p_params["uid"] ? $p_params["uid"] : 0; + $p_gid = @$p_params["gid"] ? $p_params["gid"] : 0; + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } - // ----- Look for a local copy - if ($this->_temp_tarname != '') { - // ----- Remove the local copy but not the remote tarname - @drupal_unlink($this->_temp_tarname); - $this->_temp_tarname = ''; - } else { - // ----- Remove the local tarname file - @drupal_unlink($this->_tarname); + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; } - $this->_tarname = ''; - return true; - } - // }}} + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false); - // {{{ _writeBlock() - function _writeBlock($p_binary_data, $p_len=null) - { - if (is_resource($this->_file)) { - if ($p_len === null) { - if ($this->_compress_type == 'gz') - @gzputs($this->_file, $p_binary_data); - else if ($this->_compress_type == 'bz2') - @bzwrite($this->_file, $p_binary_data); - else if ($this->_compress_type == 'none') - @fputs($this->_file, $p_binary_data); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - } else { - if ($this->_compress_type == 'gz') - @gzputs($this->_file, $p_binary_data, $p_len); - else if ($this->_compress_type == 'bz2') - @bzwrite($this->_file, $p_binary_data, $p_len); - else if ($this->_compress_type == 'none') - @fputs($this->_file, $p_binary_data, $p_len); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - - } - } - return true; - } - // }}} - - // {{{ _readBlock() - function _readBlock() - { - $v_block = null; - if (is_resource($this->_file)) { - if ($this->_compress_type == 'gz') - $v_block = @gzread($this->_file, 512); - else if ($this->_compress_type == 'bz2') - $v_block = @bzread($this->_file, 512); - else if ($this->_compress_type == 'none') - $v_block = @fread($this->_file, 512); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - } - return $v_block; - } - // }}} - - // {{{ _jumpBlock() - function _jumpBlock($p_len=null) - { - if (is_resource($this->_file)) { - if ($p_len === null) - $p_len = 1; - - if ($this->_compress_type == 'gz') { - @gzseek($this->_file, gztell($this->_file)+($p_len*512)); - } - else if ($this->_compress_type == 'bz2') { - // ----- Replace missing bztell() and bzseek() - for ($i=0; $i<$p_len; $i++) - $this->_readBlock(); - } else if ($this->_compress_type == 'none') - @fseek($this->_file, ftell($this->_file)+($p_len*512)); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - - } - return true; - } - // }}} - - // {{{ _writeFooter() - function _writeFooter() - { - if (is_resource($this->_file)) { - // ----- Write the last 0 filled block for end of archive - $v_binary_data = pack('a1024', ''); - $this->_writeBlock($v_binary_data); - } - return true; - } - // }}} - - // {{{ _addList() - function _addList($p_list, $p_add_dir, $p_remove_dir) - { - $v_result=true; - $v_header = array(); - - // ----- Remove potential windows directory separator - $p_add_dir = $this->_translateWinPath($p_add_dir); - $p_remove_dir = $this->_translateWinPath($p_remove_dir, false); - - if (!$this->_file) { - $this->_error('Invalid file descriptor'); - return false; - } - - if (sizeof($p_list) == 0) - return true; - - foreach ($p_list as $v_filename) { - if (!$v_result) { - break; - } - - // ----- Skip the current tar name - if ($v_filename == $this->_tarname) - continue; - - if ($v_filename == '') - continue; - - if (!file_exists($v_filename)) { - $this->_warning("File '$v_filename' does not exist"); - continue; - } - - // ----- Add the file or directory header - if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) - return false; - - if (@is_dir($v_filename) && !@is_link($v_filename)) { - if (!($p_hdir = opendir($v_filename))) { - $this->_warning("Directory '$v_filename' can not be read"); - continue; - } - while (false !== ($p_hitem = readdir($p_hdir))) { - if (($p_hitem != '.') && ($p_hitem != '..')) { - if ($v_filename != ".") - $p_temp_list[0] = $v_filename.'/'.$p_hitem; - else - $p_temp_list[0] = $p_hitem; - - $v_result = $this->_addList($p_temp_list, - $p_add_dir, - $p_remove_dir); - } - } - - unset($p_temp_list); - unset($p_hdir); - unset($p_hitem); - } - } - - return $v_result; - } - // }}} - - // {{{ _addFile() - function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir) - { - if (!$this->_file) { - $this->_error('Invalid file descriptor'); - return false; - } - - if ($p_filename == '') { - $this->_error('Invalid file name'); - return false; - } - - // ----- Calculate the stored filename - $p_filename = $this->_translateWinPath($p_filename, false);; - $v_stored_filename = $p_filename; - if (strcmp($p_filename, $p_remove_dir) == 0) { - return true; - } - if ($p_remove_dir != '') { - if (substr($p_remove_dir, -1) != '/') - $p_remove_dir .= '/'; - - if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) - $v_stored_filename = substr($p_filename, strlen($p_remove_dir)); - } - $v_stored_filename = $this->_translateWinPath($v_stored_filename); - if ($p_add_dir != '') { - if (substr($p_add_dir, -1) == '/') - $v_stored_filename = $p_add_dir.$v_stored_filename; - else - $v_stored_filename = $p_add_dir.'/'.$v_stored_filename; - } - - $v_stored_filename = $this->_pathReduction($v_stored_filename); - - if ($this->_isArchive($p_filename)) { - if (($v_file = @fopen($p_filename, "rb")) == 0) { - $this->_warning("Unable to open file '".$p_filename - ."' in binary read mode"); - return true; - } - - if (!$this->_writeHeader($p_filename, $v_stored_filename)) - return false; - - while (($v_buffer = fread($v_file, 512)) != '') { - $v_binary_data = pack("a512", "$v_buffer"); - $this->_writeBlock($v_binary_data); - } - - fclose($v_file); - - } else { - // ----- Only header for dir - if (!$this->_writeHeader($p_filename, $v_stored_filename)) - return false; - } - - return true; - } - // }}} - - // {{{ _addString() - function _addString($p_filename, $p_string) - { - if (!$this->_file) { - $this->_error('Invalid file descriptor'); - return false; - } - - if ($p_filename == '') { - $this->_error('Invalid file name'); - return false; - } - - // ----- Calculate the stored filename - $p_filename = $this->_translateWinPath($p_filename, false);; - - if (!$this->_writeHeaderBlock($p_filename, strlen($p_string), - time(), 384, "", 0, 0)) - return false; - - $i=0; - while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') { - $v_binary_data = pack("a512", $v_buffer); - $this->_writeBlock($v_binary_data); - } + // ----- If datetime is not specified, set current time + if ($p_datetime === false) { + $p_datetime = time(); + } + + if (!$this->_writeHeaderBlock( + $p_filename, + strlen($p_string), + $p_stamp, + $p_mode, + $p_type, + $p_uid, + $p_gid + ) + ) { + return false; + } - return true; + $i = 0; + while (($v_buffer = substr($p_string, (($i++) * 512), 512)) != '') { + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + return true; } - // }}} - // {{{ _writeHeader() - function _writeHeader($p_filename, $p_stored_filename) + /** + * @param string $p_filename + * @param string $p_stored_filename + * @return bool + */ + public function _writeHeader($p_filename, $p_stored_filename) { - if ($p_stored_filename == '') + if ($p_stored_filename == '') { $p_stored_filename = $p_filename; - $v_reduce_filename = $this->_pathReduction($p_stored_filename); - - if (strlen($v_reduce_filename) > 99) { - if (!$this->_writeLongHeader($v_reduce_filename)) - return false; } - $v_info = lstat($p_filename); - $v_uid = sprintf("%6s ", DecOct($v_info[4])); - $v_gid = sprintf("%6s ", DecOct($v_info[5])); - $v_perms = sprintf("%6s ", DecOct($v_info['mode'])); + $v_reduced_filename = $this->_pathReduction($p_stored_filename); - $v_mtime = sprintf("%11s", DecOct($v_info['mode'])); + if (strlen($v_reduced_filename) > 99) { + if (!$this->_writeLongHeader($v_reduced_filename, false)) { + return false; + } + } $v_linkname = ''; + if (@is_link($p_filename)) { + $v_linkname = readlink($p_filename); + } + + if (strlen($v_linkname) > 99) { + if (!$this->_writeLongHeader($v_linkname, true)) { + return false; + } + } + + $v_info = lstat($p_filename); + $v_uid = sprintf("%07s", DecOct($v_info[4])); + $v_gid = sprintf("%07s", DecOct($v_info[5])); + $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777)); + $v_mtime = sprintf("%011s", DecOct($v_info['mtime'])); if (@is_link($p_filename)) { - $v_typeflag = '2'; - $v_linkname = readlink($p_filename); - $v_size = sprintf("%11s ", DecOct(0)); + $v_typeflag = '2'; + $v_size = sprintf("%011s", DecOct(0)); } elseif (@is_dir($p_filename)) { - $v_typeflag = "5"; - $v_size = sprintf("%11s ", DecOct(0)); + $v_typeflag = "5"; + $v_size = sprintf("%011s", DecOct(0)); } else { - $v_typeflag = ''; - clearstatcache(); - $v_size = sprintf("%11s ", DecOct($v_info['size'])); + $v_typeflag = '0'; + clearstatcache(); + $v_size = sprintf("%011s", DecOct($v_info['size'])); } - $v_magic = ''; + $v_magic = 'ustar '; + $v_version = ' '; - $v_version = ''; - - $v_uname = ''; + if (function_exists('posix_getpwuid')) { + $userinfo = posix_getpwuid($v_info[4]); + $groupinfo = posix_getgrgid($v_info[5]); - $v_gname = ''; + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } else { + $v_uname = ''; + $v_gname = ''; + } $v_devmajor = ''; - $v_devminor = ''; - $v_prefix = ''; - $v_binary_data_first = pack("a100a8a8a8a12A12", - $v_reduce_filename, $v_perms, $v_uid, - $v_gid, $v_size, $v_mtime); - $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", - $v_typeflag, $v_linkname, $v_magic, - $v_version, $v_uname, $v_gname, - $v_devmajor, $v_devminor, $v_prefix, ''); + $v_binary_data_first = pack( + "a100a8a8a8a12a12", + $v_reduced_filename, + $v_perms, + $v_uid, + $v_gid, + $v_size, + $v_mtime + ); + $v_binary_data_last = pack( + "a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, + $v_linkname, + $v_magic, + $v_version, + $v_uname, + $v_gname, + $v_devmajor, + $v_devminor, + $v_prefix, + '' + ); // ----- Calculate the checksum $v_checksum = 0; // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum += ord(substr($v_binary_data_first,$i,1)); + for ($i = 0; $i < 148; $i++) { + $v_checksum += ord(substr($v_binary_data_first, $i, 1)); + } // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) + for ($i = 148; $i < 156; $i++) { $v_checksum += ord(' '); + } // ..... Last part of the header - for ($i=156, $j=0; $i<512; $i++, $j++) - $v_checksum += ord(substr($v_binary_data_last,$j,1)); + for ($i = 156, $j = 0; $i < 512; $i++, $j++) { + $v_checksum += ord(substr($v_binary_data_last, $j, 1)); + } // ----- Write the first 148 bytes of the header in the archive $this->_writeBlock($v_binary_data_first, 148); // ----- Write the calculated checksum - $v_checksum = sprintf("%6s ", DecOct($v_checksum)); + $v_checksum = sprintf("%06s\0 ", DecOct($v_checksum)); $v_binary_data = pack("a8", $v_checksum); $this->_writeBlock($v_binary_data, 8); @@ -1133,40 +1513,62 @@ return true; } - // }}} - // {{{ _writeHeaderBlock() - function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0, - $p_type='', $p_uid=0, $p_gid=0) - { + /** + * @param string $p_filename + * @param int $p_size + * @param int $p_mtime + * @param int $p_perms + * @param string $p_type + * @param int $p_uid + * @param int $p_gid + * @return bool + */ + public function _writeHeaderBlock( + $p_filename, + $p_size, + $p_mtime = 0, + $p_perms = 0, + $p_type = '', + $p_uid = 0, + $p_gid = 0 + ) { $p_filename = $this->_pathReduction($p_filename); if (strlen($p_filename) > 99) { - if (!$this->_writeLongHeader($p_filename)) - return false; + if (!$this->_writeLongHeader($p_filename, false)) { + return false; + } } if ($p_type == "5") { - $v_size = sprintf("%11s ", DecOct(0)); + $v_size = sprintf("%011s", DecOct(0)); } else { - $v_size = sprintf("%11s ", DecOct($p_size)); + $v_size = sprintf("%011s", DecOct($p_size)); } - $v_uid = sprintf("%6s ", DecOct($p_uid)); - $v_gid = sprintf("%6s ", DecOct($p_gid)); - $v_perms = sprintf("%6s ", DecOct($p_perms)); + $v_uid = sprintf("%07s", DecOct($p_uid)); + $v_gid = sprintf("%07s", DecOct($p_gid)); + $v_perms = sprintf("%07s", DecOct($p_perms & 000777)); $v_mtime = sprintf("%11s", DecOct($p_mtime)); $v_linkname = ''; - $v_magic = ''; + $v_magic = 'ustar '; - $v_version = ''; + $v_version = ' '; - $v_uname = ''; + if (function_exists('posix_getpwuid')) { + $userinfo = posix_getpwuid($p_uid); + $groupinfo = posix_getgrgid($p_gid); - $v_gname = ''; + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } else { + $v_uname = ''; + $v_gname = ''; + } $v_devmajor = ''; @@ -1174,31 +1576,49 @@ $v_prefix = ''; - $v_binary_data_first = pack("a100a8a8a8a12A12", - $p_filename, $v_perms, $v_uid, $v_gid, - $v_size, $v_mtime); - $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", - $p_type, $v_linkname, $v_magic, - $v_version, $v_uname, $v_gname, - $v_devmajor, $v_devminor, $v_prefix, ''); + $v_binary_data_first = pack( + "a100a8a8a8a12A12", + $p_filename, + $v_perms, + $v_uid, + $v_gid, + $v_size, + $v_mtime + ); + $v_binary_data_last = pack( + "a1a100a6a2a32a32a8a8a155a12", + $p_type, + $v_linkname, + $v_magic, + $v_version, + $v_uname, + $v_gname, + $v_devmajor, + $v_devminor, + $v_prefix, + '' + ); // ----- Calculate the checksum $v_checksum = 0; // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum += ord(substr($v_binary_data_first,$i,1)); + for ($i = 0; $i < 148; $i++) { + $v_checksum += ord(substr($v_binary_data_first, $i, 1)); + } // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) + for ($i = 148; $i < 156; $i++) { $v_checksum += ord(' '); + } // ..... Last part of the header - for ($i=156, $j=0; $i<512; $i++, $j++) - $v_checksum += ord(substr($v_binary_data_last,$j,1)); + for ($i = 156, $j = 0; $i < 512; $i++, $j++) { + $v_checksum += ord(substr($v_binary_data_last, $j, 1)); + } // ----- Write the first 148 bytes of the header in the archive $this->_writeBlock($v_binary_data_first, 148); // ----- Write the calculated checksum - $v_checksum = sprintf("%6s ", DecOct($v_checksum)); + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); $v_binary_data = pack("a8", $v_checksum); $this->_writeBlock($v_binary_data, 8); @@ -1207,55 +1627,71 @@ return true; } - // }}} - // {{{ _writeLongHeader() - function _writeLongHeader($p_filename) + /** + * @param string $p_filename + * @return bool + */ + public function _writeLongHeader($p_filename, $is_link = false) { - $v_size = sprintf("%11s ", DecOct(strlen($p_filename))); - - $v_typeflag = 'L'; - + $v_uid = sprintf("%07s", 0); + $v_gid = sprintf("%07s", 0); + $v_perms = sprintf("%07s", 0); + $v_size = sprintf("%'011s", DecOct(strlen($p_filename))); + $v_mtime = sprintf("%011s", 0); + $v_typeflag = ($is_link ? 'K' : 'L'); $v_linkname = ''; - - $v_magic = ''; - - $v_version = ''; - + $v_magic = 'ustar '; + $v_version = ' '; $v_uname = ''; - $v_gname = ''; - $v_devmajor = ''; - $v_devminor = ''; - $v_prefix = ''; - $v_binary_data_first = pack("a100a8a8a8a12A12", - '././@LongLink', 0, 0, 0, $v_size, 0); - $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", - $v_typeflag, $v_linkname, $v_magic, - $v_version, $v_uname, $v_gname, - $v_devmajor, $v_devminor, $v_prefix, ''); + $v_binary_data_first = pack( + "a100a8a8a8a12a12", + '././@LongLink', + $v_perms, + $v_uid, + $v_gid, + $v_size, + $v_mtime + ); + $v_binary_data_last = pack( + "a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, + $v_linkname, + $v_magic, + $v_version, + $v_uname, + $v_gname, + $v_devmajor, + $v_devminor, + $v_prefix, + '' + ); // ----- Calculate the checksum $v_checksum = 0; // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum += ord(substr($v_binary_data_first,$i,1)); + for ($i = 0; $i < 148; $i++) { + $v_checksum += ord(substr($v_binary_data_first, $i, 1)); + } // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) + for ($i = 148; $i < 156; $i++) { $v_checksum += ord(' '); + } // ..... Last part of the header - for ($i=156, $j=0; $i<512; $i++, $j++) - $v_checksum += ord(substr($v_binary_data_last,$j,1)); + for ($i = 156, $j = 0; $i < 512; $i++, $j++) { + $v_checksum += ord(substr($v_binary_data_last, $j, 1)); + } // ----- Write the first 148 bytes of the header in the archive $this->_writeBlock($v_binary_data_first, 148); // ----- Write the calculated checksum - $v_checksum = sprintf("%6s ", DecOct($v_checksum)); + $v_checksum = sprintf("%06s\0 ", DecOct($v_checksum)); $v_binary_data = pack("a8", $v_checksum); $this->_writeBlock($v_binary_data, 8); @@ -1263,27 +1699,30 @@ $this->_writeBlock($v_binary_data_last, 356); // ----- Write the filename as content of the block - $i=0; - while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') { + $i = 0; + while (($v_buffer = substr($p_filename, (($i++) * 512), 512)) != '') { $v_binary_data = pack("a512", "$v_buffer"); $this->_writeBlock($v_binary_data); } return true; } - // }}} - // {{{ _readHeader() - function _readHeader($v_binary_data, &$v_header) + /** + * @param mixed $v_binary_data + * @param mixed $v_header + * @return bool + */ + public function _readHeader($v_binary_data, &$v_header) { - if (strlen($v_binary_data)==0) { + if (strlen($v_binary_data) == 0) { $v_header['filename'] = ''; return true; } if (strlen($v_binary_data) != 512) { $v_header['filename'] = ''; - $this->_error('Invalid block size : '.strlen($v_binary_data)); + $this->_error('Invalid block size : ' . strlen($v_binary_data)); return false; } @@ -1293,19 +1732,17 @@ // ----- Calculate the checksum $v_checksum = 0; // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum+=ord(substr($v_binary_data,$i,1)); - // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) - $v_checksum += ord(' '); - // ..... Last part of the header - for ($i=156; $i<512; $i++) - $v_checksum+=ord(substr($v_binary_data,$i,1)); + $v_binary_split = str_split($v_binary_data); + $v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 0, 148))); + $v_checksum += array_sum(array_map('ord', array(' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',))); + $v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 156, 512))); + - $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" - ."a8checksum/a1typeflag/a100link/a6magic/a2version/" - ."a32uname/a32gname/a8devmajor/a8devminor", - $v_binary_data); + $v_data = unpack($this->_fmt, $v_binary_data); + + if (strlen($v_data["prefix"]) > 0) { + $v_data["filename"] = "$v_data[prefix]/$v_data[filename]"; + } // ----- Extract the checksum $v_header['checksum'] = OctDec(trim($v_data['checksum'])); @@ -1313,33 +1750,38 @@ $v_header['filename'] = ''; // ----- Look for last block (empty block) - if (($v_checksum == 256) && ($v_header['checksum'] == 0)) + if (($v_checksum == 256) && ($v_header['checksum'] == 0)) { return true; + } - $this->_error('Invalid checksum for file "'.$v_data['filename'] - .'" : '.$v_checksum.' calculated, ' - .$v_header['checksum'].' expected'); + $this->_error( + 'Invalid checksum for file "' . $v_data['filename'] + . '" : ' . $v_checksum . ' calculated, ' + . $v_header['checksum'] . ' expected' + ); return false; } // ----- Extract the properties - $v_header['filename'] = trim($v_data['filename']); + $v_header['filename'] = rtrim($v_data['filename'], "\0"); if ($this->_maliciousFilename($v_header['filename'])) { - $this->_error('Malicious .tar detected, file "' . $v_header['filename'] . - '" will not install in desired directory tree'); + $this->_error( + 'Malicious .tar detected, file "' . $v_header['filename'] . + '" will not install in desired directory tree' + ); return false; } $v_header['mode'] = OctDec(trim($v_data['mode'])); $v_header['uid'] = OctDec(trim($v_data['uid'])); $v_header['gid'] = OctDec(trim($v_data['gid'])); - $v_header['size'] = OctDec(trim($v_data['size'])); + $v_header['size'] = $this->_tarRecToSize($v_data['size']); $v_header['mtime'] = OctDec(trim($v_data['mtime'])); if (($v_header['typeflag'] = $v_data['typeflag']) == "5") { - $v_header['size'] = 0; + $v_header['size'] = 0; } $v_header['link'] = trim($v_data['link']); /* ----- All these fields are removed form the header because - they do not carry interesting info + they do not carry interesting info $v_header[magic] = trim($v_data[magic]); $v_header[version] = trim($v_data[version]); $v_header[uname] = trim($v_data[uname]); @@ -1350,406 +1792,580 @@ return true; } - // }}} - // {{{ _maliciousFilename() + /** + * Convert Tar record size to actual size + * + * @param string $tar_size + * @return size of tar record in bytes + */ + private function _tarRecToSize($tar_size) + { + /* + * First byte of size has a special meaning if bit 7 is set. + * + * Bit 7 indicates base-256 encoding if set. + * Bit 6 is the sign bit. + * Bits 5:0 are most significant value bits. + */ + $ch = ord($tar_size[0]); + if ($ch & 0x80) { + // Full 12-bytes record is required. + $rec_str = $tar_size . "\x00"; + + $size = ($ch & 0x40) ? -1 : 0; + $size = ($size << 6) | ($ch & 0x3f); + + for ($num_ch = 1; $num_ch < 12; ++$num_ch) { + $size = ($size * 256) + ord($rec_str[$num_ch]); + } + + return $size; + + } else { + return OctDec(trim($tar_size)); + } + } + /** * Detect and report a malicious file name * * @param string $file + * * @return bool - * @access private */ - function _maliciousFilename($file) + private function _maliciousFilename($file) { - if (strpos($file, '/../') !== false) { + if (strpos($file, 'phar://') === 0) { + return true; + } + if (strpos($file, DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR) !== false) { return true; } - if (strpos($file, '../') === 0) { + if (strpos($file, '..' . DIRECTORY_SEPARATOR) === 0) { return true; } return false; } - // }}} - // {{{ _readLongHeader() - function _readLongHeader(&$v_header) + /** + * @param $v_header + * @return bool + */ + public function _readLongHeader(&$v_header) { - $v_filename = ''; - $n = floor($v_header['size']/512); - for ($i=0; $i<$n; $i++) { - $v_content = $this->_readBlock(); - $v_filename .= $v_content; - } - if (($v_header['size'] % 512) != 0) { - $v_content = $this->_readBlock(); - $v_filename .= $v_content; - } + $v_filename = ''; + $v_filesize = $v_header['size']; + $n = floor($v_header['size'] / 512); + for ($i = 0; $i < $n; $i++) { + $v_content = $this->_readBlock(); + $v_filename .= $v_content; + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_filename .= $v_content; + } - // ----- Read the next header - $v_binary_data = $this->_readBlock(); + // ----- Read the next header + $v_binary_data = $this->_readBlock(); - if (!$this->_readHeader($v_binary_data, $v_header)) - return false; + if (!$this->_readHeader($v_binary_data, $v_header)) { + return false; + } - $v_filename = trim($v_filename); - $v_header['filename'] = $v_filename; + $v_filename = rtrim(substr($v_filename, 0, $v_filesize), "\0"); + $v_header['filename'] = $v_filename; if ($this->_maliciousFilename($v_filename)) { - $this->_error('Malicious .tar detected, file "' . $v_filename . - '" will not install in desired directory tree'); + $this->_error( + 'Malicious .tar detected, file "' . $v_filename . + '" will not install in desired directory tree' + ); return false; - } + } - return true; + return true; } - // }}} - // {{{ _extractInString() /** - * This method extract from the archive one file identified by $p_filename. - * The return value is a string with the file content, or NULL on error. - * @param string $p_filename The path of the file to extract in a string. - * @return a string with the file content or NULL. - * @access private - */ - function _extractInString($p_filename) + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or null on error. + * + * @param string $p_filename The path of the file to extract in a string. + * + * @return a string with the file content or null. + */ + private function _extractInString($p_filename) { $v_result_str = ""; - While (strlen($v_binary_data = $this->_readBlock()) != 0) - { - if (!$this->_readHeader($v_binary_data, $v_header)) - return NULL; - - if ($v_header['filename'] == '') - continue; - - // ----- Look for long filename - if ($v_header['typeflag'] == 'L') { - if (!$this->_readLongHeader($v_header)) - return NULL; - } - - if ($v_header['filename'] == $p_filename) { - if ($v_header['typeflag'] == "5") { - $this->_error('Unable to extract in string a directory ' - .'entry {'.$v_header['filename'].'}'); - return NULL; - } else { - $n = floor($v_header['size']/512); - for ($i=0; $i<$n; $i++) { - $v_result_str .= $this->_readBlock(); - } - if (($v_header['size'] % 512) != 0) { - $v_content = $this->_readBlock(); - $v_result_str .= substr($v_content, 0, - ($v_header['size'] % 512)); - } - return $v_result_str; - } - } else { - $this->_jumpBlock(ceil(($v_header['size']/512))); - } - } - - return NULL; - } - // }}} - - // {{{ _extractList() - function _extractList($p_path, &$p_list_detail, $p_mode, - $p_file_list, $p_remove_path) - { - $v_result=true; - $v_nb = 0; - $v_extract_all = true; - $v_listing = false; - - $p_path = $this->_translateWinPath($p_path, false); - if ($p_path == '' || (substr($p_path, 0, 1) != '/' - && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) { - $p_path = "./".$p_path; - } - $p_remove_path = $this->_translateWinPath($p_remove_path); - - // ----- Look for path to remove format (should end by /) - if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) - $p_remove_path .= '/'; - $p_remove_path_size = strlen($p_remove_path); - - switch ($p_mode) { - case "complete" : - $v_extract_all = TRUE; - $v_listing = FALSE; - break; - case "partial" : - $v_extract_all = FALSE; - $v_listing = FALSE; - break; - case "list" : - $v_extract_all = FALSE; - $v_listing = TRUE; - break; - default : - $this->_error('Invalid extract mode ('.$p_mode.')'); - return false; + while (strlen($v_binary_data = $this->_readBlock()) != 0) { + if (!$this->_readHeader($v_binary_data, $v_header)) { + return null; + } + + if ($v_header['filename'] == '') { + continue; + } + + switch ($v_header['typeflag']) { + case 'L': { + if (!$this->_readLongHeader($v_header)) { + return null; + } + } break; + + case 'K': { + $v_link_header = $v_header; + if (!$this->_readLongHeader($v_link_header)) { + return null; + } + $v_header['link'] = $v_link_header['filename']; + } break; + } + + if ($v_header['filename'] == $p_filename) { + if ($v_header['typeflag'] == "5") { + $this->_error( + 'Unable to extract in string a directory ' + . 'entry {' . $v_header['filename'] . '}' + ); + return null; + } else { + $n = floor($v_header['size'] / 512); + for ($i = 0; $i < $n; $i++) { + $v_result_str .= $this->_readBlock(); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_result_str .= substr( + $v_content, + 0, + ($v_header['size'] % 512) + ); + } + return $v_result_str; + } + } else { + $this->_jumpBlock(ceil(($v_header['size'] / 512))); + } + } + + return null; } - clearstatcache(); + /** + * @param string $p_path + * @param string $p_list_detail + * @param string $p_mode + * @param string $p_file_list + * @param string $p_remove_path + * @param bool $p_preserve + * @return bool + */ + public function _extractList( + $p_path, + &$p_list_detail, + $p_mode, + $p_file_list, + $p_remove_path, + $p_preserve = false + ) { + $v_result = true; + $v_nb = 0; + $v_extract_all = true; + $v_listing = false; + + $p_path = $this->_translateWinPath($p_path, false); + if ($p_path == '' || (substr($p_path, 0, 1) != '/' + && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':')) + ) { + $p_path = "./" . $p_path; + } + $p_remove_path = $this->_translateWinPath($p_remove_path); + + // ----- Look for path to remove format (should end by /) + if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) { + $p_remove_path .= '/'; + } + $p_remove_path_size = strlen($p_remove_path); + + switch ($p_mode) { + case "complete" : + $v_extract_all = true; + $v_listing = false; + break; + case "partial" : + $v_extract_all = false; + $v_listing = false; + break; + case "list" : + $v_extract_all = false; + $v_listing = true; + break; + default : + $this->_error('Invalid extract mode (' . $p_mode . ')'); + return false; + } - while (strlen($v_binary_data = $this->_readBlock()) != 0) - { - $v_extract_file = FALSE; - $v_extraction_stopped = 0; + clearstatcache(); - if (!$this->_readHeader($v_binary_data, $v_header)) - return false; + while (strlen($v_binary_data = $this->_readBlock()) != 0) { + $v_extract_file = false; + $v_extraction_stopped = 0; + + if (!$this->_readHeader($v_binary_data, $v_header)) { + return false; + } + + if ($v_header['filename'] == '') { + continue; + } + + switch ($v_header['typeflag']) { + case 'L': { + if (!$this->_readLongHeader($v_header)) { + return null; + } + } break; + + case 'K': { + $v_link_header = $v_header; + if (!$this->_readLongHeader($v_link_header)) { + return null; + } + $v_header['link'] = $v_link_header['filename']; + } break; + } + + // ignore extended / pax headers + if ($v_header['typeflag'] == 'x' || $v_header['typeflag'] == 'g') { + $this->_jumpBlock(ceil(($v_header['size'] / 512))); + continue; + } + + if ((!$v_extract_all) && (is_array($p_file_list))) { + // ----- By default no unzip if the file is not found + $v_extract_file = false; + + for ($i = 0; $i < sizeof($p_file_list); $i++) { + // ----- Look if it is a directory + if (substr($p_file_list[$i], -1) == '/') { + // ----- Look if the directory is in the filename path + if ((strlen($v_header['filename']) > strlen($p_file_list[$i])) + && (substr($v_header['filename'], 0, strlen($p_file_list[$i])) + == $p_file_list[$i]) + ) { + $v_extract_file = true; + break; + } + } // ----- It is a file, so compare the file names + elseif ($p_file_list[$i] == $v_header['filename']) { + $v_extract_file = true; + break; + } + } + } else { + $v_extract_file = true; + } + + // ----- Look if this file need to be extracted + if (($v_extract_file) && (!$v_listing)) { + if (($p_remove_path != '') + && (substr($v_header['filename'] . '/', 0, $p_remove_path_size) + == $p_remove_path) + ) { + $v_header['filename'] = substr( + $v_header['filename'], + $p_remove_path_size + ); + if ($v_header['filename'] == '') { + continue; + } + } + if (($p_path != './') && ($p_path != '/')) { + while (substr($p_path, -1) == '/') { + $p_path = substr($p_path, 0, strlen($p_path) - 1); + } - if ($v_header['filename'] == '') { - continue; - } - - // ----- Look for long filename - if ($v_header['typeflag'] == 'L') { - if (!$this->_readLongHeader($v_header)) - return false; - } - - if ((!$v_extract_all) && (is_array($p_file_list))) { - // ----- By default no unzip if the file is not found - $v_extract_file = false; - - for ($i=0; $i<sizeof($p_file_list); $i++) { - // ----- Look if it is a directory - if (substr($p_file_list[$i], -1) == '/') { - // ----- Look if the directory is in the filename path - if ((strlen($v_header['filename']) > strlen($p_file_list[$i])) - && (substr($v_header['filename'], 0, strlen($p_file_list[$i])) - == $p_file_list[$i])) { - $v_extract_file = TRUE; - break; - } - } - - // ----- It is a file, so compare the file names - elseif ($p_file_list[$i] == $v_header['filename']) { - $v_extract_file = TRUE; - break; - } - } - } else { - $v_extract_file = TRUE; - } - - // ----- Look if this file need to be extracted - if (($v_extract_file) && (!$v_listing)) - { - if (($p_remove_path != '') - && (substr($v_header['filename'], 0, $p_remove_path_size) - == $p_remove_path)) - $v_header['filename'] = substr($v_header['filename'], - $p_remove_path_size); - if (($p_path != './') && ($p_path != '/')) { - while (substr($p_path, -1) == '/') - $p_path = substr($p_path, 0, strlen($p_path)-1); - - if (substr($v_header['filename'], 0, 1) == '/') - $v_header['filename'] = $p_path.$v_header['filename']; - else - $v_header['filename'] = $p_path.'/'.$v_header['filename']; - } - if (file_exists($v_header['filename'])) { - if ( (@is_dir($v_header['filename'])) - && ($v_header['typeflag'] == '')) { - $this->_error('File '.$v_header['filename'] - .' already exists as a directory'); - return false; - } - if ( ($this->_isArchive($v_header['filename'])) - && ($v_header['typeflag'] == "5")) { - $this->_error('Directory '.$v_header['filename'] - .' already exists as a file'); - return false; - } - if (!is_writeable($v_header['filename'])) { - $this->_error('File '.$v_header['filename'] - .' already exists and is write protected'); - return false; - } - if (filemtime($v_header['filename']) > $v_header['mtime']) { - // To be completed : An error or silent no replace ? - } - } - - // ----- Check the directory availability and create it if necessary - elseif (($v_result - = $this->_dirCheck(($v_header['typeflag'] == "5" - ?$v_header['filename'] - :dirname($v_header['filename'])))) != 1) { - $this->_error('Unable to create path for '.$v_header['filename']); - return false; - } - - if ($v_extract_file) { - if ($v_header['typeflag'] == "5") { - if (!@file_exists($v_header['filename'])) { - // Drupal integration. - // Changed the code to use drupal_mkdir() instead of mkdir(). - if (!@drupal_mkdir($v_header['filename'], 0777)) { - $this->_error('Unable to create directory {' - .$v_header['filename'].'}'); + if (substr($v_header['filename'], 0, 1) == '/') { + $v_header['filename'] = $p_path . $v_header['filename']; + } else { + $v_header['filename'] = $p_path . '/' . $v_header['filename']; + } + } + if (file_exists($v_header['filename'])) { + if ((@is_dir($v_header['filename'])) + && ($v_header['typeflag'] == '') + ) { + $this->_error( + 'File ' . $v_header['filename'] + . ' already exists as a directory' + ); + return false; + } + if (($this->_isArchive($v_header['filename'])) + && ($v_header['typeflag'] == "5") + ) { + $this->_error( + 'Directory ' . $v_header['filename'] + . ' already exists as a file' + ); + return false; + } + if (!is_writeable($v_header['filename'])) { + $this->_error( + 'File ' . $v_header['filename'] + . ' already exists and is write protected' + ); + return false; + } + if (filemtime($v_header['filename']) > $v_header['mtime']) { + // To be completed : An error or silent no replace ? + } + } // ----- Check the directory availability and create it if necessary + elseif (($v_result + = $this->_dirCheck( + ($v_header['typeflag'] == "5" + ? $v_header['filename'] + : dirname($v_header['filename'])) + )) != 1 + ) { + $this->_error('Unable to create path for ' . $v_header['filename']); return false; } + + if ($v_extract_file) { + if ($v_header['typeflag'] == "5") { + if (!@file_exists($v_header['filename'])) { + if (!@mkdir($v_header['filename'], 0777)) { + $this->_error( + 'Unable to create directory {' + . $v_header['filename'] . '}' + ); + return false; + } + } + } elseif ($v_header['typeflag'] == "2") { + if (@file_exists($v_header['filename'])) { + @drupal_unlink($v_header['filename']); + } + if (!@symlink($v_header['link'], $v_header['filename'])) { + $this->_error( + 'Unable to extract symbolic link {' + . $v_header['filename'] . '}' + ); + return false; + } + } else { + if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) { + $this->_error( + 'Error while opening {' . $v_header['filename'] + . '} in write binary mode' + ); + return false; + } else { + $n = floor($v_header['size'] / 512); + for ($i = 0; $i < $n; $i++) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, 512); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, ($v_header['size'] % 512)); + } + + @fclose($v_dest_file); + + if ($p_preserve) { + @chown($v_header['filename'], $v_header['uid']); + @chgrp($v_header['filename'], $v_header['gid']); + } + + // ----- Change the file mode, mtime + @touch($v_header['filename'], $v_header['mtime']); + if ($v_header['mode'] & 0111) { + // make file executable, obey umask + $mode = fileperms($v_header['filename']) | (~umask() & 0111); + @chmod($v_header['filename'], $mode); + } + } + + // ----- Check the file size + clearstatcache(); + if (!is_file($v_header['filename'])) { + $this->_error( + 'Extracted file ' . $v_header['filename'] + . 'does not exist. Archive may be corrupted.' + ); + return false; + } + + $filesize = filesize($v_header['filename']); + if ($filesize != $v_header['size']) { + $this->_error( + 'Extracted file ' . $v_header['filename'] + . ' does not have the correct file size \'' + . $filesize + . '\' (' . $v_header['size'] + . ' expected). Archive may be corrupted.' + ); + return false; + } + } + } else { + $this->_jumpBlock(ceil(($v_header['size'] / 512))); + } + } else { + $this->_jumpBlock(ceil(($v_header['size'] / 512))); } - } elseif ($v_header['typeflag'] == "2") { - if (@file_exists($v_header['filename'])) { - @drupal_unlink($v_header['filename']); - } - if (!@symlink($v_header['link'], $v_header['filename'])) { - $this->_error('Unable to extract symbolic link {' - .$v_header['filename'].'}'); - return false; - } - } else { - if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) { - $this->_error('Error while opening {'.$v_header['filename'] - .'} in write binary mode'); - return false; - } else { - $n = floor($v_header['size']/512); - for ($i=0; $i<$n; $i++) { - $v_content = $this->_readBlock(); - fwrite($v_dest_file, $v_content, 512); - } - if (($v_header['size'] % 512) != 0) { - $v_content = $this->_readBlock(); - fwrite($v_dest_file, $v_content, ($v_header['size'] % 512)); - } - - @fclose($v_dest_file); - - // ----- Change the file mode, mtime - @touch($v_header['filename'], $v_header['mtime']); - if ($v_header['mode'] & 0111) { - // make file executable, obey umask - $mode = fileperms($v_header['filename']) | (~umask() & 0111); - @chmod($v_header['filename'], $mode); - } - } - - // ----- Check the file size - clearstatcache(); - if (filesize($v_header['filename']) != $v_header['size']) { - $this->_error('Extracted file '.$v_header['filename'] - .' does not have the correct file size \'' - .filesize($v_header['filename']) - .'\' ('.$v_header['size'] - .' expected). Archive may be corrupted.'); - return false; - } - } - } else { - $this->_jumpBlock(ceil(($v_header['size']/512))); - } - } else { - $this->_jumpBlock(ceil(($v_header['size']/512))); - } - - /* TBC : Seems to be unused ... - if ($this->_compress) - $v_end_of_file = @gzeof($this->_file); - else - $v_end_of_file = @feof($this->_file); - */ - if ($v_listing || $v_extract_file || $v_extraction_stopped) { - // ----- Log extracted files - if (($v_file_dir = dirname($v_header['filename'])) - == $v_header['filename']) - $v_file_dir = ''; - if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) - $v_file_dir = '/'; + /* TBC : Seems to be unused ... + if ($this->_compress) + $v_end_of_file = @gzeof($this->_file); + else + $v_end_of_file = @feof($this->_file); + */ - $p_list_detail[$v_nb++] = $v_header; - if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) { - return true; + if ($v_listing || $v_extract_file || $v_extraction_stopped) { + // ----- Log extracted files + if (($v_file_dir = dirname($v_header['filename'])) + == $v_header['filename'] + ) { + $v_file_dir = ''; + } + if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) { + $v_file_dir = '/'; + } + + $p_list_detail[$v_nb++] = $v_header; + if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) { + return true; + } + } } - } - } return true; } - // }}} - // {{{ _openAppend() - function _openAppend() + /** + * @return bool + */ + public function _openAppend() { - if (filesize($this->_tarname) == 0) - return $this->_openWrite(); + if (filesize($this->_tarname) == 0) { + return $this->_openWrite(); + } if ($this->_compress) { $this->_close(); - if (!@rename($this->_tarname, $this->_tarname.".tmp")) { - $this->_error('Error while renaming \''.$this->_tarname - .'\' to temporary file \''.$this->_tarname - .'.tmp\''); + if (!@rename($this->_tarname, $this->_tarname . ".tmp")) { + $this->_error( + 'Error while renaming \'' . $this->_tarname + . '\' to temporary file \'' . $this->_tarname + . '.tmp\'' + ); return false; } - if ($this->_compress_type == 'gz') - $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb"); - elseif ($this->_compress_type == 'bz2') - $v_temp_tar = @bzopen($this->_tarname.".tmp", "r"); + if ($this->_compress_type == 'gz') { + $v_temp_tar = @gzopen($this->_tarname . ".tmp", "rb"); + } elseif ($this->_compress_type == 'bz2') { + $v_temp_tar = @bzopen($this->_tarname . ".tmp", "r"); + } elseif ($this->_compress_type == 'lzma2') { + $v_temp_tar = @xzopen($this->_tarname . ".tmp", "r"); + } + if ($v_temp_tar == 0) { - $this->_error('Unable to open file \''.$this->_tarname - .'.tmp\' in binary read mode'); - @rename($this->_tarname.".tmp", $this->_tarname); + $this->_error( + 'Unable to open file \'' . $this->_tarname + . '.tmp\' in binary read mode' + ); + @rename($this->_tarname . ".tmp", $this->_tarname); return false; } if (!$this->_openWrite()) { - @rename($this->_tarname.".tmp", $this->_tarname); + @rename($this->_tarname . ".tmp", $this->_tarname); return false; } if ($this->_compress_type == 'gz') { + $end_blocks = 0; + while (!@gzeof($v_temp_tar)) { $v_buffer = @gzread($v_temp_tar, 512); - if ($v_buffer == ARCHIVE_TAR_END_BLOCK) { + if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { + $end_blocks++; // do not copy end blocks, we will re-make them // after appending continue; + } elseif ($end_blocks > 0) { + for ($i = 0; $i < $end_blocks; $i++) { + $this->_writeBlock(ARCHIVE_TAR_END_BLOCK); + } + $end_blocks = 0; } $v_binary_data = pack("a512", $v_buffer); $this->_writeBlock($v_binary_data); } @gzclose($v_temp_tar); - } - elseif ($this->_compress_type == 'bz2') { + } elseif ($this->_compress_type == 'bz2') { + $end_blocks = 0; + while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) { - if ($v_buffer == ARCHIVE_TAR_END_BLOCK) { + if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { + $end_blocks++; + // do not copy end blocks, we will re-make them + // after appending continue; + } elseif ($end_blocks > 0) { + for ($i = 0; $i < $end_blocks; $i++) { + $this->_writeBlock(ARCHIVE_TAR_END_BLOCK); + } + $end_blocks = 0; } $v_binary_data = pack("a512", $v_buffer); $this->_writeBlock($v_binary_data); } @bzclose($v_temp_tar); - } + } elseif ($this->_compress_type == 'lzma2') { + $end_blocks = 0; + + while (strlen($v_buffer = @xzread($v_temp_tar, 512)) > 0) { + if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { + $end_blocks++; + // do not copy end blocks, we will re-make them + // after appending + continue; + } elseif ($end_blocks > 0) { + for ($i = 0; $i < $end_blocks; $i++) { + $this->_writeBlock(ARCHIVE_TAR_END_BLOCK); + } + $end_blocks = 0; + } + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } - if (!@drupal_unlink($this->_tarname.".tmp")) { - $this->_error('Error while deleting temporary file \'' - .$this->_tarname.'.tmp\''); + @xzclose($v_temp_tar); } + if (!@drupal_unlink($this->_tarname . ".tmp")) { + $this->_error( + 'Error while deleting temporary file \'' + . $this->_tarname . '.tmp\'' + ); + } } else { // ----- For not compressed tar, just add files before the last - // one or two 512 bytes block - if (!$this->_openReadWrite()) - return false; + // one or two 512 bytes block + if (!$this->_openReadWrite()) { + return false; + } clearstatcache(); $v_size = filesize($this->_tarname); @@ -1760,32 +2376,34 @@ fseek($this->_file, $v_size - 1024); if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { fseek($this->_file, $v_size - 1024); - } - elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { + } elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { fseek($this->_file, $v_size - 512); } } return true; } - // }}} - // {{{ _append() - function _append($p_filelist, $p_add_dir='', $p_remove_dir='') + /** + * @param $p_filelist + * @param string $p_add_dir + * @param string $p_remove_dir + * @return bool + */ + public function _append($p_filelist, $p_add_dir = '', $p_remove_dir = '') { - if (!$this->_openAppend()) + if (!$this->_openAppend()) { return false; + } - if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) - $this->_writeFooter(); + if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) { + $this->_writeFooter(); + } $this->_close(); return true; } - // }}} - - // {{{ _dirCheck() /** * Check if a directory exists and create it (including parent @@ -1793,24 +2411,25 @@ * * @param string $p_dir directory to check * - * @return bool TRUE if the directory exists or was created + * @return bool true if the directory exists or was created */ - function _dirCheck($p_dir) + public function _dirCheck($p_dir) { clearstatcache(); - if ((@is_dir($p_dir)) || ($p_dir == '')) + if ((@is_dir($p_dir)) || ($p_dir == '')) { return true; + } $p_parent_dir = dirname($p_dir); if (($p_parent_dir != $p_dir) && ($p_parent_dir != '') && - (!$this->_dirCheck($p_parent_dir))) - return false; + (!$this->_dirCheck($p_parent_dir)) + ) { + return false; + } - // Drupal integration. - // Changed the code to use drupal_mkdir() instead of mkdir(). - if (!@drupal_mkdir($p_dir, 0777)) { + if (!@mkdir($p_dir, 0777)) { $this->_error("Unable to create directory '$p_dir'"); return false; } @@ -1818,10 +2437,6 @@ return true; } - // }}} - - // {{{ _pathReduction() - /** * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar", * rand emove double slashes. @@ -1829,11 +2444,8 @@ * @param string $p_dir path to reduce * * @return string reduced path - * - * @access private - * */ - function _pathReduction($p_dir) + private function _pathReduction($p_dir) { $v_result = ''; @@ -1843,50 +2455,57 @@ $v_list = explode('/', $p_dir); // ----- Study directories from last to first - for ($i=sizeof($v_list)-1; $i>=0; $i--) { + for ($i = sizeof($v_list) - 1; $i >= 0; $i--) { // ----- Look for current path if ($v_list[$i] == ".") { // ----- Ignore this directory // Should be the first $i=0, but no check is done - } - else if ($v_list[$i] == "..") { - // ----- Ignore it and ignore the $i-1 - $i--; - } - else if ( ($v_list[$i] == '') - && ($i!=(sizeof($v_list)-1)) - && ($i!=0)) { - // ----- Ignore only the double '//' in path, - // but not the first and last / } else { - $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/' - .$v_result:''); + if ($v_list[$i] == "..") { + // ----- Ignore it and ignore the $i-1 + $i--; + } else { + if (($v_list[$i] == '') + && ($i != (sizeof($v_list) - 1)) + && ($i != 0) + ) { + // ----- Ignore only the double '//' in path, + // but not the first and last / + } else { + $v_result = $v_list[$i] . ($i != (sizeof($v_list) - 1) ? '/' + . $v_result : ''); + } + } } } } - $v_result = strtr($v_result, '\\', '/'); + + if (defined('OS_WINDOWS') && OS_WINDOWS) { + $v_result = strtr($v_result, '\\', '/'); + } + return $v_result; } - // }}} - - // {{{ _translateWinPath() - function _translateWinPath($p_path, $p_remove_disk_letter=true) + /** + * @param $p_path + * @param bool $p_remove_disk_letter + * @return string + */ + public function _translateWinPath($p_path, $p_remove_disk_letter = true) { - if (defined('OS_WINDOWS') && OS_WINDOWS) { - // ----- Look for potential disk letter - if ( ($p_remove_disk_letter) - && (($v_position = strpos($p_path, ':')) != false)) { - $p_path = substr($p_path, $v_position+1); - } - // ----- Change potential windows directory separator - if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) { - $p_path = strtr($p_path, '\\', '/'); - } - } - return $p_path; + if (defined('OS_WINDOWS') && OS_WINDOWS) { + // ----- Look for potential disk letter + if (($p_remove_disk_letter) + && (($v_position = strpos($p_path, ':')) != false) + ) { + $p_path = substr($p_path, $v_position + 1); + } + // ----- Change potential windows directory separator + if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) { + $p_path = strtr($p_path, '\\', '/'); + } + } + return $p_path; } - // }}} - } -?> diff -Naur drupal-7.41/modules/system/system.test drupal-7.66/modules/system/system.test --- drupal-7.41/modules/system/system.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/system/system.test 2019-04-17 22:20:46.000000000 +0200 @@ -726,7 +726,7 @@ // Block a valid IP address. $edit = array(); - $edit['ip'] = '192.168.1.1'; + $edit['ip'] = '1.2.3.3'; $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add')); $ip = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $edit['ip']))->fetchField(); $this->assertTrue($ip, t('IP address found in database.')); @@ -734,7 +734,7 @@ // Try to block an IP address that's already blocked. $edit = array(); - $edit['ip'] = '192.168.1.1'; + $edit['ip'] = '1.2.3.3'; $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add')); $this->assertText(t('This IP address is already blocked.')); @@ -770,6 +770,25 @@ // $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Save')); // $this->assertText(t('You may not block your own IP address.')); } + + /** + * Test duplicate IP addresses are not present in the 'blocked_ips' table. + */ + function testDuplicateIpAddress() { + drupal_static_reset('ip_address'); + $submit_ip = $_SERVER['REMOTE_ADDR'] = '192.168.1.1'; + system_block_ip_action(); + system_block_ip_action(); + $ip_count = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->rowCount(); + $this->assertEqual('1', $ip_count); + drupal_static_reset('ip_address'); + $submit_ip = $_SERVER['REMOTE_ADDR'] = ' '; + system_block_ip_action(); + system_block_ip_action(); + system_block_ip_action(); + $ip_count = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->rowCount(); + $this->assertEqual('1', $ip_count); + } } class CronRunTestCase extends DrupalWebTestCase { @@ -905,6 +924,29 @@ $result = variable_get('common_test_cron'); $this->assertEqual($result, 'success', 'Cron correctly handles exceptions thrown during hook_cron() invocations.'); } + + /** + * Tests that hook_flush_caches() is not invoked on every single cron run. + * + * @see system_cron() + */ + public function testCronCacheExpiration() { + module_enable(array('system_cron_test')); + variable_del('system_cron_test_flush_caches'); + + // Invoke cron the first time: hook_flush_caches() should be called and then + // get cached. + drupal_cron_run(); + $this->assertEqual(variable_get('system_cron_test_flush_caches'), 1, 'hook_flush_caches() was invoked the first time.'); + $cache = cache_get('system_cache_tables'); + $this->assertEqual(empty($cache), FALSE, 'Cache is filled with cache table data.'); + + // Run cron again and ensure that hook_flush_caches() is not called. + variable_del('system_cron_test_flush_caches'); + drupal_cron_run(); + $this->assertNull(variable_get('system_cron_test_flush_caches'), 'hook_flush_caches() was not invoked the second time.'); + } + } /** @@ -1327,7 +1369,23 @@ $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time/formats', array('absolute' => TRUE)), 'Correct page redirection.'); $this->assertText(t('Custom date format updated.'), 'Custom date format successfully updated.'); + // Check that ajax callback is protected by CSRF token. + $this->drupalGet('admin/config/regional/date-time/formats/lookup', array('query' => array('format' => 'Y m d'))); + $this->assertResponse(403, 'Access denied with no token'); + $this->drupalGet('admin/config/regional/date-time/formats/lookup', array('query' => array('token' => 'invalid', 'format' => 'Y m d'))); + $this->assertResponse(403, 'Access denied with invalid token'); + $this->drupalGet('admin/config/regional/date-time/formats'); + $this->clickLink(t('edit')); + $settings = $this->drupalGetSettings(); + $lookup_url = $settings['dateTime']['date-format']['lookup']; + preg_match('/token=([^&]+)/', $lookup_url, $matches); + $this->assertFalse(empty($matches[1]), 'Found token value'); + $this->drupalGet('admin/config/regional/date-time/formats/lookup', array('query' => array('token' => $matches[1], 'format' => 'Y m d'))); + $this->assertResponse(200, 'Access allowed with valid token'); + $this->assertText(format_date(time(), 'custom', 'Y m d')); + // Delete custom date format. + $this->drupalGet('admin/config/regional/date-time/formats'); $this->clickLink(t('delete')); $this->drupalPost($this->getUrl(), array(), t('Remove')); $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time/formats', array('absolute' => TRUE)), 'Correct page redirection.'); @@ -1403,6 +1461,60 @@ } } +/** + * Tests date format configuration. + */ +class DateFormatTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Date format', + 'description' => 'Test date format configuration and defaults.', + 'group' => 'System', + ); + } + + function setUp() { + parent::setUp(); + + // Create admin user and log in admin user. + $this->admin_user = $this->drupalCreateUser(array('administer site configuration')); + $this->drupalLogin($this->admin_user); + } + + /** + * Test the default date type formats are consistent. + */ + function testDefaultDateFormats() { + // These are the default format values from format_date(). + $default_formats = array( + 'short' => 'm/d/Y - H:i', + 'medium' => 'D, m/d/Y - H:i', + 'long' => 'l, F j, Y - H:i', + ); + + // Clear the date format variables. + variable_del('date_format_short'); + variable_del('date_format_medium'); + variable_del('date_format_long'); + + $this->drupalGet('admin/config/regional/date-time'); + + foreach ($default_formats as $format_name => $format_value) { + $id = 'edit-date-format-' . $format_name; + // Check that the configuration fields match the default format. + $this->assertOptionSelected( + $id, + $format_value, + format_string('The @type format type matches the expected format @format.', + array( + '@type' => $format_name, + '@format' => $format_value, + ) + )); + } + } +} + class PageTitleFiltering extends DrupalWebTestCase { protected $content_user; protected $saved_title; @@ -1832,6 +1944,30 @@ $this->assertEqual($elements[0]['src'], file_create_url($uploaded_filename)); } + + /** + * Test the individual per-theme settings form. + */ + function testPerThemeSettings() { + // Enable the test theme and the module that controls it. Clear caches in + // between so that the module's hook_system_theme_info() implementation is + // correctly registered. + module_enable(array('theme_test')); + drupal_flush_all_caches(); + theme_enable(array('test_theme')); + + // Test that the theme-specific settings form can be saved and that the + // theme-specific checkbox is checked and unchecked as appropriate. + $this->drupalGet('admin/appearance/settings/test_theme'); + $this->assertNoFieldChecked('edit-test-theme-checkbox', 'The test_theme_checkbox setting is unchecked.'); + $this->drupalPost(NULL, array('test_theme_checkbox' => TRUE), t('Save configuration')); + $this->assertText('The test theme setting was saved.'); + $this->assertFieldChecked('edit-test-theme-checkbox', 'The test_theme_checkbox setting is checked.'); + $this->drupalPost(NULL, array('test_theme_checkbox' => FALSE), t('Save configuration')); + $this->assertText('The test theme setting was saved.'); + $this->assertNoFieldChecked('edit-test-theme-checkbox', 'The test_theme_checkbox setting is unchecked.'); + } + /** * Test the administration theme functionality. */ @@ -2316,6 +2452,20 @@ } /** + * Tests that there are no pending updates for the first test method. + */ + function testNoPendingUpdates() { + // Ensure that for the first test method in a class, there are no pending + // updates. This tests a drupal_get_schema_versions() bug that previously + // led to the wrong schema version being recorded for the initial install + // of a child site during automated testing. + $this->drupalLogin($this->update_user); + $this->drupalGet($this->update_url, array('external' => TRUE)); + $this->drupalPost(NULL, array(), t('Continue')); + $this->assertText(t('No pending updates.'), 'End of update process was reached.'); + } + + /** * Tests access to the update script. */ function testUpdateAccess() { @@ -2396,6 +2546,12 @@ $this->assertText('This is a requirements error provided by the update_script_test module.'); $this->clickLink('try again'); $this->assertText('This is a requirements error provided by the update_script_test module.'); + + // Check if the optional 'value' key displays without a notice. + variable_set('update_script_test_requirement_type', REQUIREMENT_INFO); + $this->drupalGet($this->update_url, array('external' => TRUE)); + $this->assertText('This is a requirements info provided by the update_script_test module.'); + $this->assertNoText('Notice: Undefined index: value in theme_status_report()'); } /** diff -Naur drupal-7.41/modules/system/system.updater.inc drupal-7.66/modules/system/system.updater.inc --- drupal-7.41/modules/system/system.updater.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/system/system.updater.inc 2019-04-17 22:20:46.000000000 +0200 @@ -24,7 +24,7 @@ * found on your system, and if there was a copy in sites/all, we'd see it. */ public function getInstallDirectory() { - if ($relative_path = drupal_get_path('module', $this->name)) { + if ($this->isInstalled() && ($relative_path = drupal_get_path('module', $this->name))) { $relative_path = dirname($relative_path); } else { @@ -34,7 +34,7 @@ } public function isInstalled() { - return (bool) drupal_get_path('module', $this->name); + return (bool) drupal_get_filename('module', $this->name, NULL, FALSE); } public static function canUpdateDirectory($directory) { @@ -109,7 +109,7 @@ * found on your system, and if there was a copy in sites/all, we'd see it. */ public function getInstallDirectory() { - if ($relative_path = drupal_get_path('theme', $this->name)) { + if ($this->isInstalled() && ($relative_path = drupal_get_path('theme', $this->name))) { $relative_path = dirname($relative_path); } else { @@ -119,7 +119,7 @@ } public function isInstalled() { - return (bool) drupal_get_path('theme', $this->name); + return (bool) drupal_get_filename('theme', $this->name, NULL, FALSE); } static function canUpdateDirectory($directory) { diff -Naur drupal-7.41/modules/system/tests/cron_queue_test.info drupal-7.66/modules/system/tests/cron_queue_test.info --- drupal-7.41/modules/system/tests/cron_queue_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/system/tests/cron_queue_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/system/tests/system_cron_test.info drupal-7.66/modules/system/tests/system_cron_test.info --- drupal-7.41/modules/system/tests/system_cron_test.info 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/modules/system/tests/system_cron_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -0,0 +1,11 @@ +name = System Cron Test +description = 'Support module for testing the system_cron().' +package = Testing +version = VERSION +core = 7.x +hidden = TRUE + +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" +project = "drupal" +datestamp = "1555533576" diff -Naur drupal-7.41/modules/system/tests/system_cron_test.module drupal-7.66/modules/system/tests/system_cron_test.module --- drupal-7.41/modules/system/tests/system_cron_test.module 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/modules/system/tests/system_cron_test.module 2019-04-17 22:20:46.000000000 +0200 @@ -0,0 +1,15 @@ +<?php + +/** + * @file + * Helper module for CronRunTestCase::testCronCacheExpiration(). + */ + +/** + * Implements hook_flush_caches(). + */ +function system_cron_test_flush_caches() { + // Set a variable to indicate that this hook was invoked. + variable_set('system_cron_test_flush_caches', 1); + return array(); +} diff -Naur drupal-7.41/modules/taxonomy/taxonomy.info drupal-7.66/modules/taxonomy/taxonomy.info --- drupal-7.41/modules/taxonomy/taxonomy.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/taxonomy/taxonomy.info 2019-04-17 22:39:36.000000000 +0200 @@ -8,8 +8,7 @@ files[] = taxonomy.test configure = admin/structure/taxonomy -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/taxonomy/taxonomy.install drupal-7.66/modules/taxonomy/taxonomy.install --- drupal-7.41/modules/taxonomy/taxonomy.install 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/taxonomy/taxonomy.install 2019-04-17 22:20:46.000000000 +0200 @@ -492,6 +492,7 @@ 'bundle' => $bundle->type, 'settings' => array(), 'description' => 'Debris left over after upgrade from Drupal 6', + 'required' => FALSE, 'widget' => array( 'type' => 'taxonomy_autocomplete', 'module' => 'taxonomy', @@ -557,7 +558,7 @@ // of term references stored so far for the current revision, which // provides the delta value for each term reference data insert. The // deltas are reset for each new revision. - + $conditions = array( 'type' => 'taxonomy_term_reference', 'deleted' => 0, diff -Naur drupal-7.41/modules/taxonomy/taxonomy.module drupal-7.66/modules/taxonomy/taxonomy.module --- drupal-7.41/modules/taxonomy/taxonomy.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/taxonomy/taxonomy.module 2019-04-17 22:20:46.000000000 +0200 @@ -25,7 +25,7 @@ $output .= '<h3>' . t('Uses') . '</h3>'; $output .= '<dl>'; $output .= '<dt>' . t('Creating vocabularies') . '</dt>'; - $output .= '<dd>' . t('Users with sufficient <a href="@perm">permissions</a> can create <em>vocabularies</em> and <em>terms</em> through the <a href="@taxo">Taxonomy page</a>. The page listing the terms provides a drag-and-drop interface for controlling the order of the terms and sub-terms within a vocabulary, in a hierarchical fashion. A <em>controlled vocabulary</em> classifying music by genre with terms and sub-terms could look as follows:', array('@taxo' => url('admin/structure/taxonomy'), '@perm' => url('admin/people/permissions', array('fragment'=>'module-taxonomy')))); + $output .= '<dd>' . t('Users with sufficient <a href="@perm">permissions</a> can create <em>vocabularies</em> and <em>terms</em> through the <a href="@taxo">Taxonomy page</a>. The page listing the terms provides a drag-and-drop interface for controlling the order of the terms and sub-terms within a vocabulary, in a hierarchical fashion. A <em>controlled vocabulary</em> classifying music by genre with terms and sub-terms could look as follows:', array('@taxo' => url('admin/structure/taxonomy'), '@perm' => url('admin/people/permissions', array('fragment' => 'module-taxonomy')))); $output .= '<ul><li>' . t('<em>vocabulary</em>: Music') . '</li>'; $output .= '<ul><li>' . t('<em>term</em>: Jazz') . '</li>'; $output .= '<ul><li>' . t('<em>sub-term</em>: Swing') . '</li>'; @@ -283,6 +283,8 @@ 'title' => 'Taxonomy term', 'title callback' => 'taxonomy_term_title', 'title arguments' => array(2), + // The page callback also invokes drupal_set_title() in case + // the menu router's title is overridden by a menu link. 'page callback' => 'taxonomy_term_page', 'page arguments' => array(2), 'access arguments' => array('access content'), @@ -1023,7 +1025,7 @@ $query->join('taxonomy_term_hierarchy', 'h', 'h.parent = t.tid'); $query->addField('t', 'tid'); $query->condition('h.tid', $tid); - $query->addTag('term_access'); + $query->addTag('taxonomy_term_access'); $query->orderBy('t.weight'); $query->orderBy('t.name'); $tids = $query->execute()->fetchCol(); @@ -1081,7 +1083,7 @@ if ($vid) { $query->condition('t.vid', $vid); } - $query->addTag('term_access'); + $query->addTag('taxonomy_term_access'); $query->orderBy('t.weight'); $query->orderBy('t.name'); $tids = $query->execute()->fetchCol(); @@ -1129,7 +1131,7 @@ $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid'); $result = $query ->addTag('translatable') - ->addTag('term_access') + ->addTag('taxonomy_term_access') ->fields('t') ->fields('h', array('parent')) ->condition('t.vid', $vid) @@ -1249,7 +1251,7 @@ protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { $query = parent::buildQuery($ids, $conditions, $revision_id); $query->addTag('translatable'); - $query->addTag('term_access'); + $query->addTag('taxonomy_term_access'); // When name is passed as a condition use LIKE. if (isset($conditions['name'])) { $query_conditions = &$query->conditions(); @@ -1714,13 +1716,15 @@ } /** - * Title callback for term pages. + * Title callback: Returns the title of the taxonomy term. * * @param $term * A term object. * * @return - * The term name to be used as the page title. + * An unsanitized string that is the title of the taxonomy term. + * + * @see taxonomy_menu() */ function taxonomy_term_title($term) { return $term->name; diff -Naur drupal-7.41/modules/taxonomy/taxonomy.pages.inc drupal-7.66/modules/taxonomy/taxonomy.pages.inc --- drupal-7.41/modules/taxonomy/taxonomy.pages.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/taxonomy/taxonomy.pages.inc 2019-04-17 22:20:46.000000000 +0200 @@ -150,7 +150,7 @@ $query = db_select('taxonomy_term_data', 't'); $query->addTag('translatable'); - $query->addTag('term_access'); + $query->addTag('taxonomy_term_access'); // Do not select already entered terms. if (!empty($tags_typed)) { diff -Naur drupal-7.41/modules/taxonomy/taxonomy.test drupal-7.66/modules/taxonomy/taxonomy.test --- drupal-7.41/modules/taxonomy/taxonomy.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/taxonomy/taxonomy.test 2019-04-17 22:20:46.000000000 +0200 @@ -1025,7 +1025,7 @@ function setUp() { parent::setUp('taxonomy'); - $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access', 'administer content types')); + $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access', 'administer content types', 'administer fields')); $this->drupalLogin($this->admin_user); $this->vocabulary = $this->createVocabulary(); @@ -1983,3 +1983,113 @@ } } + +/** + * Tests that appropriate query tags are added. + */ +class TaxonomyQueryAlterTestCase extends TaxonomyWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Taxonomy query tags', + 'description' => 'Verifies that taxonomy_term_access tags are added to queries.', + 'group' => 'Taxonomy', + ); + } + + public function setUp() { + parent::setUp('taxonomy_test'); + } + + /** + * Tests that appropriate tags are added when querying the database. + */ + public function testTaxonomyQueryAlter() { + // Create a new vocabulary and add a few terms to it. + $vocabulary = $this->createVocabulary(); + $terms = array(); + for ($i = 0; $i < 5; $i++) { + $terms[$i] = $this->createTerm($vocabulary); + } + + // Set up hierarchy. Term 2 is a child of 1. + $terms[2]->parent = array($terms[1]->tid); + taxonomy_term_save($terms[2]); + + $this->setupQueryTagTestHooks(); + $loaded_term = taxonomy_term_load($terms[0]->tid); + $this->assertEqual($loaded_term->tid, $terms[0]->tid, 'First term was loaded'); + $this->assertQueryTagTestResult(1, 'taxonomy_term_load()'); + + $this->setupQueryTagTestHooks(); + $loaded_terms = taxonomy_get_tree($vocabulary->vid); + $this->assertEqual(count($loaded_terms), count($terms), 'All terms were loaded'); + $this->assertQueryTagTestResult(1, 'taxonomy_get_tree()'); + + $this->setupQueryTagTestHooks(); + $loaded_terms = taxonomy_get_parents($terms[2]->tid); + $this->assertEqual(count($loaded_terms), 1, 'All parent terms were loaded'); + $this->assertQueryTagTestResult(2, 'taxonomy_get_parents()'); + + $this->setupQueryTagTestHooks(); + $loaded_terms = taxonomy_get_children($terms[1]->tid); + $this->assertEqual(count($loaded_terms), 1, 'All child terms were loaded'); + $this->assertQueryTagTestResult(2, 'taxonomy_get_children()'); + + $this->setupQueryTagTestHooks(); + $query = db_select('taxonomy_term_data', 't'); + $query->addField('t', 'tid'); + $query->addTag('taxonomy_term_access'); + $tids = $query->execute()->fetchCol(); + $this->assertEqual(count($tids), count($terms), 'All term IDs were retrieved'); + $this->assertQueryTagTestResult(1, 'custom db_select() with taxonomy_term_access tag (preferred)'); + + $this->setupQueryTagTestHooks(); + $query = db_select('taxonomy_term_data', 't'); + $query->addField('t', 'tid'); + $query->addTag('term_access'); + $tids = $query->execute()->fetchCol(); + $this->assertEqual(count($tids), count($terms), 'All term IDs were retrieved'); + $this->assertQueryTagTestResult(1, 'custom db_select() with term_access tag (deprecated)'); + + $this->setupQueryTagTestHooks(); + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'taxonomy_term'); + $query->addTag('taxonomy_term_access'); + $result = $query->execute(); + $this->assertEqual(count($result['taxonomy_term']), count($terms), 'All term IDs were retrieved'); + $this->assertQueryTagTestResult(1, 'custom EntityFieldQuery with taxonomy_term_access tag (preferred)'); + + $this->setupQueryTagTestHooks(); + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'taxonomy_term'); + $query->addTag('term_access'); + $result = $query->execute(); + $this->assertEqual(count($result['taxonomy_term']), count($terms), 'All term IDs were retrieved'); + $this->assertQueryTagTestResult(1, 'custom EntityFieldQuery with term_access tag (deprecated)'); + } + + /** + * Sets up the hooks in the test module. + */ + protected function setupQueryTagTestHooks() { + taxonomy_terms_static_reset(); + variable_set('taxonomy_test_query_alter', 0); + variable_set('taxonomy_test_query_term_access_alter', 0); + variable_set('taxonomy_test_query_taxonomy_term_access_alter', 0); + } + + /** + * Verifies invocation of the hooks in the test module. + * + * @param int $expected_invocations + * The number of times the hooks are expected to have been invoked. + * @param string $method + * A string describing the invoked function which generated the query. + */ + protected function assertQueryTagTestResult($expected_invocations, $method) { + $this->assertIdentical($expected_invocations, variable_get('taxonomy_test_query_alter'), 'hook_query_alter() invoked when executing ' . $method); + $this->assertIdentical($expected_invocations, variable_get('taxonomy_test_query_term_access_alter'), 'Deprecated hook_query_term_access_alter() invoked when executing ' . $method); + $this->assertIdentical($expected_invocations, variable_get('taxonomy_test_query_taxonomy_term_access_alter'), 'Preferred hook_query_taxonomy_term_access_alter() invoked when executing ' . $method); + } + +} diff -Naur drupal-7.41/modules/toolbar/toolbar.info drupal-7.66/modules/toolbar/toolbar.info --- drupal-7.41/modules/toolbar/toolbar.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/toolbar/toolbar.info 2019-04-17 22:39:36.000000000 +0200 @@ -4,8 +4,7 @@ package = Core version = VERSION -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/tracker/tracker.info drupal-7.66/modules/tracker/tracker.info --- drupal-7.41/modules/tracker/tracker.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/tracker/tracker.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ core = 7.x files[] = tracker.test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/tracker/tracker.test drupal-7.66/modules/tracker/tracker.test --- drupal-7.41/modules/tracker/tracker.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/tracker/tracker.test 2019-04-17 22:20:46.000000000 +0200 @@ -151,7 +151,6 @@ $node = $this->drupalCreateNode(array( 'comment' => 2, - 'title' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(8)))), )); // Add a comment to the page. diff -Naur drupal-7.41/modules/translation/tests/translation_test.info drupal-7.66/modules/translation/tests/translation_test.info --- drupal-7.41/modules/translation/tests/translation_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/translation/tests/translation_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/translation/translation.info drupal-7.66/modules/translation/translation.info --- drupal-7.41/modules/translation/translation.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/translation/translation.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ core = 7.x files[] = translation.test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/trigger/tests/trigger_test.info drupal-7.66/modules/trigger/tests/trigger_test.info --- drupal-7.41/modules/trigger/tests/trigger_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/trigger/tests/trigger_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -4,8 +4,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/trigger/trigger.info drupal-7.66/modules/trigger/trigger.info --- drupal-7.41/modules/trigger/trigger.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/trigger/trigger.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ files[] = trigger.test configure = admin/structure/trigger -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/trigger/trigger.test drupal-7.66/modules/trigger/trigger.test --- drupal-7.41/modules/trigger/trigger.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/trigger/trigger.test 2019-04-17 22:20:46.000000000 +0200 @@ -85,7 +85,7 @@ $this->assertRaw(t('!post %title has been created.', array('!post' => 'Basic page', '%title' => $edit["title"])), 'Make sure the Basic page has actually been created'); // Action should have been fired. $loaded_node = $this->drupalGetNodeByTitle($edit["title"]); - $this->assertTrue($loaded_node->$info['property'] == $info['expected'], format_string('Make sure the @action action fired.', array('@action' => $info['name']))); + $this->assertTrue($loaded_node->{$info['property']} == $info['expected'], format_string('Make sure the @action action fired.', array('@action' => $info['name']))); // Leave action assigned for next test // There should be an error when the action is assigned to the trigger diff -Naur drupal-7.41/modules/update/tests/aaa_update_test.info drupal-7.66/modules/update/tests/aaa_update_test.info --- drupal-7.41/modules/update/tests/aaa_update_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/update/tests/aaa_update_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -4,8 +4,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/update/tests/bbb_update_test.info drupal-7.66/modules/update/tests/bbb_update_test.info --- drupal-7.41/modules/update/tests/bbb_update_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/update/tests/bbb_update_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -4,8 +4,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/update/tests/ccc_update_test.info drupal-7.66/modules/update/tests/ccc_update_test.info --- drupal-7.41/modules/update/tests/ccc_update_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/update/tests/ccc_update_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -4,8 +4,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info drupal-7.66/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info --- drupal-7.41/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info 1970-01-01 01:00:00.000000000 +0100 +++ drupal-7.66/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info 2019-04-17 22:39:36.000000000 +0200 @@ -0,0 +1,9 @@ +name = Update test admin theme +description = Test theme which is used as admin theme. +core = 7.x +hidden = TRUE + +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" +project = "drupal" +datestamp = "1555533576" diff -Naur drupal-7.41/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info drupal-7.66/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info --- drupal-7.41/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info 2019-04-17 22:39:36.000000000 +0200 @@ -3,8 +3,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info drupal-7.66/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info --- drupal-7.41/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info 2019-04-17 22:39:36.000000000 +0200 @@ -4,8 +4,7 @@ base theme = update_test_basetheme hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/update/tests/update_test.info drupal-7.66/modules/update/tests/update_test.info --- drupal-7.41/modules/update/tests/update_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/update/tests/update_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/update/tests/update_test.module drupal-7.66/modules/update/tests/update_test.module --- drupal-7.41/modules/update/tests/update_test.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/update/tests/update_test.module 2019-04-17 22:20:46.000000000 +0200 @@ -11,6 +11,7 @@ function update_test_system_theme_info() { $themes['update_test_basetheme'] = drupal_get_path('module', 'update_test') . '/themes/update_test_basetheme/update_test_basetheme.info'; $themes['update_test_subtheme'] = drupal_get_path('module', 'update_test') . '/themes/update_test_subtheme/update_test_subtheme.info'; + $themes['update_test_admintheme'] = drupal_get_path('module', 'update_test') . '/themes/update_test_admintheme/update_test_admintheme.info'; return $themes; } diff -Naur drupal-7.41/modules/update/update.compare.inc drupal-7.66/modules/update/update.compare.inc --- drupal-7.41/modules/update/update.compare.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/update/update.compare.inc 2019-04-17 22:20:46.000000000 +0200 @@ -104,7 +104,13 @@ * @see update_get_projects() */ function _update_process_info_list(&$projects, $list, $project_type, $status) { + $admin_theme = variable_get('admin_theme', 'seven'); foreach ($list as $file) { + // The admin theme is a special case. It should always be considered enabled + // for the purposes of update checking. + if ($file->name === $admin_theme) { + $file->status = TRUE; + } // A disabled base theme of an enabled sub-theme still has all of its code // run by the sub-theme, so we include it in our "enabled" projects list. if ($status && !$file->status && !empty($file->sub_themes)) { diff -Naur drupal-7.41/modules/update/update.info drupal-7.66/modules/update/update.info --- drupal-7.41/modules/update/update.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/update/update.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ files[] = update.test configure = admin/reports/updates/settings -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/update/update.manager.inc drupal-7.66/modules/update/update.manager.inc --- drupal-7.41/modules/update/update.manager.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/update/update.manager.inc 2019-04-17 22:20:46.000000000 +0200 @@ -59,7 +59,7 @@ * @see update_menu() * @ingroup forms */ -function update_manager_update_form($form, $form_state = array(), $context) { +function update_manager_update_form($form, $form_state, $context) { if (!_update_manager_check_backends($form, 'update')) { return $form; } diff -Naur drupal-7.41/modules/update/update.settings.inc drupal-7.66/modules/update/update.settings.inc --- drupal-7.41/modules/update/update.settings.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/update/update.settings.inc 2019-04-17 22:20:46.000000000 +0200 @@ -26,7 +26,7 @@ $form['update_check_disabled'] = array( '#type' => 'checkbox', - '#title' => t('Check for updates of disabled modules and themes'), + '#title' => t('Check for updates of disabled and uninstalled modules and themes'), '#default_value' => variable_get('update_check_disabled', FALSE), ); @@ -98,10 +98,11 @@ * Form submission handler for update_settings(). * * Also invalidates the cache of available updates if the "Check for updates of - * disabled modules and themes" setting is being changed. The available updates - * report needs to refetch available update data after this setting changes or - * it would show misleading things (e.g., listing the disabled projects on the - * site with the "No available releases found" warning). + * disabled and uninstalled modules and themes" setting is being changed. The + * available updates report needs to refetch available update data after this + * setting changes or it would show misleading things (e.g., listing the + * disabled projects on the site with the "No available releases found" + * warning). * * @see update_settings_validate() */ diff -Naur drupal-7.41/modules/update/update.test drupal-7.66/modules/update/update.test --- drupal-7.41/modules/update/update.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/update/update.test 2019-04-17 22:20:46.000000000 +0200 @@ -463,6 +463,55 @@ } /** + * Tests that the admin theme is always notified about security updates. + */ + function testUpdateAdminThemeSecurityUpdate() { + // Disable the admin theme. + db_update('system') + ->fields(array('status' => 0)) + ->condition('type', 'theme') + ->condition('name', 'update_test_%', 'LIKE') + ->execute(); + + variable_set('admin_theme', 'update_test_admintheme'); + + // Define the initial state for core and the themes. + $system_info = array( + '#all' => array( + 'version' => '7.0', + ), + 'update_test_admintheme' => array( + 'project' => 'update_test_admintheme', + 'version' => '7.x-1.0', + 'hidden' => FALSE, + ), + 'update_test_basetheme' => array( + 'project' => 'update_test_basetheme', + 'version' => '7.x-1.1', + 'hidden' => FALSE, + ), + 'update_test_subtheme' => array( + 'project' => 'update_test_subtheme', + 'version' => '7.x-1.0', + 'hidden' => FALSE, + ), + ); + variable_set('update_test_system_info', $system_info); + variable_set('update_check_disabled', FALSE); + $xml_mapping = array( + // This is enough because we don't check the update status of the admin + // theme. We want to check that the admin theme is included in the list. + 'drupal' => '0', + ); + $this->refreshUpdateStatus($xml_mapping); + // The admin theme is displayed even if it's disabled. + $this->assertText('update_test_admintheme', "The admin theme is checked for update even if it's disabled"); + // The other disabled themes are not displayed. + $this->assertNoText('update_test_basetheme', 'Disabled theme is not checked for update in the list.'); + $this->assertNoText('update_test_subtheme', 'Disabled theme is not checked for update in the list.'); + } + + /** * Tests that disabled themes are only shown when desired. */ function testUpdateShowDisabledThemes() { @@ -800,4 +849,4 @@ $this->assertEqual($url, $expected, "When ? is present, '$url' should be '$expected'."); } -} \ No newline at end of file +} diff -Naur drupal-7.41/modules/user/tests/user_form_test.info drupal-7.66/modules/user/tests/user_form_test.info --- drupal-7.41/modules/user/tests/user_form_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/user/tests/user_form_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/user/tests/user_form_test.module drupal-7.66/modules/user/tests/user_form_test.module --- drupal-7.41/modules/user/tests/user_form_test.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/user/tests/user_form_test.module 2019-04-17 22:20:46.000000000 +0200 @@ -62,3 +62,21 @@ function user_form_test_current_password_submit($form, &$form_state) { drupal_set_message(t('The password has been validated and the form submitted successfully.')); } + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function user_form_test_form_user_profile_form_alter(&$form, &$form_state) { + if (variable_get('user_form_test_user_profile_form_rebuild', FALSE)) { + $form['#submit'][] = 'user_form_test_user_account_submit'; + } +} + +/** + * Submit function for user_profile_form(). + */ +function user_form_test_user_account_submit($form, &$form_state) { + // Rebuild the form instead of letting the process end. This allows us to + // test for bugs that can be triggered in contributed modules. + $form_state['rebuild'] = TRUE; +} diff -Naur drupal-7.41/modules/user/user.info drupal-7.66/modules/user/user.info --- drupal-7.41/modules/user/user.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/modules/user/user.info 2019-04-17 22:39:36.000000000 +0200 @@ -9,8 +9,7 @@ configure = admin/config/people stylesheets[all][] = user.css -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/modules/user/user.install drupal-7.66/modules/user/user.install --- drupal-7.41/modules/user/user.install 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/user/user.install 2019-04-17 22:20:46.000000000 +0200 @@ -49,6 +49,9 @@ 'columns' => array('uid' => 'uid'), ), ), + 'indexes' => array( + 'uid_module' => array('uid', 'module'), + ), ); $schema['role_permission'] = array( @@ -911,5 +914,14 @@ } /** + * Ensure there is a combined index on {authmap}.uid and {authmap}.module. + */ +function user_update_7019() { + // Check first in case it was already added manually. + if (!db_index_exists('authmap', 'uid_module')) { + db_add_index('authmap', 'uid_module', array('uid', 'module')); + } +} +/** * @} End of "addtogroup updates-7.x-extra". */ diff -Naur drupal-7.41/modules/user/user.module drupal-7.66/modules/user/user.module --- drupal-7.41/modules/user/user.module 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/user/user.module 2019-04-17 22:20:46.000000000 +0200 @@ -418,13 +418,11 @@ * * @return * A fully-loaded $user object upon successful save or FALSE if the save failed. - * - * @todo D8: Drop $edit and fix user_save() to be consistent with others. */ function user_save($account, $edit = array(), $category = 'account') { $transaction = db_transaction(); try { - if (!empty($edit['pass'])) { + if (isset($edit['pass']) && strlen(trim($edit['pass'])) > 0) { // Allow alternate password hashing schemes. require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc'); $edit['pass'] = user_hash_password(trim($edit['pass'])); @@ -639,7 +637,7 @@ if (strpos($name, ' ') !== FALSE) { return t('The username cannot contain multiple spaces in a row.'); } - if (preg_match('/[^\x{80}-\x{F7} a-z0-9@_.\'-]/i', $name)) { + if (preg_match('/[^\x{80}-\x{F7} a-z0-9@+_.\'-]/i', $name)) { return t('The username contains an illegal character.'); } if (preg_match('/[\x{80}-\x{A0}' . // Non-printable ISO-8859-1 + NBSP @@ -691,7 +689,7 @@ $validators = array( 'file_validate_is_image' => array(), 'file_validate_image_resolution' => array(variable_get('user_picture_dimensions', '85x85')), - 'file_validate_size' => array(variable_get('user_picture_file_size', '30') * 1024), + 'file_validate_size' => array((int) variable_get('user_picture_file_size', '30') * 1024), ); // Save the file as a temporary file. @@ -791,7 +789,7 @@ * (optional) The account to check, if not given use currently logged in user. * * @return - * Boolean TRUE if the current user has the requested permission. + * Boolean TRUE if the user has the requested permission. * * All permission checks in Drupal should go through this function. This * way, we guarantee consistent behavior, and ensure that the superuser @@ -958,6 +956,8 @@ */ function user_search_execute($keys = NULL, $conditions = NULL) { $find = array(); + // Escape for LIKE matching. + $keys = db_like($keys); // Replace wildcards with MySQL/PostgreSQL wildcards. $keys = preg_replace('!\*+!', '%', $keys); $query = db_select('users')->extend('PagerDefault'); @@ -967,13 +967,13 @@ // and they don't need to be restricted to only active users. $query->fields('users', array('mail')); $query->condition(db_or()-> - condition('name', '%' . db_like($keys) . '%', 'LIKE')-> - condition('mail', '%' . db_like($keys) . '%', 'LIKE')); + condition('name', '%' . $keys . '%', 'LIKE')-> + condition('mail', '%' . $keys . '%', 'LIKE')); } else { // Regular users can only search via usernames, and we do not show them // blocked accounts. - $query->condition('name', '%' . db_like($keys) . '%', 'LIKE') + $query->condition('name', '%' . $keys . '%', 'LIKE') ->condition('status', 1); } $uids = $query @@ -1088,13 +1088,16 @@ '#description' => t('To change the current user password, enter the new password in both fields.'), ); // To skip the current password field, the user must have logged in via a - // one-time link and have the token in the URL. - $pass_reset = isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]); + // one-time link and have the token in the URL. Store this in $form_state + // so it persists even on subsequent Ajax requests. + if (!isset($form_state['user_pass_reset'])) { + $form_state['user_pass_reset'] = isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]); + } $protected_values = array(); $current_pass_description = ''; // The user may only change their own password without their current // password if they logged in via a one-time login link. - if (!$pass_reset) { + if (!$form_state['user_pass_reset']) { $protected_values['mail'] = $form['account']['mail']['#title']; $protected_values['pass'] = t('Password'); $request_new = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.')))); @@ -1160,7 +1163,7 @@ $form['account']['roles'] = array( '#type' => 'checkboxes', '#title' => t('Roles'), - '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()), + '#default_value' => (!$register && !empty($account->roles) ? array_keys(array_filter($account->roles)) : array()), '#options' => $roles, '#access' => $roles && user_access('administer permissions'), DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated, @@ -1230,7 +1233,7 @@ // that prevent them from being empty if they are changed. if ((strlen(trim($form_state['values'][$key])) > 0) && ($form_state['values'][$key] != $account->$key)) { require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc'); - $current_pass_failed = empty($form_state['values']['current_pass']) || !user_check_password($form_state['values']['current_pass'], $account); + $current_pass_failed = strlen(trim($form_state['values']['current_pass'])) == 0 || !user_check_password($form_state['values']['current_pass'], $account); if ($current_pass_failed) { form_set_error('current_pass', t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => $name))); form_set_error($key); @@ -1306,10 +1309,12 @@ elseif (!empty($edit['picture_delete'])) { $edit['picture'] = NULL; } - // Prepare user roles. - if (isset($edit['roles'])) { - $edit['roles'] = array_filter($edit['roles']); - } + } + + // Filter out roles with empty values to avoid granting extra roles when + // processing custom form submissions. + if (isset($edit['roles'])) { + $edit['roles'] = array_filter($edit['roles']); } // Move account cancellation information into $user->data. @@ -1751,9 +1756,11 @@ $items['admin/people/create'] = array( 'title' => 'Add user', + 'page callback' => 'user_admin', 'page arguments' => array('create'), 'access arguments' => array('administer users'), 'type' => MENU_LOCAL_ACTION, + 'file' => 'user.admin.inc', ); // Administration pages. @@ -2161,7 +2168,7 @@ */ function user_login_authenticate_validate($form, &$form_state) { $password = trim($form_state['values']['pass']); - if (!empty($form_state['values']['name']) && !empty($password)) { + if (!empty($form_state['values']['name']) && strlen(trim($password)) > 0) { // Do not allow any login from the current user's IP if the limit has been // reached. Default is 50 failed attempts allowed in one hour. This is // independent of the per-user limit to catch attempts from one IP to log @@ -2225,7 +2232,11 @@ } } else { - form_set_error('name', t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>', array('@password' => url('user/password', array('query' => array('name' => $form_state['values']['name'])))))); + // Use $form_state['input']['name'] here to guarantee that we send + // exactly what the user typed in. $form_state['values']['name'] may have + // been modified by validation handlers that ran earlier than this one. + $query = isset($form_state['input']['name']) ? array('name' => $form_state['input']['name']) : array(); + form_set_error('name', t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>', array('@password' => url('user/password', array('query' => $query))))); watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name'])); } } @@ -2248,7 +2259,7 @@ */ function user_authenticate($name, $password) { $uid = FALSE; - if (!empty($name) && !empty($password)) { + if (!empty($name) && strlen(trim($password)) > 0) { $account = user_load_by_name($name); if ($account) { // Allow alternate password hashing schemes. @@ -3663,12 +3674,7 @@ ); $element['#attached']['js'][] = drupal_get_path('module', 'user') . '/user.js'; - // Ensure settings are only added once per page. - static $already_added = FALSE; - if (!$already_added) { - $already_added = TRUE; - $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting'); - } + $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting'); return $element; } diff -Naur drupal-7.41/modules/user/user.pages.inc drupal-7.66/modules/user/user.pages.inc --- drupal-7.41/modules/user/user.pages.inc 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/user/user.pages.inc 2019-04-17 22:20:46.000000000 +0200 @@ -44,6 +44,12 @@ $form['name']['#value'] = $user->mail; $form['mail'] = array( '#prefix' => '<p>', + // As of https://www.drupal.org/node/889772 the user no longer must log + // out (if they are still logged in when using the password reset link, + // they will be logged out automatically then), but this text is kept as + // is to avoid breaking translations as well as to encourage the user to + // log out manually at a time of their own choosing (when it will not + // interrupt anything else they may have been in the middle of doing). '#markup' => t('Password reset instructions will be mailed to %email. You must log out to use the password reset link in the e-mail.', array('%email' => $user->mail)), '#suffix' => '</p>', ); @@ -54,6 +60,11 @@ return $form; } +/** + * Form validation handler for user_pass(). + * + * @see user_pass_submit() + */ function user_pass_validate($form, &$form_state) { $name = trim($form_state['values']['name']); // Try to load by email. @@ -72,6 +83,11 @@ } } +/** + * Form submission handler for user_pass(). + * + * @see user_pass_validate() + */ function user_pass_submit($form, &$form_state) { global $language; @@ -96,9 +112,20 @@ // When processing the one-time login link, we have to make sure that a user // isn't already logged in. if ($user->uid) { - // The existing user is already logged in. + // The existing user is already logged in. Log them out and reload the + // current page so the password reset process can continue. if ($user->uid == $uid) { - drupal_set_message(t('You are logged in as %user. <a href="!user_edit">Change your password.</a>', array('%user' => $user->name, '!user_edit' => url("user/$user->uid/edit")))); + // Preserve the current destination (if any) and ensure the redirect goes + // back to the current page; any custom destination set in + // hook_user_logout() and intended for regular logouts would not be + // appropriate here. + $destination = array(); + if (isset($_GET['destination'])) { + $destination = drupal_get_destination(); + } + user_logout_current_user(); + unset($_GET['destination']); + drupal_goto(current_path(), array('query' => drupal_get_query_parameters() + $destination)); } // A different user is already logged in on the computer. else { @@ -110,8 +137,8 @@ // Invalid one-time link specifies an unknown user. drupal_set_message(t('The one-time login link you clicked is invalid.'), 'error'); } + drupal_goto(); } - drupal_goto(); } else { // Time out, in seconds, until login URL expires. Defaults to 24 hours = @@ -168,6 +195,14 @@ * Menu callback; logs the current user out, and redirects to the home page. */ function user_logout() { + user_logout_current_user(); + drupal_goto(); +} + +/** + * Logs the current user out. + */ +function user_logout_current_user() { global $user; watchdog('user', 'Session closed for %name.', array('%name' => $user->name)); @@ -176,8 +211,6 @@ // Destroy the current session, and reset $user to the anonymous user. session_destroy(); - - drupal_goto(); } /** @@ -294,14 +327,18 @@ } /** - * Validation function for the user account and profile editing form. + * Form validation handler for user_profile_form(). + * + * @see user_profile_form_submit() */ function user_profile_form_validate($form, &$form_state) { entity_form_field_validate('user', $form, $form_state); } /** - * Submit function for the user account and profile editing form. + * Form submission handler for user_profile_form(). + * + * @see user_profile_form_validate() */ function user_profile_form_submit($form, &$form_state) { $account = $form_state['user']; diff -Naur drupal-7.41/modules/user/user.test drupal-7.66/modules/user/user.test --- drupal-7.41/modules/user/user.test 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/modules/user/user.test 2019-04-17 22:20:46.000000000 +0200 @@ -276,6 +276,7 @@ 'foo@example.com' => array('Valid username', 'assertNull'), 'foo@-example.com' => array('Valid username', 'assertNull'), // invalid domains are allowed in usernames 'þòøÇߪř€' => array('Valid username', 'assertNull'), + 'foo+bar' => array('Valid username', 'assertNull'), // '+' symbol is allowed 'ᚠᛇᚻ᛫ᛒᛦᚦ' => array('Valid UTF8 username', 'assertNull'), // runes ' foo' => array('Invalid username that starts with a space', 'assertNotNull'), 'foo ' => array('Invalid username that ends with a space', 'assertNotNull'), @@ -466,6 +467,19 @@ } /** + * Retrieves password reset email and extracts the login link. + */ + public function getResetURL() { + // Assume the most recent email. + $_emails = $this->drupalGetMails(); + $email = end($_emails); + $urls = array(); + preg_match('#.+user/reset/.+#', $email['body'], $urls); + + return $urls[0]; + } + + /** * Tests password reset functionality. */ function testUserPasswordReset() { @@ -478,6 +492,77 @@ $this->drupalPost('user/password', $edit, t('E-mail new password')); // Confirm the password reset. $this->assertText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message displayed.'); + + // Create an image field to enable an Ajax request on the user profile page. + $field = array( + 'field_name' => 'field_avatar', + 'type' => 'image', + 'settings' => array(), + 'cardinality' => 1, + ); + field_create_field($field); + + $instance = array( + 'field_name' => $field['field_name'], + 'entity_type' => 'user', + 'label' => 'Avatar', + 'bundle' => 'user', + 'required' => FALSE, + 'settings' => array(), + 'widget' => array( + 'type' => 'image_image', + 'settings' => array(), + ), + ); + field_create_instance($instance); + + $resetURL = $this->getResetURL(); + $this->drupalGet($resetURL); + + // Check successful login. + $this->drupalPost(NULL, NULL, t('Log in')); + + // Make sure the Ajax request from uploading a file does not invalidate the + // reset token. + $image = current($this->drupalGetTestFiles('image')); + $edit = array( + 'files[field_avatar_und_0]' => drupal_realpath($image->uri), + ); + $this->drupalPostAJAX(NULL, $edit, 'field_avatar_und_0_upload_button'); + + // Change the forgotten password. + $password = user_password(); + $edit = array('pass[pass1]' => $password, 'pass[pass2]' => $password); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertText(t('The changes have been saved.'), 'Forgotten password changed.'); + } + + /** + * Test user password reset while logged in. + */ + function testUserPasswordResetLoggedIn() { + $account = $this->drupalCreateUser(); + $this->drupalLogin($account); + // Make sure the test account has a valid password. + user_save($account, array('pass' => user_password())); + + // Generate one time login link. + $reset_url = user_pass_reset_url($account); + $this->drupalGet($reset_url); + + $this->assertText('Reset password'); + $this->drupalPost(NULL, NULL, t('Log in')); + + $this->assertText('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.'); + + $pass = user_password(); + $edit = array( + 'pass[pass1]' => $pass, + 'pass[pass2]' => $pass, + ); + $this->drupalPost(NULL, $edit, t('Save')); + + $this->assertText('The changes have been saved.'); } /** @@ -1501,7 +1586,13 @@ // Setup date/time settings for Los Angeles time. variable_set('date_default_timezone', 'America/Los_Angeles'); variable_set('configurable_timezones', 1); - variable_set('date_format_medium', 'Y-m-d H:i T'); + + // Override the 'medium' date format, which is the default for node + // creation time. Since we are testing time zones with Daylight Saving + // Time, and need to future proof against changes to the zoneinfo database, + // we choose the 'I' format placeholder instead of a human-readable zone + // name. With 'I', a 1 means the date is in DST, and 0 if not. + variable_set('date_format_medium', 'Y-m-d H:i I'); // Create a user account and login. $web_user = $this->drupalCreateUser(); @@ -1519,11 +1610,11 @@ // Confirm date format and time zone. $this->drupalGet("node/$node1->nid"); - $this->assertText('2007-03-09 21:00 PST', 'Date should be PST.'); + $this->assertText('2007-03-09 21:00 0', 'Date should be PST.'); $this->drupalGet("node/$node2->nid"); - $this->assertText('2007-03-11 01:00 PST', 'Date should be PST.'); + $this->assertText('2007-03-11 01:00 0', 'Date should be PST.'); $this->drupalGet("node/$node3->nid"); - $this->assertText('2007-03-20 21:00 PDT', 'Date should be PDT.'); + $this->assertText('2007-03-20 21:00 1', 'Date should be PDT.'); // Change user time zone to Santiago time. $edit = array(); @@ -1534,11 +1625,11 @@ // Confirm date format and time zone. $this->drupalGet("node/$node1->nid"); - $this->assertText('2007-03-10 02:00 CLST', 'Date should be Chile summer time; five hours ahead of PST.'); + $this->assertText('2007-03-10 02:00 1', 'Date should be Chile summer time; five hours ahead of PST.'); $this->drupalGet("node/$node2->nid"); - $this->assertText('2007-03-11 05:00 CLT', 'Date should be Chile time; four hours ahead of PST'); + $this->assertText('2007-03-11 05:00 0', 'Date should be Chile time; four hours ahead of PST'); $this->drupalGet("node/$node3->nid"); - $this->assertText('2007-03-21 00:00 CLT', 'Date should be Chile time; three hours ahead of PDT.'); + $this->assertText('2007-03-21 00:00 0', 'Date should be Chile time; three hours ahead of PDT.'); } } @@ -1849,6 +1940,19 @@ $this->drupalGet('admin/people'); $this->assertText($edit['name'], 'User found in list of users'); } + + // Test that the password '0' is considered a password. + $name = $this->randomName(); + $edit = array( + 'name' => $name, + 'mail' => $name . '@example.com', + 'pass[pass1]' => 0, + 'pass[pass2]' => 0, + 'notify' => FALSE, + ); + $this->drupalPost('admin/people/create', $edit, t('Create new account')); + $this->assertText(t('Created a new user account for @name. No e-mail has been sent.', array('@name' => $edit['name'])), 'User created with password 0'); + $this->assertNoText('Password field is required'); } } @@ -1926,6 +2030,74 @@ $this->drupalLogin($user1); $this->drupalLogout(); } + + /** + * Tests setting the password to "0". + */ + public function testUserWith0Password() { + $admin = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($admin); + // Create a regular user. + $user1 = $this->drupalCreateUser(array()); + + $edit = array('pass[pass1]' => '0', 'pass[pass2]' => '0'); + $this->drupalPost("user/" . $user1->uid . "/edit", $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + + $this->drupalLogout(); + $user1->pass_raw = '0'; + $this->drupalLogin($user1); + $this->drupalLogout(); + } +} + +/** + * Tests editing a user account with and without a form rebuild. + */ +class UserEditRebuildTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'User edit with form rebuild', + 'description' => 'Test user edit page when a form rebuild is triggered.', + 'group' => 'User', + ); + } + + function setUp() { + parent::setUp('user_form_test'); + } + + /** + * Test user edit page when the form is set to rebuild. + */ + function testUserEditFormRebuild() { + $user1 = $this->drupalCreateUser(array('change own username')); + $this->drupalLogin($user1); + + $roles = array_keys($user1->roles); + // Save the user form twice. + $edit = array(); + $edit['current_pass'] = $user1->pass_raw; + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + $saved_user1 = entity_load_unchanged('user', $user1->uid); + $this->assertEqual(count($roles), count($saved_user1->roles), 'Count of user roles in database matches original count.'); + $diff = array_diff(array_keys($saved_user1->roles), $roles); + $this->assertTrue(empty($diff), format_string('User roles in database match original: @roles', array('@roles' => implode(', ', $saved_user1->roles)))); + // Set variable that causes the form to be rebuilt in user_form_test.module. + variable_set('user_form_test_user_profile_form_rebuild', TRUE); + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + $saved_user1 = entity_load_unchanged('user', $user1->uid); + $this->assertEqual(count($roles), count($saved_user1->roles), 'Count of user roles in database matches original count.'); + $diff = array_diff(array_keys($saved_user1->roles), $roles); + $this->assertTrue(empty($diff), format_string('User roles in database match original: @roles', array('@roles' => implode(', ', $saved_user1->roles)))); + } } /** @@ -2215,7 +2387,13 @@ } function testUserSearch() { + // Verify that a user without 'administer users' permission cannot search + // for users by email address. Additionally, ensure that the username has a + // plus sign to ensure searching works with that. $user1 = $this->drupalCreateUser(array('access user profiles', 'search content', 'use advanced search')); + $edit['name'] = 'foo+bar'; + $edit['mail'] = $edit['name'] . '@example.com'; + user_save($user1, $edit); $this->drupalLogin($user1); $keys = $user1->mail; $edit = array('keys' => $keys); @@ -2230,6 +2408,20 @@ $this->drupalPost('search/user/', $edit, t('Search')); $this->assertText($keys); + // Verify that wildcard search works. + $keys = $user1->name; + $keys = substr($keys, 0, 2) . '*' . substr($keys, 4, 2); + $edit = array('keys' => $keys); + $this->drupalPost('search/user/', $edit, t('Search')); + $this->assertText($user1->name, 'Search for username wildcard resulted in user name on page for administrative user.'); + + // Verify that wildcard search works for email. + $keys = $user1->mail; + $keys = substr($keys, 0, 2) . '*' . substr($keys, 4, 2); + $edit = array('keys' => $keys); + $this->drupalPost('search/user/', $edit, t('Search')); + $this->assertText($user1->name, 'Search for email wildcard resulted in user name on page for administrative user.'); + // Create a blocked user. $blocked_user = $this->drupalCreateUser(); $edit = array('status' => 0); diff -Naur drupal-7.41/profiles/minimal/minimal.info drupal-7.66/profiles/minimal/minimal.info --- drupal-7.41/profiles/minimal/minimal.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/profiles/minimal/minimal.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ dependencies[] = block dependencies[] = dblog -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/profiles/standard/standard.info drupal-7.66/profiles/standard/standard.info --- drupal-7.41/profiles/standard/standard.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/profiles/standard/standard.info 2019-04-17 22:39:36.000000000 +0200 @@ -24,8 +24,7 @@ dependencies[] = file dependencies[] = rdf -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info drupal-7.66/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info --- drupal-7.41/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -6,8 +6,7 @@ hidden = TRUE files[] = drupal_system_listing_compatible_test.test -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info drupal-7.66/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info --- drupal-7.41/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info 2019-04-17 22:39:36.000000000 +0200 @@ -8,8 +8,7 @@ core = 6.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/profiles/testing/testing.info drupal-7.66/profiles/testing/testing.info --- drupal-7.41/profiles/testing/testing.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/profiles/testing/testing.info 2019-04-17 22:39:36.000000000 +0200 @@ -4,8 +4,7 @@ core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/robots.txt drupal-7.66/robots.txt --- drupal-7.41/robots.txt 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/robots.txt 2019-04-17 22:20:46.000000000 +0200 @@ -15,6 +15,39 @@ User-agent: * Crawl-delay: 10 +# CSS, JS, Images +Allow: /misc/*.css$ +Allow: /misc/*.css? +Allow: /misc/*.js$ +Allow: /misc/*.js? +Allow: /misc/*.gif +Allow: /misc/*.jpg +Allow: /misc/*.jpeg +Allow: /misc/*.png +Allow: /modules/*.css$ +Allow: /modules/*.css? +Allow: /modules/*.js$ +Allow: /modules/*.js? +Allow: /modules/*.gif +Allow: /modules/*.jpg +Allow: /modules/*.jpeg +Allow: /modules/*.png +Allow: /profiles/*.css$ +Allow: /profiles/*.css? +Allow: /profiles/*.js$ +Allow: /profiles/*.js? +Allow: /profiles/*.gif +Allow: /profiles/*.jpg +Allow: /profiles/*.jpeg +Allow: /profiles/*.png +Allow: /themes/*.css$ +Allow: /themes/*.css? +Allow: /themes/*.js$ +Allow: /themes/*.js? +Allow: /themes/*.gif +Allow: /themes/*.jpg +Allow: /themes/*.jpeg +Allow: /themes/*.png # Directories Disallow: /includes/ Disallow: /misc/ diff -Naur drupal-7.41/scripts/generate-d6-content.sh drupal-7.66/scripts/generate-d6-content.sh --- drupal-7.41/scripts/generate-d6-content.sh 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/scripts/generate-d6-content.sh 2019-04-17 22:20:46.000000000 +0200 @@ -67,6 +67,7 @@ ++$voc_id; $vocabulary['name'] = "vocabulary $voc_id (i=$i)"; $vocabulary['description'] = "description of ". $vocabulary['name']; + $vocabulary['help'] = "help for ". $vocabulary['name']; $vocabulary['nodes'] = $i > 11 ? array('page' => TRUE) : array(); $vocabulary['multiple'] = $multiple[$i % 12]; $vocabulary['required'] = $required[$i % 12]; diff -Naur drupal-7.41/scripts/run-tests.sh drupal-7.66/scripts/run-tests.sh --- drupal-7.41/scripts/run-tests.sh 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/scripts/run-tests.sh 2019-04-17 22:20:46.000000000 +0200 @@ -8,12 +8,16 @@ define('SIMPLETEST_SCRIPT_COLOR_FAIL', 31); // Red. define('SIMPLETEST_SCRIPT_COLOR_EXCEPTION', 33); // Brown. +define('SIMPLETEST_SCRIPT_EXIT_SUCCESS', 0); +define('SIMPLETEST_SCRIPT_EXIT_FAILURE', 1); +define('SIMPLETEST_SCRIPT_EXIT_EXCEPTION', 2); + // Set defaults and get overrides. list($args, $count) = simpletest_script_parse_args(); if ($args['help'] || $count == 0) { simpletest_script_help(); - exit; + exit(($count == 0) ? SIMPLETEST_SCRIPT_EXIT_FAILURE : SIMPLETEST_SCRIPT_EXIT_SUCCESS); } if ($args['execute-test']) { @@ -30,7 +34,7 @@ drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); if (!module_exists('simpletest')) { simpletest_script_print_error("The simpletest module must be enabled before this script can run."); - exit; + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } if ($args['clean']) { @@ -43,7 +47,7 @@ foreach ($messages as $text) { echo " - " . $text . "\n"; } - exit; + exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS); } // Load SimpleTest files. @@ -64,7 +68,7 @@ echo " - " . $info['name'] . ' (' . $class . ')' . "\n"; } } - exit; + exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS); } $test_list = simpletest_script_get_test_list(); @@ -78,7 +82,7 @@ $test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute(); // Execute tests. -simpletest_script_execute_batch($test_id, simpletest_script_get_test_list()); +$status = simpletest_script_execute_batch($test_id, simpletest_script_get_test_list()); // Retrieve the last database prefix used for testing and the last test class // that was run from. Use the information to read the lgo file in case any @@ -100,7 +104,7 @@ simpletest_clean_results_table($test_id); // Test complete, exit. -exit; +exit($status); /** * Print help text. @@ -142,6 +146,8 @@ --file Run tests identified by specific file names, instead of group names. Specify the path and the extension (i.e. 'modules/user/user.test'). + --directory Run all tests found within the specified file directory. + --xml <path> If provided, test results will be written as xml files to this path. @@ -190,6 +196,7 @@ 'all' => FALSE, 'class' => FALSE, 'file' => FALSE, + 'directory' => '', 'color' => FALSE, 'verbose' => FALSE, 'test_names' => array(), @@ -222,7 +229,7 @@ else { // Argument not found in list. simpletest_script_print_error("Unknown argument '$arg'."); - exit; + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } } else { @@ -235,7 +242,7 @@ // Validate the concurrency argument if (!is_numeric($args['concurrency']) || $args['concurrency'] <= 0) { simpletest_script_print_error("--concurrency must be a strictly positive integer."); - exit; + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } return array($args, $count); @@ -265,7 +272,7 @@ else { simpletest_script_print_error('Unable to automatically determine the path to the PHP interpreter. Supply the --php command line argument.'); simpletest_script_help(); - exit(); + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } // Get URL from arguments. @@ -310,6 +317,8 @@ function simpletest_script_execute_batch($test_id, $test_classes) { global $args; + $total_status = SIMPLETEST_SCRIPT_EXIT_SUCCESS; + // Multi-process execution. $children = array(); while (!empty($test_classes) || !empty($children)) { @@ -325,7 +334,7 @@ if (!is_resource($process)) { echo "Unable to fork test process. Aborting.\n"; - exit; + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } // Register our new child. @@ -345,13 +354,22 @@ if (empty($status['running'])) { // The child exited, unregister it. proc_close($child['process']); - if ($status['exitcode']) { + if ($status['exitcode'] == SIMPLETEST_SCRIPT_EXIT_FAILURE) { + if ($status['exitcode'] > $total_status) { + $total_status = $status['exitcode']; + } + } + elseif ($status['exitcode']) { + $total_status = $status['exitcode']; echo 'FATAL ' . $test_class . ': test runner returned a non-zero error code (' . $status['exitcode'] . ').' . "\n"; } + + // Remove this child. unset($children[$cid]); } } } + return $total_status; } /** @@ -374,11 +392,14 @@ simpletest_script_print($info['name'] . ' ' . _simpletest_format_summary_line($test->results) . "\n", simpletest_script_color_code($status)); // Finished, kill this runner. - exit(0); + if ($had_fails || $had_exceptions) { + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); + } + exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS); } catch (Exception $e) { echo (string) $e; - exit(1); + exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION); } } @@ -432,7 +453,7 @@ } simpletest_script_print_error('Test class not found: ' . $test_class); simpletest_script_print_alternatives($test_class, $all_classes, 6); - exit(1); + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } } } @@ -451,6 +472,51 @@ } } } + elseif ($args['directory']) { + // Extract test case class names from specified directory. + // Find all tests in the PSR-X structure; Drupal\$extension\Tests\*.php + // Since we do not want to hard-code too many structural file/directory + // assumptions about PSR-0/4 files and directories, we check for the + // minimal conditions only; i.e., a '*.php' file that has '/Tests/' in + // its path. + // Ignore anything from third party vendors, and ignore template files used in tests. + // And any api.php files. + $ignore = array('nomask' => '/vendor|\.tpl\.php|\.api\.php/'); + $files = array(); + if ($args['directory'][0] === '/') { + $directory = $args['directory']; + } + else { + $directory = DRUPAL_ROOT . "/" . $args['directory']; + } + $file_list = file_scan_directory($directory, '/\.php|\.test$/', $ignore); + foreach ($file_list as $file) { + // '/Tests/' can be contained anywhere in the file's path (there can be + // sub-directories below /Tests), but must be contained literally. + // Case-insensitive to match all Simpletest and PHPUnit tests: + // ./lib/Drupal/foo/Tests/Bar/Baz.php + // ./foo/src/Tests/Bar/Baz.php + // ./foo/tests/Drupal/foo/Tests/FooTest.php + // ./foo/tests/src/FooTest.php + // $file->filename doesn't give us a directory, so we use $file->uri + // Strip the drupal root directory and trailing slash off the URI + $filename = substr($file->uri, strlen(DRUPAL_ROOT)+1); + if (stripos($filename, '/Tests/')) { + $files[drupal_realpath($filename)] = 1; + } else if (stripos($filename, '.test')){ + $files[drupal_realpath($filename)] = 1; + } + } + + // Check for valid class names. + foreach ($all_tests as $class_name) { + $refclass = new ReflectionClass($class_name); + $classfile = $refclass->getFileName(); + if (isset($files[$classfile])) { + $test_list[] = $class_name; + } + } + } else { // Check for valid group names and get all valid classes in group. foreach ($args['test_names'] as $group_name) { @@ -460,7 +526,7 @@ else { simpletest_script_print_error('Test group not found: ' . $group_name); simpletest_script_print_alternatives($group_name, array_keys($groups)); - exit(1); + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } } } @@ -468,7 +534,7 @@ if (empty($test_list)) { simpletest_script_print_error('No valid tests were specified.'); - exit; + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } return $test_list; } diff -Naur drupal-7.41/sites/default/default.settings.php drupal-7.66/sites/default/default.settings.php --- drupal-7.41/sites/default/default.settings.php 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/sites/default/default.settings.php 2019-04-17 22:20:46.000000000 +0200 @@ -126,6 +126,38 @@ * ); * @endcode * + * For handling full UTF-8 in MySQL, including multi-byte characters such as + * emojis, Asian symbols, and mathematical symbols, you may set the collation + * and charset to "utf8mb4" prior to running install.php: + * @code + * $databases['default']['default'] = array( + * 'driver' => 'mysql', + * 'database' => 'databasename', + * 'username' => 'username', + * 'password' => 'password', + * 'host' => 'localhost', + * 'charset' => 'utf8mb4', + * 'collation' => 'utf8mb4_general_ci', + * ); + * @endcode + * When using this setting on an existing installation, ensure that all existing + * tables have been converted to the utf8mb4 charset, for example by using the + * utf8mb4_convert contributed project available at + * https://www.drupal.org/project/utf8mb4_convert, so as to prevent mixing data + * with different charsets. + * Note this should only be used when all of the following conditions are met: + * - In order to allow for large indexes, MySQL must be set up with the + * following my.cnf settings: + * [mysqld] + * innodb_large_prefix=true + * innodb_file_format=barracuda + * innodb_file_per_table=true + * These settings are available as of MySQL 5.5.14, and are defaults in + * MySQL 5.7.7 and up. + * - The PHP MySQL driver must support the utf8mb4 charset (libmysqlclient + * 5.5.3 and up, as well as mysqlnd 5.0.9 and up). + * - The MySQL server must support the utf8mb4 charset (5.5.3 and up). + * * You can optionally set prefixes for some or all database table names * by using the 'prefix' setting. If a prefix is specified, the table * name will be prepended with its value. Be sure to use valid database @@ -447,6 +479,23 @@ # $conf['block_cache_bypass_node_grants'] = TRUE; /** + * Expiration of cache_form entries: + * + * Drupal's Form API stores details of forms in cache_form and these entries are + * kept for at least 6 hours by default. Expired entries are cleared by cron. + * Busy sites can encounter problems with the cache_form table becoming very + * large. It's possible to mitigate this by setting a shorter expiration for + * cached forms. In some cases it may be desirable to set a longer cache + * expiration, for example to prolong cache_form entries for Ajax forms in + * cached HTML. + * + * @see form_set_cache() + * @see system_cron() + * @see ajax_get_form() + */ +# $conf['form_cache_expiration'] = 21600; + +/** * String overrides: * * To override specific strings on your site with or without enabling the Locale @@ -584,3 +633,29 @@ * Remove the leading hash sign to enable. */ # $conf['theme_debug'] = TRUE; + +/** + * CSS identifier double underscores allowance: + * + * To allow CSS identifiers to contain double underscores (.example__selector) + * for Drupal's BEM-style naming standards, uncomment the line below. + * Note that if you change this value in existing sites, existing page styles + * may be broken. + * + * @see drupal_clean_css_identifier() + */ +# $conf['allow_css_double_underscores'] = TRUE; + +/** + * The default list of directories that will be ignored by Drupal's file API. + * + * By default ignore node_modules and bower_components folders to avoid issues + * with common frontend tools and recursive scanning of directories looking for + * extensions. + * + * @see file_scan_directory() + */ +$conf['file_scan_ignore_directories'] = array( + 'node_modules', + 'bower_components', +); diff -Naur drupal-7.41/themes/bartik/bartik.info drupal-7.66/themes/bartik/bartik.info --- drupal-7.41/themes/bartik/bartik.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/themes/bartik/bartik.info 2019-04-17 22:39:36.000000000 +0200 @@ -34,8 +34,7 @@ settings[shortcut_module_link] = 0 -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/themes/garland/garland.info drupal-7.66/themes/garland/garland.info --- drupal-7.41/themes/garland/garland.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/themes/garland/garland.info 2019-04-17 22:39:36.000000000 +0200 @@ -7,8 +7,7 @@ stylesheets[print][] = print.css settings[garland_width] = fluid -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/themes/seven/seven.info drupal-7.66/themes/seven/seven.info --- drupal-7.41/themes/seven/seven.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/themes/seven/seven.info 2019-04-17 22:39:36.000000000 +0200 @@ -13,8 +13,7 @@ regions[sidebar_first] = First sidebar regions_hidden[] = sidebar_first -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/themes/stark/stark.info drupal-7.66/themes/stark/stark.info --- drupal-7.41/themes/stark/stark.info 2015-10-21 22:02:09.000000000 +0200 +++ drupal-7.66/themes/stark/stark.info 2019-04-17 22:39:36.000000000 +0200 @@ -5,8 +5,7 @@ core = 7.x stylesheets[all][] = layout.css -; Information added by Drupal.org packaging script on 2015-10-21 -version = "7.41" +; Information added by Drupal.org packaging script on 2019-04-17 +version = "7.66" project = "drupal" -datestamp = "1445457729" - +datestamp = "1555533576" diff -Naur drupal-7.41/web.config drupal-7.66/web.config --- drupal-7.41/web.config 2015-10-21 21:47:40.000000000 +0200 +++ drupal-7.66/web.config 2019-04-17 22:20:46.000000000 +0200 @@ -6,7 +6,7 @@ <rewrite> <rules> <rule name="Protect files and directories from prying eyes" stopProcessing="true"> - <match url="\.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)$|^(\..*|Entries.*|Repository|Root|Tag|Template)$" /> + <match url="\.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)$|^(\..*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock))$" /> <action type="CustomResponse" statusCode="403" subStatusCode="0" statusReason="Forbidden" statusDescription="Access is forbidden." /> </rule> <rule name="Force simple error message for requests for non-existent favicon.ico" stopProcessing="true">