diff --git a/www7/includes/bootstrap.inc b/www7/includes/bootstrap.inc index 6891f1277..8b05bc5c6 100644 --- a/www7/includes/bootstrap.inc +++ b/www7/includes/bootstrap.inc @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '7.63'); +define('VERSION', '7.67'); /** * Core API compatibility. diff --git a/www7/includes/common.inc b/www7/includes/common.inc index c5b4ff26e..44ff46078 100644 --- a/www7/includes/common.inc +++ b/www7/includes/common.inc @@ -1094,6 +1094,11 @@ function drupal_http_request($url, array $options = array()) { 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; } diff --git a/www7/includes/file.inc b/www7/includes/file.inc index 50a3b768a..95bb65843 100644 --- a/www7/includes/file.inc +++ b/www7/includes/file.inc @@ -993,8 +993,15 @@ function file_build_uri($path) { * @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: @@ -1002,7 +1009,6 @@ function file_destination($destination, $replace) { break; case FILE_EXISTS_RENAME: - $basename = drupal_basename($destination); $directory = drupal_dirname($destination); $destination = file_create_filename($basename, $directory); break; @@ -1218,11 +1224,20 @@ function file_unmunge_filename($filename) { * @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); @@ -1563,7 +1578,13 @@ function file_save_upload($form_field_name, $validators = array(), $destination 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) { @@ -2130,9 +2151,33 @@ function file_download_access($uri) { * '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', diff --git a/www7/includes/file.phar.inc b/www7/includes/file.phar.inc index 3d7ba01d4..a0b0e12fa 100644 --- a/www7/includes/file.phar.inc +++ b/www7/includes/file.phar.inc @@ -18,7 +18,21 @@ function file_register_phar_wrapper() { include_once $directory . '/Helper.php'; include_once $directory . '/Manager.php'; include_once $directory . '/PharStreamWrapper.php'; + include_once $directory . '/Collectable.php'; + include_once $directory . '/Interceptor/ConjunctionInterceptor.php'; + include_once $directory . '/Interceptor/PharMetaDataInterceptor.php'; + include_once $directory . '/Phar/Container.php'; + include_once $directory . '/Phar/DeserializationException.php'; + include_once $directory . '/Phar/Manifest.php'; + include_once $directory . '/Phar/Reader.php'; + include_once $directory . '/Phar/ReaderException.php'; + include_once $directory . '/Phar/Stub.php'; + include_once $directory . '/Resolvable.php'; + include_once $directory . '/Resolver/PharInvocation.php'; + include_once $directory . '/Resolver/PharInvocationCollection.php'; + include_once $directory . '/Resolver/PharInvocationResolver.php'; include_once DRUPAL_ROOT . '/misc/typo3/drupal-security/PharExtensionInterceptor.php'; + include_once DRUPAL_ROOT . '/misc/brumann/polyfill-unserialize/src/Unserialize.php'; // Set up a stream wrapper to handle insecurities due to PHP's built-in // phar stream wrapper. diff --git a/www7/includes/registry.inc b/www7/includes/registry.inc index 29a1fca8c..ee678e891 100644 --- a/www7/includes/registry.inc +++ b/www7/includes/registry.inc @@ -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 @@ function _registry_update() { 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 @@ function _registry_update() { $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 @@ function _registry_update() { // 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 @@ function _registry_update() { ->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 @@ function _registry_update() { 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 @@ function _registry_update() { 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 --git a/www7/misc/brumann/polyfill-unserialize/.gitignore b/www7/misc/brumann/polyfill-unserialize/.gitignore new file mode 100644 index 000000000..767699f1b --- /dev/null +++ b/www7/misc/brumann/polyfill-unserialize/.gitignore @@ -0,0 +1,4 @@ +/vendor/ +/phpunit.xml +/.composer.lock + diff --git a/www7/misc/brumann/polyfill-unserialize/.travis.yml b/www7/misc/brumann/polyfill-unserialize/.travis.yml new file mode 100644 index 000000000..352536f45 --- /dev/null +++ b/www7/misc/brumann/polyfill-unserialize/.travis.yml @@ -0,0 +1,20 @@ +language: php + +sudo: false + +php: + - '5.3' + - '5.4' + - '5.5' + - '5.6' + - '7.0' + - '7.1' + +before_install: + - phpenv config-rm xdebug.ini + - composer self-update + +install: + - composer install + +script: phpunit diff --git a/www7/misc/brumann/polyfill-unserialize/LICENSE b/www7/misc/brumann/polyfill-unserialize/LICENSE new file mode 100644 index 000000000..0cb53d3b0 --- /dev/null +++ b/www7/misc/brumann/polyfill-unserialize/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Denis Brumann + +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 --git a/www7/misc/brumann/polyfill-unserialize/README.md b/www7/misc/brumann/polyfill-unserialize/README.md new file mode 100644 index 000000000..bac25fe04 --- /dev/null +++ b/www7/misc/brumann/polyfill-unserialize/README.md @@ -0,0 +1,61 @@ +Polyfill unserialize [![Build Status](https://travis-ci.org/dbrumann/polyfill-unserialize.svg?branch=master)](https://travis-ci.org/dbrumann/polyfill-unserialize) +=== + +Backports unserialize options introduced in PHP 7.0 to older PHP versions. +This was originally designed as a Proof of Concept for Symfony Issue [#21090](https://github.com/symfony/symfony/pull/21090). + +You can use this package in projects that rely on PHP versions older than PHP 7.0. +In case you are using PHP 7.0+ the original `unserialize()` will be used instead. + +From the [documentation](https://secure.php.net/manual/en/function.unserialize.php): + +> Warning: Do not pass untrusted user input to unserialize(). Unserialization can +> result in code being loaded and executed due to object instantiation +> and autoloading, and a malicious user may be able to exploit this. + +This warning holds true even when `allowed_classes` is used. + +Requirements +------------ + + - PHP 5.3+ + +Installation +------------ + +You can install this package via composer: + +``` +composer require brumann/polyfill-unserialize "^1.0" +``` + +Known Issues +------------ + +There is a mismatch in behavior when `allowed_classes` in `$options` is not +of the correct type (array or boolean). PHP 7.1 will issue a warning, whereas +PHP 7.0 will not. I opted to copy the behavior of the former. + +Tests +----- + +You can run the test suite using PHPUnit. It is intentionally not bundled as +dev dependency to make sure this package has the lowest restrictions on the +implementing system as possible. + +Please read the [PHPUnit Manual](https://phpunit.de/manual/current/en/installation.html) +for information how to install it on your system. + +You can run the test suite as follows: + +``` +phpunit -c phpunit.xml.dist tests/ +``` + +Contributing +------------ + +This package is considered feature complete. As such I will likely not update it +unless there are security issues. + +Should you find any bugs or have questions, feel free to submit an Issue or a Pull Request. diff --git a/www7/misc/brumann/polyfill-unserialize/composer.json b/www7/misc/brumann/polyfill-unserialize/composer.json new file mode 100644 index 000000000..ec4a2cf0e --- /dev/null +++ b/www7/misc/brumann/polyfill-unserialize/composer.json @@ -0,0 +1,26 @@ +{ + "name": "brumann/polyfill-unserialize", + "description": "Backports unserialize options introduced in PHP 7.0 to older PHP versions.", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Denis Brumann", + "email": "denis.brumann@sensiolabs.de" + } + ], + "autoload": { + "psr-4": { + "Brumann\\Polyfill\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\Brumann\\Polyfill\\": "tests/" + } + }, + "minimum-stability": "stable", + "require": { + "php": "^5.3|^7.0" + } +} diff --git a/www7/misc/brumann/polyfill-unserialize/phpunit.xml.dist b/www7/misc/brumann/polyfill-unserialize/phpunit.xml.dist new file mode 100644 index 000000000..8fea1bab8 --- /dev/null +++ b/www7/misc/brumann/polyfill-unserialize/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + + + + + + ./tests/ + + + + + + ./src/ + + + diff --git a/www7/misc/brumann/polyfill-unserialize/src/Unserialize.php b/www7/misc/brumann/polyfill-unserialize/src/Unserialize.php new file mode 100644 index 000000000..e025d55ed --- /dev/null +++ b/www7/misc/brumann/polyfill-unserialize/src/Unserialize.php @@ -0,0 +1,58 @@ += 70000) { + return \unserialize($serialized, $options); + } + if (!array_key_exists('allowed_classes', $options)) { + $options['allowed_classes'] = true; + } + $allowedClasses = $options['allowed_classes']; + if (true === $allowedClasses) { + return \unserialize($serialized); + } + if (false === $allowedClasses) { + $allowedClasses = array(); + } + if (!is_array($allowedClasses)) { + trigger_error( + 'unserialize(): allowed_classes option should be array or boolean', + E_USER_WARNING + ); + $allowedClasses = array(); + } + + $sanitizedSerialized = preg_replace_callback( + '/(^|;)O:\d+:"([^"]*)":(\d+):{/', + function ($match) use ($allowedClasses) { + list($completeMatch, $leftBorder, $className, $objectSize) = $match; + if (in_array($className, $allowedClasses)) { + return $completeMatch; + } else { + return sprintf( + '%sO:22:"__PHP_Incomplete_Class":%d:{s:27:"__PHP_Incomplete_Class_Name";%s', + $leftBorder, + $objectSize + 1, // size of object + 1 for added string + \serialize($className) + ); + } + }, + $serialized + ); + + return \unserialize($sanitizedSerialized); + } +} diff --git a/www7/misc/jquery-extend-3.4.0.js b/www7/misc/jquery-extend-3.4.0.js new file mode 100644 index 000000000..975910949 --- /dev/null +++ b/www7/misc/jquery-extend-3.4.0.js @@ -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 --git a/www7/misc/typo3/phar-stream-wrapper/.gitignore b/www7/misc/typo3/phar-stream-wrapper/.gitignore new file mode 100644 index 000000000..157ff0c59 --- /dev/null +++ b/www7/misc/typo3/phar-stream-wrapper/.gitignore @@ -0,0 +1,3 @@ +.idea +vendor/ +composer.lock diff --git a/www7/misc/typo3/phar-stream-wrapper/README.md b/www7/misc/typo3/phar-stream-wrapper/README.md index b632784bd..179bb6fd7 100644 --- a/www7/misc/typo3/phar-stream-wrapper/README.md +++ b/www7/misc/typo3/phar-stream-wrapper/README.md @@ -63,7 +63,7 @@ adjusted to according requirements. ``` $behavior = new \TYPO3\PharStreamWrapper\Behavior(); -Manager::initialize( +\TYPO3\PharStreamWrapper\Manager::initialize( $behavior->withAssertion(new PharExtensionInterceptor()) ); @@ -90,7 +90,7 @@ if (in_array('phar', stream_get_wrappers())) { + `COMMAND_UNLINK` + `COMMAND_URL_STAT` -## Interceptor +## Interceptors 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 @@ -137,9 +137,72 @@ class PharExtensionInterceptor implements Assertable } ``` +### ConjunctionInterceptor + +This interceptor combines multiple interceptors implementing `Assertable`. +It succeeds when all nested interceptors succeed as well (logical `AND`). + +``` +$behavior = new \TYPO3\PharStreamWrapper\Behavior(); +\TYPO3\PharStreamWrapper\Manager::initialize( + $behavior->withAssertion(new ConjunctionInterceptor(array( + new PharExtensionInterceptor(), + new PharMetaDataInterceptor() + ))) +); +``` + +### PharExtensionInterceptor + +This (basic) interceptor just checks whether the invoked Phar archive has +an according `.phar` file extension. Resolving symbolic links as well as +Phar internal alias resolving are considered as well. + +``` +$behavior = new \TYPO3\PharStreamWrapper\Behavior(); +\TYPO3\PharStreamWrapper\Manager::initialize( + $behavior->withAssertion(new PharExtensionInterceptor()) +); +``` + +### PharMetaDataInterceptor + +This interceptor is actually checking serialized Phar meta-data against +PHP objects and would consider a Phar archive malicious in case not only +scalar values are found. A custom low-level `Phar\Reader` is used in order to +avoid using PHP's `Phar` object which would trigger the initial vulnerability. + +``` +$behavior = new \TYPO3\PharStreamWrapper\Behavior(); +\TYPO3\PharStreamWrapper\Manager::initialize( + $behavior->withAssertion(new PharMetaDataInterceptor()) +); +``` + +## Reader + +* `Phar\Reader::__construct(string $fileName)`: Creates low-level reader for Phar archive +* `Phar\Reader::resolveContainer(): Phar\Container`: Resolves model representing Phar archive +* `Phar\Container::getStub(): Phar\Stub`: Resolves (plain PHP) stub section of Phar archive +* `Phar\Container::getManifest(): Phar\Manifest`: Resolves parsed Phar archive manifest as + documented at http://php.net/manual/en/phar.fileformat.manifestfile.php +* `Phar\Stub::getMappedAlias(): string`: Resolves internal Phar archive alias defined in stub + using `Phar::mapPhar('alias.phar')` - actually the plain PHP source is analyzed here +* `Phar\Manifest::getAlias(): string` - Resolves internal Phar archive alias defined in manifest + using `Phar::setAlias('alias.phar')` +* `Phar\Manifest::getMetaData(): string`: Resolves serialized Phar archive meta-data +* `Phar\Manifest::deserializeMetaData(): mixed`: Resolves deserialized Phar archive meta-data + containing only scalar values - in case an object is determined, an according + `Phar\DeserializationException` will be thrown + +``` +$reader = new Phar\Reader('example.phar'); +var_dump($reader->resolveContainer()->getManifest()->deserializeMetaData()); +``` + ## Helper -* `Helper::determineBaseFile(string $path)`: Determines base file that can be +* `Helper::determineBaseFile(string $path): string`: 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`. diff --git a/www7/misc/typo3/phar-stream-wrapper/composer.json b/www7/misc/typo3/phar-stream-wrapper/composer.json index d308f8c87..8c2241187 100644 --- a/www7/misc/typo3/phar-stream-wrapper/composer.json +++ b/www7/misc/typo3/phar-stream-wrapper/composer.json @@ -6,9 +6,13 @@ "homepage": "https://typo3.org/", "keywords": ["php", "phar", "stream-wrapper", "security"], "require": { - "php": "^5.3.3|^7.0" + "php": "^5.3.3|^7.0", + "ext-fileinfo": "*", + "ext-json": "*", + "brumann/polyfill-unserialize": "^1.0" }, "require-dev": { + "ext-xdebug": "*", "phpunit/phpunit": "^4.8.36" }, "autoload": { diff --git a/www7/misc/typo3/phar-stream-wrapper/src/Collectable.php b/www7/misc/typo3/phar-stream-wrapper/src/Collectable.php new file mode 100644 index 000000000..4694dc946 --- /dev/null +++ b/www7/misc/typo3/phar-stream-wrapper/src/Collectable.php @@ -0,0 +1,37 @@ +assertAssertions($assertions); + $this->assertions = $assertions; + } + + /** + * Executes assertions based on all contained assertions. + * + * @param string $path + * @param string $command + * @return bool + * @throws Exception + */ + public function assert($path, $command) + { + if ($this->invokeAssertions($path, $command)) { + return true; + } + throw new Exception( + sprintf( + 'Assertion failed in "%s"', + $path + ), + 1539625084 + ); + } + + /** + * @param Assertable[] $assertions + */ + private function assertAssertions(array $assertions) + { + foreach ($assertions as $assertion) { + if (!$assertion instanceof Assertable) { + throw new \InvalidArgumentException( + sprintf( + 'Instance %s must implement Assertable', + get_class($assertion) + ), + 1539624719 + ); + } + } + } + + /** + * @param string $path + * @param string $command + * @return bool + */ + private function invokeAssertions($path, $command) + { + try { + foreach ($this->assertions as $assertion) { + if (!$assertion->assert($path, $command)) { + return false; + } + } + } catch (Exception $exception) { + return false; + } + return true; + } +} diff --git a/www7/misc/typo3/phar-stream-wrapper/src/Interceptor/PharExtensionInterceptor.php b/www7/misc/typo3/phar-stream-wrapper/src/Interceptor/PharExtensionInterceptor.php index db500afc8..6e7aeedcb 100644 --- a/www7/misc/typo3/phar-stream-wrapper/src/Interceptor/PharExtensionInterceptor.php +++ b/www7/misc/typo3/phar-stream-wrapper/src/Interceptor/PharExtensionInterceptor.php @@ -12,8 +12,8 @@ */ use TYPO3\PharStreamWrapper\Assertable; -use TYPO3\PharStreamWrapper\Helper; use TYPO3\PharStreamWrapper\Exception; +use TYPO3\PharStreamWrapper\Manager; class PharExtensionInterceptor implements Assertable { @@ -45,11 +45,11 @@ public function assert($path, $command) */ private function baseFileContainsPharExtension($path) { - $baseFile = Helper::determineBaseFile($path); - if ($baseFile === null) { + $invocation = Manager::instance()->resolve($path); + if ($invocation === null) { return false; } - $fileExtension = pathinfo($baseFile, PATHINFO_EXTENSION); + $fileExtension = pathinfo($invocation->getBaseName(), PATHINFO_EXTENSION); return strtolower($fileExtension) === 'phar'; } } diff --git a/www7/misc/typo3/phar-stream-wrapper/src/Interceptor/PharMetaDataInterceptor.php b/www7/misc/typo3/phar-stream-wrapper/src/Interceptor/PharMetaDataInterceptor.php new file mode 100644 index 000000000..e981dc6a6 --- /dev/null +++ b/www7/misc/typo3/phar-stream-wrapper/src/Interceptor/PharMetaDataInterceptor.php @@ -0,0 +1,73 @@ +baseFileDoesNotHaveMetaDataIssues($path)) { + return true; + } + throw new Exception( + sprintf( + 'Problematic meta-data in "%s"', + $path + ), + 1539632368 + ); + } + + /** + * @param string $path + * @return bool + */ + private function baseFileDoesNotHaveMetaDataIssues($path) + { + $invocation = Manager::instance()->resolve($path); + if ($invocation === null) { + return false; + } + // directly return in case invocation was checked before + if ($invocation->getVariable(__CLASS__) === true) { + return true; + } + // otherwise analyze meta-data + try { + $reader = new Reader($invocation->getBaseName()); + $reader->resolveContainer()->getManifest()->deserializeMetaData(); + $invocation->setVariable(__CLASS__, true); + } catch (DeserializationException $exception) { + return false; + } + return true; + } +} diff --git a/www7/misc/typo3/phar-stream-wrapper/src/Manager.php b/www7/misc/typo3/phar-stream-wrapper/src/Manager.php index 1eb9735d9..f938ad985 100644 --- a/www7/misc/typo3/phar-stream-wrapper/src/Manager.php +++ b/www7/misc/typo3/phar-stream-wrapper/src/Manager.php @@ -11,7 +11,11 @@ * The TYPO3 project - inspiring people to share! */ -class Manager implements Assertable +use TYPO3\PharStreamWrapper\Resolver\PharInvocation; +use TYPO3\PharStreamWrapper\Resolver\PharInvocationCollection; +use TYPO3\PharStreamWrapper\Resolver\PharInvocationResolver; + +class Manager { /** * @var self @@ -23,14 +27,29 @@ class Manager implements Assertable */ private $behavior; + /** + * @var Resolvable + */ + private $resolver; + + /** + * @var Collectable + */ + private $collection; + /** * @param Behavior $behaviour + * @param Resolvable $resolver + * @param Collectable $collection * @return self */ - public static function initialize(Behavior $behaviour) - { + public static function initialize( + Behavior $behaviour, + Resolvable $resolver = null, + Collectable $collection = null + ) { if (self::$instance === null) { - self::$instance = new self($behaviour); + self::$instance = new self($behaviour, $resolver, $collection); return self::$instance; } throw new \LogicException( @@ -67,9 +86,22 @@ public static function destroy() /** * @param Behavior $behaviour + * @param Resolvable $resolver + * @param Collectable $collection */ - private function __construct(Behavior $behaviour) - { + private function __construct( + Behavior $behaviour, + Resolvable $resolver = null, + Collectable $collection = null + ) { + if ($collection === null) { + $collection = new PharInvocationCollection(); + } + if ($resolver === null) { + $resolver = new PharInvocationResolver(); + } + $this->collection = $collection; + $this->resolver = $resolver; $this->behavior = $behaviour; } @@ -82,4 +114,22 @@ public function assert($path, $command) { return $this->behavior->assert($path, $command); } + + /** + * @param string $path + * @param null|int $flags + * @return null|PharInvocation + */ + public function resolve($path, $flags = null) + { + return $this->resolver->resolve($path, $flags); + } + + /** + * @return Collectable + */ + public function getCollection() + { + return $this->collection; + } } diff --git a/www7/misc/typo3/phar-stream-wrapper/src/Phar/Container.php b/www7/misc/typo3/phar-stream-wrapper/src/Phar/Container.php new file mode 100644 index 000000000..f02387d73 --- /dev/null +++ b/www7/misc/typo3/phar-stream-wrapper/src/Phar/Container.php @@ -0,0 +1,59 @@ +stub = $stub; + $this->manifest = $manifest; + } + + /** + * @return Stub + */ + public function getStub() + { + return $this->stub; + } + + /** + * @return Manifest + */ + public function getManifest() + { + return $this->manifest; + } + + /** + * @return string + */ + public function getAlias() + { + return $this->manifest->getAlias() ?: $this->stub->getMappedAlias(); + } +} diff --git a/www7/misc/typo3/phar-stream-wrapper/src/Phar/DeserializationException.php b/www7/misc/typo3/phar-stream-wrapper/src/Phar/DeserializationException.php new file mode 100644 index 000000000..5a675d34f --- /dev/null +++ b/www7/misc/typo3/phar-stream-wrapper/src/Phar/DeserializationException.php @@ -0,0 +1,18 @@ +manifestLength = Reader::resolveFourByteLittleEndian($content, 0); + $target->amountOfFiles = Reader::resolveFourByteLittleEndian($content, 4); + $target->flags = Reader::resolveFourByteLittleEndian($content, 10); + $target->aliasLength = Reader::resolveFourByteLittleEndian($content, 14); + $target->alias = substr($content, 18, $target->aliasLength); + $target->metaDataLength = Reader::resolveFourByteLittleEndian($content, 18 + $target->aliasLength); + $target->metaData = substr($content, 22 + $target->aliasLength, $target->metaDataLength); + + $apiVersionNibbles = Reader::resolveTwoByteBigEndian($content, 8); + $target->apiVersion = implode('.', array( + ($apiVersionNibbles & 0xf000) >> 12, + ($apiVersionNibbles & 0x0f00) >> 8, + ($apiVersionNibbles & 0x00f0) >> 4, + )); + + return $target; + } + + /** + * @var int + */ + private $manifestLength; + + /** + * @var int + */ + private $amountOfFiles; + + /** + * @var string + */ + private $apiVersion; + + /** + * @var int + */ + private $flags; + + /** + * @var int + */ + private $aliasLength; + + /** + * @var string + */ + private $alias; + + /** + * @var int + */ + private $metaDataLength; + + /** + * @var string + */ + private $metaData; + + /** + * Avoid direct instantiation. + */ + private function __construct() + { + } + + /** + * @return int + */ + public function getManifestLength() + { + return $this->manifestLength; + } + + /** + * @return int + */ + public function getAmountOfFiles() + { + return $this->amountOfFiles; + } + + /** + * @return string + */ + public function getApiVersion() + { + return $this->apiVersion; + } + + /** + * @return int + */ + public function getFlags() + { + return $this->flags; + } + + /** + * @return int + */ + public function getAliasLength() + { + return $this->aliasLength; + } + + /** + * @return string + */ + public function getAlias() + { + return $this->alias; + } + + /** + * @return int + */ + public function getMetaDataLength() + { + return $this->metaDataLength; + } + + /** + * @return string + */ + public function getMetaData() + { + return $this->metaData; + } + + /** + * @return mixed|null + */ + public function deserializeMetaData() + { + if (empty($this->metaData)) { + return null; + } + + $result = Unserialize::unserialize($this->metaData, array('allowed_classes' => false)); + + $serialized = json_encode($result); + if (strpos($serialized, '__PHP_Incomplete_Class_Name') !== false) { + throw new DeserializationException( + 'Meta-data contains serialized object', + 1539623382 + ); + } + + return $result; + } +} diff --git a/www7/misc/typo3/phar-stream-wrapper/src/Phar/Reader.php b/www7/misc/typo3/phar-stream-wrapper/src/Phar/Reader.php new file mode 100644 index 000000000..32e516be3 --- /dev/null +++ b/www7/misc/typo3/phar-stream-wrapper/src/Phar/Reader.php @@ -0,0 +1,220 @@ +fileName = $fileName; + $this->fileType = $this->determineFileType(); + } + + /** + * @return Container + */ + public function resolveContainer() + { + $data = $this->extractData($this->resolveStream() . $this->fileName); + + if ($data['stubContent'] === null) { + throw new ReaderException( + 'Cannot resolve stub', + 1547807881 + ); + } + if ($data['manifestContent'] === null || $data['manifestLength'] === null) { + throw new ReaderException( + 'Cannot resolve manifest', + 1547807882 + ); + } + if (strlen($data['manifestContent']) < $data['manifestLength']) { + throw new ReaderException( + sprintf( + 'Exected manifest length %d, got %d', + strlen($data['manifestContent']), + $data['manifestLength'] + ), + 1547807883 + ); + } + + return new Container( + Stub::fromContent($data['stubContent']), + Manifest::fromContent($data['manifestContent']) + ); + } + + /** + * @param string $fileName e.g. '/path/file.phar' or 'compress.zlib:///path/file.phar' + * @return array + */ + private function extractData($fileName) + { + $stubContent = null; + $manifestContent = null; + $manifestLength = null; + + $resource = fopen($fileName, 'r'); + if (!is_resource($resource)) { + throw new ReaderException( + sprintf('Resource %s could not be opened', $fileName), + 1547902055 + ); + } + + while (!feof($resource)) { + $line = fgets($resource); + // stop reading file when manifest can be extracted + if ($manifestLength !== null && $manifestContent !== null && strlen($manifestContent) >= $manifestLength) { + break; + } + + $manifestPosition = strpos($line, '__HALT_COMPILER();'); + + // first line contains start of manifest + if ($stubContent === null && $manifestContent === null && $manifestPosition !== false) { + $stubContent = substr($line, 0, $manifestPosition - 1); + $manifestContent = preg_replace('#^.*__HALT_COMPILER\(\);(?>[ \n]\?>(?>\r\n|\n)?)?#', '', $line); + $manifestLength = $this->resolveManifestLength($manifestContent); + // line contains start of stub + } elseif ($stubContent === null) { + $stubContent = $line; + // line contains start of manifest + } elseif ($manifestContent === null && $manifestPosition !== false) { + $manifestContent = preg_replace('#^.*__HALT_COMPILER\(\);(?>[ \n]\?>(?>\r\n|\n)?)?#', '', $line); + $manifestLength = $this->resolveManifestLength($manifestContent); + // manifest has been started (thus is cannot be stub anymore), add content + } elseif ($manifestContent !== null) { + $manifestContent .= $line; + $manifestLength = $this->resolveManifestLength($manifestContent); + // stub has been started (thus cannot be manifest here, yet), add content + } elseif ($stubContent !== null) { + $stubContent .= $line; + } + } + fclose($resource); + + return array( + 'stubContent' => $stubContent, + 'manifestContent' => $manifestContent, + 'manifestLength' => $manifestLength, + ); + } + + /** + * Resolves stream in order to handle compressed Phar archives. + * + * @return string + */ + private function resolveStream() + { + if ($this->fileType === 'application/x-gzip') { + return 'compress.zlib://'; + } elseif ($this->fileType === 'application/x-bzip2') { + return 'compress.bzip2://'; + } + return ''; + } + + /** + * @return string + */ + private function determineFileType() + { + $fileInfo = new \finfo(); + return $fileInfo->file($this->fileName, FILEINFO_MIME_TYPE); + } + + /** + * @param string $content + * @return int|null + */ + private function resolveManifestLength($content) + { + if (strlen($content) < 4) { + return null; + } + return static::resolveFourByteLittleEndian($content, 0); + } + + /** + * @param string $content + * @param int $start + * @return int + */ + public static function resolveFourByteLittleEndian($content, $start) + { + $payload = substr($content, $start, 4); + if (!is_string($payload)) { + throw new ReaderException( + sprintf('Cannot resolve value at offset %d', $start), + 1539614260 + ); + } + + $value = unpack('V', $payload); + if (!isset($value[1])) { + throw new ReaderException( + sprintf('Cannot resolve value at offset %d', $start), + 1539614261 + ); + } + return $value[1]; + } + + /** + * @param string $content + * @param int $start + * @return int + */ + public static function resolveTwoByteBigEndian($content, $start) + { + $payload = substr($content, $start, 2); + if (!is_string($payload)) { + throw new ReaderException( + sprintf('Cannot resolve value at offset %d', $start), + 1539614263 + ); + } + + $value = unpack('n', $payload); + if (!isset($value[1])) { + throw new ReaderException( + sprintf('Cannot resolve value at offset %d', $start), + 1539614264 + ); + } + return $value[1]; + } +} diff --git a/www7/misc/typo3/phar-stream-wrapper/src/Phar/ReaderException.php b/www7/misc/typo3/phar-stream-wrapper/src/Phar/ReaderException.php new file mode 100644 index 000000000..002afe158 --- /dev/null +++ b/www7/misc/typo3/phar-stream-wrapper/src/Phar/ReaderException.php @@ -0,0 +1,18 @@ +content = $content; + + if ( + stripos($content, 'Phar::mapPhar(') !== false + && preg_match('#Phar\:\:mapPhar\(([^)]+)\)#', $content, $matches) + ) { + // remove spaces, single & double quotes + // @todo `'my' . 'alias' . '.phar'` is not evaluated here + $target->mappedAlias = trim($matches[1], ' \'"'); + } + + return $target; + } + + /** + * @var string + */ + private $content; + + /** + * @var string + */ + private $mappedAlias = ''; + + /** + * @return string + */ + public function getContent() + { + return $this->content; + } + + /** + * @return string + */ + public function getMappedAlias() + { + return $this->mappedAlias; + } +} diff --git a/www7/misc/typo3/phar-stream-wrapper/src/PharStreamWrapper.php b/www7/misc/typo3/phar-stream-wrapper/src/PharStreamWrapper.php index 5a924e4cc..acd5656f4 100644 --- a/www7/misc/typo3/phar-stream-wrapper/src/PharStreamWrapper.php +++ b/www7/misc/typo3/phar-stream-wrapper/src/PharStreamWrapper.php @@ -11,6 +11,8 @@ * The TYPO3 project - inspiring people to share! */ +use TYPO3\PharStreamWrapper\Resolver\PharInvocation; + class PharStreamWrapper { /** @@ -29,6 +31,11 @@ class PharStreamWrapper */ protected $internalResource; + /** + * @var PharInvocation + */ + protected $invocation; + /** * @return bool */ @@ -409,7 +416,8 @@ public function url_stat($path, $flags) */ protected function assert($path, $command) { - if ($this->resolveAssertable()->assert($path, $command) === true) { + if (Manager::instance()->assert($path, $command) === true) { + $this->collectInvocation($path); return; } @@ -424,7 +432,33 @@ protected function assert($path, $command) } /** - * @return Assertable + * @param string $path + */ + protected function collectInvocation($path) + { + if (isset($this->invocation)) { + return; + } + + $manager = Manager::instance(); + $this->invocation = $manager->resolve($path); + if ($this->invocation === null) { + throw new Exception( + 'Expected invocation could not be resolved', + 1556389591 + ); + } + // confirm, previous interceptor(s) validated invocation + $this->invocation->confirm(); + $collection = $manager->getCollection(); + if (!$collection->has($this->invocation)) { + $collection->collect($this->invocation); + } + } + + /** + * @return Manager|Assertable + * @deprecated Use Manager::instance() directly */ protected function resolveAssertable() { diff --git a/www7/misc/typo3/phar-stream-wrapper/src/Resolvable.php b/www7/misc/typo3/phar-stream-wrapper/src/Resolvable.php new file mode 100644 index 000000000..5d5fdc63e --- /dev/null +++ b/www7/misc/typo3/phar-stream-wrapper/src/Resolvable.php @@ -0,0 +1,24 @@ +baseName = $baseName; + $this->alias = $alias; + } + + /** + * @return string + */ + public function __toString() + { + return $this->baseName; + } + + /** + * @return string + */ + public function getBaseName() + { + return $this->baseName; + } + + /** + * @return null|string + */ + public function getAlias() + { + return $this->alias; + } + + /** + * @return bool + */ + public function isConfirmed() + { + return $this->confirmed; + } + + public function confirm() + { + $this->confirmed = true; + } + + /** + * @param string $name + * @return mixed|null + */ + public function getVariable($name) + { + if (!isset($this->variables[$name])) { + return null; + } + return $this->variables[$name]; + } + + /** + * @param string $name + * @param mixed $value + */ + public function setVariable($name, $value) + { + $this->variables[$name] = $value; + } + + /** + * @param PharInvocation $other + * @return bool + */ + public function equals(PharInvocation $other) + { + return $other->baseName === $this->baseName + && $other->alias === $this->alias; + } +} \ No newline at end of file diff --git a/www7/misc/typo3/phar-stream-wrapper/src/Resolver/PharInvocationCollection.php b/www7/misc/typo3/phar-stream-wrapper/src/Resolver/PharInvocationCollection.php new file mode 100644 index 000000000..e445ff66e --- /dev/null +++ b/www7/misc/typo3/phar-stream-wrapper/src/Resolver/PharInvocationCollection.php @@ -0,0 +1,156 @@ +invocations, true); + } + + /** + * @param PharInvocation $invocation + * @param null|int $flags + * @return bool + */ + public function collect(PharInvocation $invocation, $flags = null) + { + if ($flags === null) { + $flags = static::UNIQUE_INVOCATION | static::DUPLICATE_ALIAS_WARNING; + } + if ($invocation->getBaseName() === '' + || $invocation->getAlias() === '' + || !$this->assertUniqueBaseName($invocation, $flags) + || !$this->assertUniqueInvocation($invocation, $flags) + ) { + return false; + } + if ($flags & static::DUPLICATE_ALIAS_WARNING) { + $this->triggerDuplicateAliasWarning($invocation); + } + + $this->invocations[] = $invocation; + return true; + } + + /** + * @param callable $callback + * @param bool $reverse + * @return null|PharInvocation + */ + public function findByCallback($callback, $reverse = false) + { + foreach ($this->getInvocations($reverse) as $invocation) { + if (call_user_func($callback, $invocation) === true) { + return $invocation; + } + } + return null; + } + + /** + * Asserts that base-name is unique. This disallows having multiple invocations for + * same base-name but having different alias names. + * + * @param PharInvocation $invocation + * @param int $flags + * @return bool + */ + private function assertUniqueBaseName(PharInvocation $invocation, $flags) + { + if (!($flags & static::UNIQUE_BASE_NAME)) { + return true; + } + return $this->findByCallback( + function (PharInvocation $candidate) use ($invocation) { + return $candidate->getBaseName() === $invocation->getBaseName(); + } + ) === null; + } + + /** + * Asserts that combination of base-name and alias is unique. This allows having multiple + * invocations for same base-name but having different alias names (for whatever reason). + * + * @param PharInvocation $invocation + * @param int $flags + * @return bool + */ + private function assertUniqueInvocation(PharInvocation $invocation, $flags) + { + if (!($flags & static::UNIQUE_INVOCATION)) { + return true; + } + return $this->findByCallback( + function (PharInvocation $candidate) use ($invocation) { + return $candidate->equals($invocation); + } + ) === null; + } + + /** + * Triggers warning for invocations with same alias and same confirmation state. + * + * @param PharInvocation $invocation + * @see \TYPO3\PharStreamWrapper\PharStreamWrapper::collectInvocation() + */ + private function triggerDuplicateAliasWarning(PharInvocation $invocation) + { + $sameAliasInvocation = $this->findByCallback( + function (PharInvocation $candidate) use ($invocation) { + return $candidate->isConfirmed() === $invocation->isConfirmed() + && $candidate->getAlias() === $invocation->getAlias(); + }, + true + ); + if ($sameAliasInvocation === null) { + return; + } + trigger_error( + sprintf( + 'Alias %s cannot be used by %s, already used by %s', + $invocation->getAlias(), + $invocation->getBaseName(), + $sameAliasInvocation->getBaseName() + ), + E_USER_WARNING + ); + } + + /** + * @param bool $reverse + * @return PharInvocation[] + */ + private function getInvocations($reverse = false) + { + if ($reverse) { + return array_reverse($this->invocations); + } + return $this->invocations; + } +} \ No newline at end of file diff --git a/www7/misc/typo3/phar-stream-wrapper/src/Resolver/PharInvocationResolver.php b/www7/misc/typo3/phar-stream-wrapper/src/Resolver/PharInvocationResolver.php new file mode 100644 index 000000000..80b86d3db --- /dev/null +++ b/www7/misc/typo3/phar-stream-wrapper/src/Resolver/PharInvocationResolver.php @@ -0,0 +1,241 @@ +findByAlias($path); + if ($invocation !== null) { + return $invocation; + } + } + + $baseName = $this->resolveBaseName($path, $flags); + if ($baseName === null) { + return null; + } + + if ($flags & static::RESOLVE_REALPATH) { + $baseName = $this->baseNames[$baseName]; + } + + return $this->retrieveInvocation($baseName, $flags); + } + + /** + * Retrieves PharInvocation, either existing in collection or created on demand + * with resolving a potential alias name used in the according Phar archive. + * + * @param string $baseName + * @param int $flags + * @return PharInvocation + */ + private function retrieveInvocation($baseName, $flags) + { + $invocation = $this->findByBaseName($baseName); + if ($invocation !== null) { + return $invocation; + } + + if ($flags & static::RESOLVE_ALIAS) { + $reader = new Reader($baseName); + $alias = $reader->resolveContainer()->getAlias(); + } else { + $alias = ''; + } + // add unconfirmed(!) new invocation to collection + $invocation = new PharInvocation($baseName, $alias); + Manager::instance()->getCollection()->collect($invocation); + return $invocation; + } + + /** + * @param string $path + * @param int $flags + * @return null|string + */ + private function resolveBaseName($path, $flags) + { + $baseName = $this->findInBaseNames($path); + if ($baseName !== null) { + return $baseName; + } + + $baseName = Helper::determineBaseFile($path); + if ($baseName !== null) { + $this->addBaseName($baseName); + return $baseName; + } + + $possibleAlias = $this->resolvePossibleAlias($path); + if (!($flags & static::RESOLVE_ALIAS) || $possibleAlias === null) { + return null; + } + + $trace = debug_backtrace(); + foreach ($trace as $item) { + if (!isset($item['function']) || !isset($item['args'][0]) + || !in_array($item['function'], $this->invocationFunctionNames, true)) { + continue; + } + $currentPath = $item['args'][0]; + if (Helper::hasPharPrefix($currentPath)) { + continue; + } + $currentBaseName = Helper::determineBaseFile($currentPath); + if ($currentBaseName === null) { + continue; + } + // ensure the possible alias name (how we have been called initially) matches + // the resolved alias name that was retrieved by the current possible base name + $reader = new Reader($currentBaseName); + $currentAlias = $reader->resolveContainer()->getAlias(); + if ($currentAlias !== $possibleAlias) { + continue; + } + $this->addBaseName($currentBaseName); + return $currentBaseName; + } + + return null; + } + + /** + * @param string $path + * @return null|string + */ + private function resolvePossibleAlias($path) + { + $normalizedPath = Helper::normalizePath($path); + return strstr($normalizedPath, '/', true) ?: null; + } + + /** + * @param string $baseName + * @return null|PharInvocation + */ + private function findByBaseName($baseName) + { + return Manager::instance()->getCollection()->findByCallback( + function (PharInvocation $candidate) use ($baseName) { + return $candidate->getBaseName() === $baseName; + }, + true + ); + } + + /** + * @param string $path + * @return null|string + */ + private function findInBaseNames($path) + { + // return directly if the resolved base name was submitted + if (in_array($path, $this->baseNames, true)) { + return $path; + } + + $parts = explode('/', Helper::normalizePath($path)); + + while (count($parts)) { + $currentPath = implode('/', $parts); + if (isset($this->baseNames[$currentPath])) { + return $currentPath; + } + array_pop($parts); + } + + return null; + } + + /** + * @param string $baseName + */ + private function addBaseName($baseName) + { + if (isset($this->baseNames[$baseName])) { + return; + } + $this->baseNames[$baseName] = realpath($baseName); + } + + /** + * Finds confirmed(!) invocations by alias. + * + * @param string $path + * @return null|PharInvocation + * @see \TYPO3\PharStreamWrapper\PharStreamWrapper::collectInvocation() + */ + private function findByAlias($path) + { + $possibleAlias = $this->resolvePossibleAlias($path); + if ($possibleAlias === null) { + return null; + } + return Manager::instance()->getCollection()->findByCallback( + function (PharInvocation $candidate) use ($possibleAlias) { + return $candidate->isConfirmed() && $candidate->getAlias() === $possibleAlias; + }, + true + ); + } +} diff --git a/www7/modules/aggregator/aggregator.info b/www7/modules/aggregator/aggregator.info index a5532dc2f..d96edbdbf 100644 --- a/www7/modules/aggregator/aggregator.info +++ b/www7/modules/aggregator/aggregator.info @@ -7,7 +7,7 @@ files[] = aggregator.test configure = admin/config/services/aggregator/settings stylesheets[all][] = aggregator.css -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/aggregator/tests/aggregator_test.info b/www7/modules/aggregator/tests/aggregator_test.info index 90a8b2f63..e0253ce91 100644 --- a/www7/modules/aggregator/tests/aggregator_test.info +++ b/www7/modules/aggregator/tests/aggregator_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/block/block.info b/www7/modules/block/block.info index c9c4171ec..8fccc4881 100644 --- a/www7/modules/block/block.info +++ b/www7/modules/block/block.info @@ -6,7 +6,7 @@ core = 7.x files[] = block.test configure = admin/structure/block -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/block/tests/block_test.info b/www7/modules/block/tests/block_test.info index beb9141a0..dbd486261 100644 --- a/www7/modules/block/tests/block_test.info +++ b/www7/modules/block/tests/block_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/block/tests/themes/block_test_theme/block_test_theme.info b/www7/modules/block/tests/themes/block_test_theme/block_test_theme.info index 89305fc0e..9fe95fa66 100644 --- a/www7/modules/block/tests/themes/block_test_theme/block_test_theme.info +++ b/www7/modules/block/tests/themes/block_test_theme/block_test_theme.info @@ -13,7 +13,7 @@ regions[footer] = Footer regions[highlighted] = Highlighted regions[help] = Help -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/blog/blog.info b/www7/modules/blog/blog.info index 041aadcfe..aade48494 100644 --- a/www7/modules/blog/blog.info +++ b/www7/modules/blog/blog.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = blog.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/book/book.info b/www7/modules/book/book.info index a3c5af7eb..95054e461 100644 --- a/www7/modules/book/book.info +++ b/www7/modules/book/book.info @@ -7,7 +7,7 @@ files[] = book.test configure = admin/content/book/settings stylesheets[all][] = book.css -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/color/color.info b/www7/modules/color/color.info index 2f986d902..764e24944 100644 --- a/www7/modules/color/color.info +++ b/www7/modules/color/color.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = color.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/comment/comment.info b/www7/modules/comment/comment.info index 7970128c7..8cf5f1673 100644 --- a/www7/modules/comment/comment.info +++ b/www7/modules/comment/comment.info @@ -9,7 +9,7 @@ files[] = comment.test configure = admin/content/comment stylesheets[all][] = comment.css -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/contact/contact.info b/www7/modules/contact/contact.info index 663818e1e..b7c403c8c 100644 --- a/www7/modules/contact/contact.info +++ b/www7/modules/contact/contact.info @@ -6,7 +6,7 @@ core = 7.x files[] = contact.test configure = admin/structure/contact -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/contextual/contextual.info b/www7/modules/contextual/contextual.info index d23cc9a81..17a83bd3e 100644 --- a/www7/modules/contextual/contextual.info +++ b/www7/modules/contextual/contextual.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = contextual.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/dashboard/dashboard.info b/www7/modules/dashboard/dashboard.info index d3ca72162..4fc749d3c 100644 --- a/www7/modules/dashboard/dashboard.info +++ b/www7/modules/dashboard/dashboard.info @@ -7,7 +7,7 @@ files[] = dashboard.test dependencies[] = block configure = admin/dashboard/customize -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/dblog/dblog.info b/www7/modules/dblog/dblog.info index 26e173710..cbc892687 100644 --- a/www7/modules/dblog/dblog.info +++ b/www7/modules/dblog/dblog.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = dblog.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/field/field.info b/www7/modules/field/field.info index 2228c0591..5a60e62ae 100644 --- a/www7/modules/field/field.info +++ b/www7/modules/field/field.info @@ -11,7 +11,7 @@ dependencies[] = field_sql_storage required = TRUE stylesheets[all][] = theme/field.css -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/field/modules/field_sql_storage/field_sql_storage.info b/www7/modules/field/modules/field_sql_storage/field_sql_storage.info index e43fe820f..ecf448ea6 100644 --- a/www7/modules/field/modules/field_sql_storage/field_sql_storage.info +++ b/www7/modules/field/modules/field_sql_storage/field_sql_storage.info @@ -7,7 +7,7 @@ dependencies[] = field files[] = field_sql_storage.test required = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/field/modules/list/list.info b/www7/modules/field/modules/list/list.info index 851292eb0..433bb72f8 100644 --- a/www7/modules/field/modules/list/list.info +++ b/www7/modules/field/modules/list/list.info @@ -7,7 +7,7 @@ dependencies[] = field dependencies[] = options files[] = tests/list.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/field/modules/list/tests/list_test.info b/www7/modules/field/modules/list/tests/list_test.info index 9a8e792ec..f1a251179 100644 --- a/www7/modules/field/modules/list/tests/list_test.info +++ b/www7/modules/field/modules/list/tests/list_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/field/modules/number/number.info b/www7/modules/field/modules/number/number.info index a7853d595..18b595b1a 100644 --- a/www7/modules/field/modules/number/number.info +++ b/www7/modules/field/modules/number/number.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = field files[] = number.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/field/modules/number/number.test b/www7/modules/field/modules/number/number.test index 839da36c3..db225855b 100644 --- a/www7/modules/field/modules/number/number.test +++ b/www7/modules/field/modules/number/number.test @@ -69,7 +69,7 @@ class NumberFieldTestCase extends DrupalWebTestCase { 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( diff --git a/www7/modules/field/modules/options/options.info b/www7/modules/field/modules/options/options.info index 77b1359f8..b55064304 100644 --- a/www7/modules/field/modules/options/options.info +++ b/www7/modules/field/modules/options/options.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = field files[] = options.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/field/modules/text/text.info b/www7/modules/field/modules/text/text.info index 39881eb18..1627800bc 100644 --- a/www7/modules/field/modules/text/text.info +++ b/www7/modules/field/modules/text/text.info @@ -7,7 +7,7 @@ dependencies[] = field files[] = text.test required = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/field/tests/field_test.info b/www7/modules/field/tests/field_test.info index e0f42e987..a74f7b1d3 100644 --- a/www7/modules/field/tests/field_test.info +++ b/www7/modules/field/tests/field_test.info @@ -6,7 +6,7 @@ files[] = field_test.entity.inc version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/field_ui/field_ui.info b/www7/modules/field_ui/field_ui.info index d7564f5a4..b44f1c96a 100644 --- a/www7/modules/field_ui/field_ui.info +++ b/www7/modules/field_ui/field_ui.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = field files[] = field_ui.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/file/file.field.inc b/www7/modules/file/file.field.inc index d592381bd..fc1a1df20 100644 --- a/www7/modules/file/file.field.inc +++ b/www7/modules/file/file.field.inc @@ -599,7 +599,7 @@ function file_field_widget_value($element, $input = FALSE, $form_state) { // If the display field is present make sure its unchecked value is saved. $field = field_widget_field($element, $form_state); if (empty($input['display'])) { - $input['display'] = $field['settings']['display_field'] ? 0 : 1; + $input['display'] = !empty($field['settings']['display_field']) ? 0 : 1; } } diff --git a/www7/modules/file/file.info b/www7/modules/file/file.info index f2d0872fd..220bae2ae 100644 --- a/www7/modules/file/file.info +++ b/www7/modules/file/file.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = field files[] = tests/file.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/file/tests/file.test b/www7/modules/file/tests/file.test index f764a9033..849451a55 100644 --- a/www7/modules/file/tests/file.test +++ b/www7/modules/file/tests/file.test @@ -1875,3 +1875,60 @@ class FileFieldAnonymousSubmission extends FileFieldTestCase { } } + +/** + * Tests the file_scan_directory() function. + */ +class FileScanDirectory extends FileFieldTestCase { + + /** + * @var string + */ + protected $path; + + /** + * {@inheritdoc} + */ + public static function getInfo() { + return array( + 'name' => 'File ScanDirectory', + 'description' => 'Tests the file_scan_directory() function.', + 'group' => 'File', + ); + } + + /** + * {@inheritdoc} + */ + function setUp() { + parent::setUp(); + + $this->path = 'modules/file/tests/fixtures/file_scan_ignore'; + } + + /** + * Tests file_scan_directory() obeys 'file_scan_ignore_directories' setting. + * If nomask is not passed as argument, it should use the default settings. + * If nomask is passed as argument, it should obey this rule. + */ + public function testNoMask() { + $files = file_scan_directory($this->path, '/\.txt$/'); + $this->assertEqual(3, count($files), '3 text files found when not ignoring directories.'); + + global $conf; + $conf['file_scan_ignore_directories'] = array('frontend_framework'); + + $files = file_scan_directory($this->path, '/\.txt$/'); + $this->assertEqual(1, count($files), '1 text files found when ignoring directories called "frontend_framework".'); + + // Make that directories specified by default still work when a new nomask is provided. + $files = file_scan_directory($this->path, '/\.txt$/', array('nomask' => '/^c.txt/')); + $this->assertEqual(2, count($files), '2 text files found when an "nomask" option is passed in.'); + + // Ensure that the directories in file_scan_ignore_directories are escaped using preg_quote. + $conf['file_scan_ignore_directories'] = array('frontend.*'); + $files = file_scan_directory($this->path, '/\.txt$/'); + $this->assertEqual(3, count($files), '2 text files found when ignoring a directory that is not there.'); + } + +} diff --git a/www7/modules/file/tests/file_module_test.info b/www7/modules/file/tests/file_module_test.info index a8b4acc9d..5bad5c83d 100644 --- a/www7/modules/file/tests/file_module_test.info +++ b/www7/modules/file/tests/file_module_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/file/tests/fixtures/file_scan_ignore/a.txt b/www7/modules/file/tests/fixtures/file_scan_ignore/a.txt new file mode 100644 index 000000000..e69de29bb diff --git a/www7/modules/file/tests/fixtures/file_scan_ignore/frontend_framework/b.txt b/www7/modules/file/tests/fixtures/file_scan_ignore/frontend_framework/b.txt new file mode 100644 index 000000000..e69de29bb diff --git a/www7/modules/file/tests/fixtures/file_scan_ignore/frontend_framework/c.txt b/www7/modules/file/tests/fixtures/file_scan_ignore/frontend_framework/c.txt new file mode 100644 index 000000000..e69de29bb diff --git a/www7/modules/filter/filter.info b/www7/modules/filter/filter.info index f16777f81..3133b03a9 100644 --- a/www7/modules/filter/filter.info +++ b/www7/modules/filter/filter.info @@ -7,7 +7,7 @@ files[] = filter.test required = TRUE configure = admin/config/content/formats -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/forum/forum.info b/www7/modules/forum/forum.info index 758f35b23..d389c244a 100644 --- a/www7/modules/forum/forum.info +++ b/www7/modules/forum/forum.info @@ -9,7 +9,7 @@ files[] = forum.test configure = admin/structure/forum stylesheets[all][] = forum.css -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/help/help.info b/www7/modules/help/help.info index 46ee825cd..18f1cd342 100644 --- a/www7/modules/help/help.info +++ b/www7/modules/help/help.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = help.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/image/image.info b/www7/modules/image/image.info index 7903f2c25..b2940584d 100644 --- a/www7/modules/image/image.info +++ b/www7/modules/image/image.info @@ -7,7 +7,7 @@ dependencies[] = file files[] = image.test configure = admin/config/media/image-styles -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/image/tests/image_module_test.info b/www7/modules/image/tests/image_module_test.info index 124b6996b..57d065efb 100644 --- a/www7/modules/image/tests/image_module_test.info +++ b/www7/modules/image/tests/image_module_test.info @@ -6,7 +6,7 @@ core = 7.x files[] = image_module_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/locale/locale.info b/www7/modules/locale/locale.info index fb257d1aa..f295d8890 100644 --- a/www7/modules/locale/locale.info +++ b/www7/modules/locale/locale.info @@ -6,7 +6,7 @@ core = 7.x files[] = locale.test configure = admin/config/regional/language -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/locale/tests/locale_test.info b/www7/modules/locale/tests/locale_test.info index 5727160f2..15f49ec88 100644 --- a/www7/modules/locale/tests/locale_test.info +++ b/www7/modules/locale/tests/locale_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/menu/menu.info b/www7/modules/menu/menu.info index 52b127b6f..b24f2966c 100644 --- a/www7/modules/menu/menu.info +++ b/www7/modules/menu/menu.info @@ -6,7 +6,7 @@ core = 7.x files[] = menu.test configure = admin/structure/menu -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/node/node.info b/www7/modules/node/node.info index 91e552477..e802aa682 100644 --- a/www7/modules/node/node.info +++ b/www7/modules/node/node.info @@ -9,7 +9,7 @@ required = TRUE configure = admin/structure/types stylesheets[all][] = node.css -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/node/tests/node_access_test.info b/www7/modules/node/tests/node_access_test.info index b4c70c84c..c78209ad4 100644 --- a/www7/modules/node/tests/node_access_test.info +++ b/www7/modules/node/tests/node_access_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/node/tests/node_test.info b/www7/modules/node/tests/node_test.info index 162dca0e4..c12a8c154 100644 --- a/www7/modules/node/tests/node_test.info +++ b/www7/modules/node/tests/node_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/node/tests/node_test_exception.info b/www7/modules/node/tests/node_test_exception.info index 80032910e..a23c4c7ca 100644 --- a/www7/modules/node/tests/node_test_exception.info +++ b/www7/modules/node/tests/node_test_exception.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/openid/openid.info b/www7/modules/openid/openid.info index c6b932514..5e630f9cc 100644 --- a/www7/modules/openid/openid.info +++ b/www7/modules/openid/openid.info @@ -5,7 +5,7 @@ package = Core core = 7.x files[] = openid.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/openid/tests/openid_test.info b/www7/modules/openid/tests/openid_test.info index 5f06fc28e..f7ea5dca3 100644 --- a/www7/modules/openid/tests/openid_test.info +++ b/www7/modules/openid/tests/openid_test.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = openid hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/overlay/overlay.info b/www7/modules/overlay/overlay.info index d8f9cf62f..00a167bc5 100644 --- a/www7/modules/overlay/overlay.info +++ b/www7/modules/overlay/overlay.info @@ -4,7 +4,7 @@ package = Core version = VERSION core = 7.x -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/path/path.info b/www7/modules/path/path.info index 1878b27df..40fb7ea35 100644 --- a/www7/modules/path/path.info +++ b/www7/modules/path/path.info @@ -6,7 +6,7 @@ core = 7.x files[] = path.test configure = admin/config/search/path -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/php/php.info b/www7/modules/php/php.info index 8f64cf09a..256116e9b 100644 --- a/www7/modules/php/php.info +++ b/www7/modules/php/php.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = php.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/poll/poll.info b/www7/modules/poll/poll.info index 630ba42a1..7224eadcb 100644 --- a/www7/modules/poll/poll.info +++ b/www7/modules/poll/poll.info @@ -6,7 +6,7 @@ core = 7.x files[] = poll.test stylesheets[all][] = poll.css -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/profile/profile.info b/www7/modules/profile/profile.info index 7d1e79147..907b1b0a8 100644 --- a/www7/modules/profile/profile.info +++ b/www7/modules/profile/profile.info @@ -11,7 +11,7 @@ configure = admin/config/people/profile ; See user_system_info_alter(). hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/rdf/rdf.info b/www7/modules/rdf/rdf.info index ee1d9c828..5ae8c6569 100644 --- a/www7/modules/rdf/rdf.info +++ b/www7/modules/rdf/rdf.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = rdf.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/rdf/tests/rdf_test.info b/www7/modules/rdf/tests/rdf_test.info index 063514f73..c95e12da9 100644 --- a/www7/modules/rdf/tests/rdf_test.info +++ b/www7/modules/rdf/tests/rdf_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = blog -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/search/search.info b/www7/modules/search/search.info index 4806cb8f3..5478e5b96 100644 --- a/www7/modules/search/search.info +++ b/www7/modules/search/search.info @@ -8,7 +8,7 @@ files[] = search.test configure = admin/config/search/settings stylesheets[all][] = search.css -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/search/tests/search_embedded_form.info b/www7/modules/search/tests/search_embedded_form.info index a16393ff2..bc8e49cd5 100644 --- a/www7/modules/search/tests/search_embedded_form.info +++ b/www7/modules/search/tests/search_embedded_form.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/search/tests/search_extra_type.info b/www7/modules/search/tests/search_extra_type.info index 85d76cc17..85e45dfb5 100644 --- a/www7/modules/search/tests/search_extra_type.info +++ b/www7/modules/search/tests/search_extra_type.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/search/tests/search_node_tags.info b/www7/modules/search/tests/search_node_tags.info index 5f0f84d6c..a62e6f1cf 100644 --- a/www7/modules/search/tests/search_node_tags.info +++ b/www7/modules/search/tests/search_node_tags.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/shortcut/shortcut.info b/www7/modules/shortcut/shortcut.info index a8bcc2681..69c95d1b1 100644 --- a/www7/modules/shortcut/shortcut.info +++ b/www7/modules/shortcut/shortcut.info @@ -6,7 +6,7 @@ core = 7.x files[] = shortcut.test configure = admin/config/user-interface/shortcut -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/drupal_web_test_case.php b/www7/modules/simpletest/drupal_web_test_case.php index 3124ffe82..a0872c234 100644 --- a/www7/modules/simpletest/drupal_web_test_case.php +++ b/www7/modules/simpletest/drupal_web_test_case.php @@ -3012,7 +3012,7 @@ protected function assertRaw($raw, $message = '', $group = 'Other') { 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); } /** @@ -3032,7 +3032,7 @@ protected function assertNoRaw($raw, $message = '', $group = 'Other') { 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 --git a/www7/modules/simpletest/simpletest.info b/www7/modules/simpletest/simpletest.info index 7190c6764..783ace447 100644 --- a/www7/modules/simpletest/simpletest.info +++ b/www7/modules/simpletest/simpletest.info @@ -57,7 +57,7 @@ files[] = tests/upgrade/update.trigger.test files[] = tests/upgrade/update.field.test files[] = tests/upgrade/update.user.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/actions_loop_test.info b/www7/modules/simpletest/tests/actions_loop_test.info index a7939735b..25db0c71a 100644 --- a/www7/modules/simpletest/tests/actions_loop_test.info +++ b/www7/modules/simpletest/tests/actions_loop_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/ajax_forms_test.info b/www7/modules/simpletest/tests/ajax_forms_test.info index d802f30c3..56e2f1a91 100644 --- a/www7/modules/simpletest/tests/ajax_forms_test.info +++ b/www7/modules/simpletest/tests/ajax_forms_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/ajax_test.info b/www7/modules/simpletest/tests/ajax_test.info index 6ecc1ca0f..39cf716d9 100644 --- a/www7/modules/simpletest/tests/ajax_test.info +++ b/www7/modules/simpletest/tests/ajax_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/batch_test.info b/www7/modules/simpletest/tests/batch_test.info index 579ca641a..ef335ad42 100644 --- a/www7/modules/simpletest/tests/batch_test.info +++ b/www7/modules/simpletest/tests/batch_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/boot_test_1.info b/www7/modules/simpletest/tests/boot_test_1.info index b46d19f6b..139fe8f09 100644 --- a/www7/modules/simpletest/tests/boot_test_1.info +++ b/www7/modules/simpletest/tests/boot_test_1.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/boot_test_2.info b/www7/modules/simpletest/tests/boot_test_2.info index c2d6f8ced..7e38cc274 100644 --- a/www7/modules/simpletest/tests/boot_test_2.info +++ b/www7/modules/simpletest/tests/boot_test_2.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/bootstrap.test b/www7/modules/simpletest/tests/bootstrap.test index 16ac1714f..effd04bf2 100644 --- a/www7/modules/simpletest/tests/bootstrap.test +++ b/www7/modules/simpletest/tests/bootstrap.test @@ -729,16 +729,12 @@ class BootstrapMiscTestCase extends DrupalUnitTestCase { * 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 --git a/www7/modules/simpletest/tests/common_test.info b/www7/modules/simpletest/tests/common_test.info index 21e565d01..79f8badde 100644 --- a/www7/modules/simpletest/tests/common_test.info +++ b/www7/modules/simpletest/tests/common_test.info @@ -7,7 +7,7 @@ stylesheets[all][] = common_test.css stylesheets[print][] = common_test.print.css hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/common_test_cron_helper.info b/www7/modules/simpletest/tests/common_test_cron_helper.info index cfe45113e..cf545fa05 100644 --- a/www7/modules/simpletest/tests/common_test_cron_helper.info +++ b/www7/modules/simpletest/tests/common_test_cron_helper.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/database_test.info b/www7/modules/simpletest/tests/database_test.info index a88282371..2862e6a60 100644 --- a/www7/modules/simpletest/tests/database_test.info +++ b/www7/modules/simpletest/tests/database_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info b/www7/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info index 766405496..da7721609 100644 --- a/www7/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info +++ b/www7/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info @@ -7,7 +7,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info b/www7/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info index d1f3545e4..a5697a704 100644 --- a/www7/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info +++ b/www7/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info b/www7/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info index 061a14376..4897bbd2f 100644 --- a/www7/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info +++ b/www7/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/entity_cache_test.info b/www7/modules/simpletest/tests/entity_cache_test.info index 7f52e24b6..3f7211a6a 100644 --- a/www7/modules/simpletest/tests/entity_cache_test.info +++ b/www7/modules/simpletest/tests/entity_cache_test.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = entity_cache_test_dependency hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/entity_cache_test_dependency.info b/www7/modules/simpletest/tests/entity_cache_test_dependency.info index 5dc9a367d..8710673f0 100644 --- a/www7/modules/simpletest/tests/entity_cache_test_dependency.info +++ b/www7/modules/simpletest/tests/entity_cache_test_dependency.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/entity_crud_hook_test.info b/www7/modules/simpletest/tests/entity_crud_hook_test.info index edc4d5ca4..f2a6b50f8 100644 --- a/www7/modules/simpletest/tests/entity_crud_hook_test.info +++ b/www7/modules/simpletest/tests/entity_crud_hook_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/entity_query_access_test.info b/www7/modules/simpletest/tests/entity_query_access_test.info index 21d13156f..dd1fd0f0e 100644 --- a/www7/modules/simpletest/tests/entity_query_access_test.info +++ b/www7/modules/simpletest/tests/entity_query_access_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/error_test.info b/www7/modules/simpletest/tests/error_test.info index c7fe885ee..40048ccbe 100644 --- a/www7/modules/simpletest/tests/error_test.info +++ b/www7/modules/simpletest/tests/error_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/file.test b/www7/modules/simpletest/tests/file.test index 55dd1906e..032f2cbac 100644 --- a/www7/modules/simpletest/tests/file.test +++ b/www7/modules/simpletest/tests/file.test @@ -957,6 +957,15 @@ class FileDirectoryTest extends FileTestCase { $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 @@ class FileDirectoryTest extends FileTestCase { $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()); + } } /** diff --git a/www7/modules/simpletest/tests/file_test.info b/www7/modules/simpletest/tests/file_test.info index d73960ce7..849fcb316 100644 --- a/www7/modules/simpletest/tests/file_test.info +++ b/www7/modules/simpletest/tests/file_test.info @@ -6,7 +6,7 @@ core = 7.x files[] = file_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/filter_test.info b/www7/modules/simpletest/tests/filter_test.info index 6433ddd3a..3b847858b 100644 --- a/www7/modules/simpletest/tests/filter_test.info +++ b/www7/modules/simpletest/tests/filter_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/form_test.info b/www7/modules/simpletest/tests/form_test.info index a2657115f..350ce2595 100644 --- a/www7/modules/simpletest/tests/form_test.info +++ b/www7/modules/simpletest/tests/form_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/image_test.info b/www7/modules/simpletest/tests/image_test.info index cc14c8f94..5724949e5 100644 --- a/www7/modules/simpletest/tests/image_test.info +++ b/www7/modules/simpletest/tests/image_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/menu_test.info b/www7/modules/simpletest/tests/menu_test.info index c61864d13..717c52580 100644 --- a/www7/modules/simpletest/tests/menu_test.info +++ b/www7/modules/simpletest/tests/menu_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/module_test.info b/www7/modules/simpletest/tests/module_test.info index bdf17ea1c..c985a0412 100644 --- a/www7/modules/simpletest/tests/module_test.info +++ b/www7/modules/simpletest/tests/module_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/path_test.info b/www7/modules/simpletest/tests/path_test.info index 4f177d4de..259063967 100644 --- a/www7/modules/simpletest/tests/path_test.info +++ b/www7/modules/simpletest/tests/path_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/psr_0_test/psr_0_test.info b/www7/modules/simpletest/tests/psr_0_test/psr_0_test.info index 3d01fab31..5d83fcb27 100644 --- a/www7/modules/simpletest/tests/psr_0_test/psr_0_test.info +++ b/www7/modules/simpletest/tests/psr_0_test/psr_0_test.info @@ -5,7 +5,7 @@ core = 7.x hidden = TRUE package = Testing -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/psr_4_test/psr_4_test.info b/www7/modules/simpletest/tests/psr_4_test/psr_4_test.info index 0619a2430..ac574e151 100644 --- a/www7/modules/simpletest/tests/psr_4_test/psr_4_test.info +++ b/www7/modules/simpletest/tests/psr_4_test/psr_4_test.info @@ -5,7 +5,7 @@ core = 7.x hidden = TRUE package = Testing -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/requirements1_test.info b/www7/modules/simpletest/tests/requirements1_test.info index 783326cf3..2901a6bad 100644 --- a/www7/modules/simpletest/tests/requirements1_test.info +++ b/www7/modules/simpletest/tests/requirements1_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/requirements2_test.info b/www7/modules/simpletest/tests/requirements2_test.info index 8312c3a24..8c03a0cc8 100644 --- a/www7/modules/simpletest/tests/requirements2_test.info +++ b/www7/modules/simpletest/tests/requirements2_test.info @@ -7,7 +7,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/session_test.info b/www7/modules/simpletest/tests/session_test.info index 8bf277cfc..37f353849 100644 --- a/www7/modules/simpletest/tests/session_test.info +++ b/www7/modules/simpletest/tests/session_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/system_dependencies_test.info b/www7/modules/simpletest/tests/system_dependencies_test.info index 2a82092ac..de4b98122 100644 --- a/www7/modules/simpletest/tests/system_dependencies_test.info +++ b/www7/modules/simpletest/tests/system_dependencies_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = _missing_dependency -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info b/www7/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info index a385a05ac..e0bd91bd9 100644 --- a/www7/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info +++ b/www7/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = system_incompatible_core_version_test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/system_incompatible_core_version_test.info b/www7/modules/simpletest/tests/system_incompatible_core_version_test.info index e12847471..6afe2e4fa 100644 --- a/www7/modules/simpletest/tests/system_incompatible_core_version_test.info +++ b/www7/modules/simpletest/tests/system_incompatible_core_version_test.info @@ -5,7 +5,7 @@ version = VERSION core = 5.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info b/www7/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info index 29bd5445c..a12a356cb 100644 --- a/www7/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info +++ b/www7/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info @@ -7,7 +7,7 @@ hidden = TRUE ; system_incompatible_module_version_test declares version 1.0 dependencies[] = system_incompatible_module_version_test (>2.0) -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/system_incompatible_module_version_test.info b/www7/modules/simpletest/tests/system_incompatible_module_version_test.info index 7383a2796..028183805 100644 --- a/www7/modules/simpletest/tests/system_incompatible_module_version_test.info +++ b/www7/modules/simpletest/tests/system_incompatible_module_version_test.info @@ -5,7 +5,7 @@ version = 1.0 core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/system_project_namespace_test.info b/www7/modules/simpletest/tests/system_project_namespace_test.info index 9bc711dc2..136c6b6ad 100644 --- a/www7/modules/simpletest/tests/system_project_namespace_test.info +++ b/www7/modules/simpletest/tests/system_project_namespace_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = drupal:filter -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/system_test.info b/www7/modules/simpletest/tests/system_test.info index 5e78b89b4..b66b34b8f 100644 --- a/www7/modules/simpletest/tests/system_test.info +++ b/www7/modules/simpletest/tests/system_test.info @@ -6,7 +6,7 @@ core = 7.x files[] = system_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/taxonomy_test.info b/www7/modules/simpletest/tests/taxonomy_test.info index 59f714229..09893faa6 100644 --- a/www7/modules/simpletest/tests/taxonomy_test.info +++ b/www7/modules/simpletest/tests/taxonomy_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = taxonomy -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/theme_test.info b/www7/modules/simpletest/tests/theme_test.info index c810edcb2..31faf08cd 100644 --- a/www7/modules/simpletest/tests/theme_test.info +++ b/www7/modules/simpletest/tests/theme_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info b/www7/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info index cab073b5a..e3cac6c30 100644 --- a/www7/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info +++ b/www7/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info @@ -6,7 +6,7 @@ hidden = TRUE settings[basetheme_only] = base theme value settings[subtheme_override] = base theme value -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info b/www7/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info index d80c5d0ff..6287d8ed5 100644 --- a/www7/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info +++ b/www7/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info @@ -6,7 +6,7 @@ hidden = TRUE settings[subtheme_override] = subtheme value -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/themes/test_theme/test_theme.info b/www7/modules/simpletest/tests/themes/test_theme/test_theme.info index a0e0dd68f..04324c809 100644 --- a/www7/modules/simpletest/tests/themes/test_theme/test_theme.info +++ b/www7/modules/simpletest/tests/themes/test_theme/test_theme.info @@ -17,7 +17,7 @@ stylesheets[all][] = system.base.css settings[theme_test_setting] = default value -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info b/www7/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info index 49513572f..2276ea896 100644 --- a/www7/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info +++ b/www7/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info @@ -4,7 +4,7 @@ core = 7.x hidden = TRUE engine = nyan_cat -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/update_script_test.info b/www7/modules/simpletest/tests/update_script_test.info index ea7b5065f..839b054e5 100644 --- a/www7/modules/simpletest/tests/update_script_test.info +++ b/www7/modules/simpletest/tests/update_script_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/update_test_1.info b/www7/modules/simpletest/tests/update_test_1.info index 3e3b36f65..5ed289208 100644 --- a/www7/modules/simpletest/tests/update_test_1.info +++ b/www7/modules/simpletest/tests/update_test_1.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/update_test_2.info b/www7/modules/simpletest/tests/update_test_2.info index 3e3b36f65..5ed289208 100644 --- a/www7/modules/simpletest/tests/update_test_2.info +++ b/www7/modules/simpletest/tests/update_test_2.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/update_test_3.info b/www7/modules/simpletest/tests/update_test_3.info index 3e3b36f65..5ed289208 100644 --- a/www7/modules/simpletest/tests/update_test_3.info +++ b/www7/modules/simpletest/tests/update_test_3.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/url_alter_test.info b/www7/modules/simpletest/tests/url_alter_test.info index 36d1ef401..0d55fbf0b 100644 --- a/www7/modules/simpletest/tests/url_alter_test.info +++ b/www7/modules/simpletest/tests/url_alter_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/simpletest/tests/xmlrpc_test.info b/www7/modules/simpletest/tests/xmlrpc_test.info index 2c6d90cc3..488bc50bd 100644 --- a/www7/modules/simpletest/tests/xmlrpc_test.info +++ b/www7/modules/simpletest/tests/xmlrpc_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/statistics/statistics.info b/www7/modules/statistics/statistics.info index 617ada790..f078b4143 100644 --- a/www7/modules/statistics/statistics.info +++ b/www7/modules/statistics/statistics.info @@ -6,7 +6,7 @@ core = 7.x files[] = statistics.test configure = admin/config/system/statistics -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/syslog/syslog.info b/www7/modules/syslog/syslog.info index 0814a3983..28197ac3e 100644 --- a/www7/modules/syslog/syslog.info +++ b/www7/modules/syslog/syslog.info @@ -6,7 +6,7 @@ core = 7.x files[] = syslog.test configure = admin/config/development/logging -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/system/system.info b/www7/modules/system/system.info index dfaf47fef..74c8fa4aa 100644 --- a/www7/modules/system/system.info +++ b/www7/modules/system/system.info @@ -12,7 +12,7 @@ files[] = system.test required = TRUE configure = admin/config/system -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/system/system.install b/www7/modules/system/system.install index d5e67435d..3bb07d955 100644 --- a/www7/modules/system/system.install +++ b/www7/modules/system/system.install @@ -2870,7 +2870,7 @@ function system_update_7061(&$sandbox) { 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); @@ -3285,6 +3285,13 @@ function system_update_7081() { ->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 --git a/www7/modules/system/system.module b/www7/modules/system/system.module index 53844d878..4ce6b9b99 100644 --- a/www7/modules/system/system.module +++ b/www7/modules/system/system.module @@ -1182,6 +1182,9 @@ function system_library() { '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), ), ); diff --git a/www7/modules/system/tests/cron_queue_test.info b/www7/modules/system/tests/cron_queue_test.info index d574d64dc..f43ee03a8 100644 --- a/www7/modules/system/tests/cron_queue_test.info +++ b/www7/modules/system/tests/cron_queue_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/system/tests/system_cron_test.info b/www7/modules/system/tests/system_cron_test.info index d57b6043a..086825f49 100644 --- a/www7/modules/system/tests/system_cron_test.info +++ b/www7/modules/system/tests/system_cron_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/taxonomy/taxonomy.info b/www7/modules/taxonomy/taxonomy.info index 34e21b910..28a287ec8 100644 --- a/www7/modules/taxonomy/taxonomy.info +++ b/www7/modules/taxonomy/taxonomy.info @@ -8,7 +8,7 @@ files[] = taxonomy.module files[] = taxonomy.test configure = admin/structure/taxonomy -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/toolbar/toolbar.info b/www7/modules/toolbar/toolbar.info index 55f8c5cfc..0fb05069e 100644 --- a/www7/modules/toolbar/toolbar.info +++ b/www7/modules/toolbar/toolbar.info @@ -4,7 +4,7 @@ core = 7.x package = Core version = VERSION -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/tracker/tracker.info b/www7/modules/tracker/tracker.info index 098696228..867aa592b 100644 --- a/www7/modules/tracker/tracker.info +++ b/www7/modules/tracker/tracker.info @@ -6,7 +6,7 @@ version = VERSION core = 7.x files[] = tracker.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/translation/tests/translation_test.info b/www7/modules/translation/tests/translation_test.info index a4e0bd281..1efe113d9 100644 --- a/www7/modules/translation/tests/translation_test.info +++ b/www7/modules/translation/tests/translation_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/translation/translation.info b/www7/modules/translation/translation.info index cab857c4b..7184e9909 100644 --- a/www7/modules/translation/translation.info +++ b/www7/modules/translation/translation.info @@ -6,7 +6,7 @@ version = VERSION core = 7.x files[] = translation.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/trigger/tests/trigger_test.info b/www7/modules/trigger/tests/trigger_test.info index b49b6e984..bf83fa084 100644 --- a/www7/modules/trigger/tests/trigger_test.info +++ b/www7/modules/trigger/tests/trigger_test.info @@ -4,7 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/trigger/trigger.info b/www7/modules/trigger/trigger.info index 4baee27e3..92e6fc83c 100644 --- a/www7/modules/trigger/trigger.info +++ b/www7/modules/trigger/trigger.info @@ -6,7 +6,7 @@ core = 7.x files[] = trigger.test configure = admin/structure/trigger -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/update/tests/aaa_update_test.info b/www7/modules/update/tests/aaa_update_test.info index e39841306..3d8718ac9 100644 --- a/www7/modules/update/tests/aaa_update_test.info +++ b/www7/modules/update/tests/aaa_update_test.info @@ -4,7 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/update/tests/bbb_update_test.info b/www7/modules/update/tests/bbb_update_test.info index bf0ea54bd..9916b5048 100644 --- a/www7/modules/update/tests/bbb_update_test.info +++ b/www7/modules/update/tests/bbb_update_test.info @@ -4,7 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/update/tests/ccc_update_test.info b/www7/modules/update/tests/ccc_update_test.info index a541d0def..b80db4dbd 100644 --- a/www7/modules/update/tests/ccc_update_test.info +++ b/www7/modules/update/tests/ccc_update_test.info @@ -4,7 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info b/www7/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info index c6507858f..f3bf0a622 100644 --- a/www7/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info +++ b/www7/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info @@ -3,7 +3,7 @@ description = Test theme which is used as admin theme. core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info b/www7/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info index 4f1bb1251..27ca277d3 100644 --- a/www7/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info +++ b/www7/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info @@ -3,7 +3,7 @@ description = Test theme which acts as a base theme for other test subthemes. core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info b/www7/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info index b3d8ba9d3..9184b5c56 100644 --- a/www7/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info +++ b/www7/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info @@ -4,7 +4,7 @@ core = 7.x base theme = update_test_basetheme hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/update/tests/update_test.info b/www7/modules/update/tests/update_test.info index 4a0ab86a9..6b6a9f7b8 100644 --- a/www7/modules/update/tests/update_test.info +++ b/www7/modules/update/tests/update_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/update/update.info b/www7/modules/update/update.info index 0f591346f..9c49f6dcd 100644 --- a/www7/modules/update/update.info +++ b/www7/modules/update/update.info @@ -6,7 +6,7 @@ core = 7.x files[] = update.test configure = admin/reports/updates/settings -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/user/tests/user_form_test.info b/www7/modules/user/tests/user_form_test.info index 0526ab2b7..a761b4e4e 100644 --- a/www7/modules/user/tests/user_form_test.info +++ b/www7/modules/user/tests/user_form_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/modules/user/user.info b/www7/modules/user/user.info index 6c132a38b..e23c49137 100644 --- a/www7/modules/user/user.info +++ b/www7/modules/user/user.info @@ -9,7 +9,7 @@ required = TRUE configure = admin/config/people stylesheets[all][] = user.css -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/profiles/minimal/minimal.info b/www7/profiles/minimal/minimal.info index a4d469df4..9a5c04d81 100644 --- a/www7/profiles/minimal/minimal.info +++ b/www7/profiles/minimal/minimal.info @@ -5,7 +5,7 @@ core = 7.x dependencies[] = block dependencies[] = dblog -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/profiles/standard/standard.info b/www7/profiles/standard/standard.info index 9c30220f4..beff9922b 100644 --- a/www7/profiles/standard/standard.info +++ b/www7/profiles/standard/standard.info @@ -24,7 +24,7 @@ dependencies[] = field_ui dependencies[] = file dependencies[] = rdf -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info b/www7/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info index 2adcf3dc3..7600c6a30 100644 --- a/www7/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info +++ b/www7/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE files[] = drupal_system_listing_compatible_test.test -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info b/www7/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info index 8ae9c3fcd..d81c41ded 100644 --- a/www7/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info +++ b/www7/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info @@ -8,7 +8,7 @@ version = VERSION core = 6.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/profiles/testing/testing.info b/www7/profiles/testing/testing.info index a8a2c5c98..6646ce611 100644 --- a/www7/profiles/testing/testing.info +++ b/www7/profiles/testing/testing.info @@ -4,7 +4,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2019-01-16 -version = "7.63" +; Information added by Drupal.org packaging script on 2019-05-08 +version = "7.67" project = "drupal" -datestamp = "1547681965" +datestamp = "1557336079" diff --git a/www7/sites/all/modules/contrib/commerce_paypal/commerce_paypal.info b/www7/sites/all/modules/contrib/commerce_paypal/commerce_paypal.info index 7a8793257..6f50d5a0b 100644 --- a/www7/sites/all/modules/contrib/commerce_paypal/commerce_paypal.info +++ b/www7/sites/all/modules/contrib/commerce_paypal/commerce_paypal.info @@ -10,8 +10,8 @@ core = 7.x ; Simple tests ; files[] = tests/commerce_paypal.test -; Information added by Drupal.org packaging script on 2018-10-22 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2019-05-06 +version = "7.x-2.7" core = "7.x" project = "commerce_paypal" -datestamp = "1540244884" +datestamp = "1557130694" diff --git a/www7/sites/all/modules/contrib/commerce_paypal/commerce_paypal.module b/www7/sites/all/modules/contrib/commerce_paypal/commerce_paypal.module index dad78213f..2a617538f 100644 --- a/www7/sites/all/modules/contrib/commerce_paypal/commerce_paypal.module +++ b/www7/sites/all/modules/contrib/commerce_paypal/commerce_paypal.module @@ -488,13 +488,12 @@ function commerce_paypal_reverse_payment_action($payment_action) { */ function commerce_paypal_currencies($method_id) { switch ($method_id) { - case 'paypal_wpp': - case 'paypal_ec': - case 'payflow_link': - return drupal_map_assoc(array('AUD', 'BRL', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD', 'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD')); case 'paypal_ppa': return drupal_map_assoc(array('AUD', 'CAD', 'EUR', 'GBP', 'JPY', 'USD')); case 'paypal_wps': + case 'paypal_wpp': + case 'paypal_ec': + case 'payflow_link': return drupal_map_assoc(array('AUD', 'BRL', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD', 'HUF', 'ILS', 'INR', 'JPY', 'MXN', 'MYR', 'NOK', 'NZD', 'PHP', 'PLN', 'RUB', 'SEK', 'SGD', 'THB', 'TRY', 'TWD', 'USD')); } } diff --git a/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/commerce_paypal_checkout.api.php b/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/commerce_paypal_checkout.api.php new file mode 100644 index 000000000..c1ad7eb89 --- /dev/null +++ b/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/commerce_paypal_checkout.api.php @@ -0,0 +1,32 @@ + array( + 'title' => 'Capture', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('commerce_paypal_checkout_capture_form', 3, 5), + 'access callback' => 'commerce_paypal_checkout_capture_void_access', + 'access arguments' => array(3, 5), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, + 'weight' => 2, + 'file' => 'includes/commerce_paypal_checkout.admin.inc', + ), + // Add a menu item for voiding authorizations. + 'admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/paypal-checkout-void' => array( + 'title' => 'Void', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('commerce_paypal_checkout_void_form', 3, 5), + 'access callback' => 'commerce_paypal_checkout_capture_void_access', + 'access arguments' => array(3, 5), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, + 'weight' => 4, + 'file' => 'includes/commerce_paypal_checkout.admin.inc', + ), + // Add a menu item for refunding settled transactions. + 'admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/paypal-checkout-refund' => array( + 'title' => 'Refund', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('commerce_paypal_checkout_refund_form', 3, 5), + 'access callback' => 'commerce_paypal_checkout_refund_access', + 'access arguments' => array(3, 5), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, + 'weight' => 4, + 'file' => 'includes/commerce_paypal_checkout.admin.inc', + ), + 'commerce-paypal-checkout/create-order/%commerce_order/%commerce_paypal_checkout_method_instance' => array( + 'page callback' => 'commerce_paypal_checkout_create_order', + 'page arguments' => array(2, 3), + 'access callback' => 'commerce_checkout_access', + 'access arguments' => array(2), + 'type' => MENU_CALLBACK, + ), + 'commerce-paypal-checkout/approve-order/%commerce_order/%commerce_paypal_checkout_method_instance/%' => array( + 'page callback' => 'commerce_paypal_checkout_approve_order', + 'page arguments' => array(2, 3, 4), + 'access callback' => 'commerce_checkout_access', + 'access arguments' => array(2), + 'type' => MENU_CALLBACK, + ), + ); +} + +/** + * Determines access to the prior authorization capture form or void form for + * Paypal Checkout transactions. + * + * @param $order + * The order the transaction is on. + * @param $transaction + * The payment transaction object to be captured. + * + * @return + * TRUE or FALSE indicating access. + */ +function commerce_paypal_checkout_capture_void_access($order, $transaction) { + // Return FALSE if the transaction isn't for Paypal Checkout or isn't + // awaiting capture. + if ($transaction->payment_method != 'paypal_checkout' || strtolower($transaction->remote_status) != 'created') { + return FALSE; + } + + // Return FALSE if the transaction is not pending. + if ($transaction->status != COMMERCE_PAYMENT_STATUS_PENDING) { + return FALSE; + } + + // Return FALSE if it is more than 29 days past the original authorization. + if (REQUEST_TIME - $transaction->created > 86400 * 29) { + return FALSE; + } + + // Allow access if the user can update payments on this transaction. + return commerce_payment_transaction_access('update', $transaction); +} + +/** + * Determines access to the refund form for Paypal Checkout transactions. + * + * @param $order + * The order the transaction is on. + * @param $transaction + * The payment transaction object to be captured. + * + * @return + * TRUE or FALSE indicating access. + */ +function commerce_paypal_checkout_refund_access($order, $transaction) { + // Return FALSE if the transaction isn't Completed. + if ($transaction->payment_method != 'paypal_checkout' || strtolower($transaction->remote_status) != 'completed') { + return FALSE; + } + + // Return FALSE if the transaction was not a success. + if ($transaction->status != COMMERCE_PAYMENT_STATUS_SUCCESS) { + return FALSE; + } + + // Return FALSE if it is more than 60 days since the original transaction. + if (REQUEST_TIME - $transaction->created > 86400 * 60) { + return FALSE; + } + + // Allow access if the user can update payments on this transaction. + return commerce_payment_transaction_access('update', $transaction); +} + +/** + * Load the given PayPal checkout payment method instance. + * + * @param $rule_name + * The enabling's rule name. + * + * @return + * The payment method instance object which is identical to the payment method + * object with the addition of the settings array. + */ +function commerce_paypal_checkout_method_instance_load($rule_name) { + return commerce_payment_method_instance_load("paypal_checkout|$rule_name"); +} + +/** + * Implements hook_commerce_checkout_page_info(). + */ +function commerce_paypal_checkout_commerce_checkout_page_info() { + $checkout_pages = array(); + + $checkout_pages['paypal_checkout'] = array( + 'title' => t('Confirm order'), + 'help' => t('Confirm your order information and use the button at the bottom of the page to finalize your payment.'), + 'status_cart' => FALSE, + 'locked' => TRUE, + 'buttons' => FALSE, + 'weight' => 30, + ); + + return $checkout_pages; +} + +/** + * Implements hook_commerce_checkout_pane_info(). + */ +function commerce_paypal_checkout_commerce_checkout_pane_info() { + $checkout_panes = array(); + + $checkout_panes['paypal_checkout_review'] = array( + 'title' => t('Review and confirm your order'), + 'name' => t('PayPal Checkout review and confirm (only to be used on the confirm order page)'), + 'file' => 'includes/commerce_paypal_checkout.checkout_pane.inc', + 'base' => 'commerce_paypal_checkout_review_pane', + 'page' => 'paypal_checkout', + 'fieldset' => FALSE, + ); + + return $checkout_panes; +} + +/** + * Implements hook_commerce_checkout_router(). + */ +function commerce_paypal_checkout_commerce_checkout_router($order, $checkout_page) { + // If the current page is the PayPal Checkout page but the current order did + // not use the shortcut Checkout flow... + if ($checkout_page['page_id'] == 'paypal_checkout' && + (empty($order->data['commerce_paypal_checkout']['flow']) || $order->data['commerce_paypal_checkout']['flow'] != 'shortcut')) { + // Update the order status to the next checkout page. + $next_page = $checkout_page['next_page']; + $order = commerce_order_status_update($order, 'checkout_' . $next_page, FALSE, FALSE); + + // Inform modules of checkout completion if the next page is Completed. + if ($next_page == 'complete') { + commerce_checkout_complete($order); + } + + // Redirect to the URL for the new checkout page. + $target_uri = commerce_checkout_order_uri($order); + return drupal_goto($target_uri); + } +} + +/** + * Returns the checkout pane IDs of checkout panes that should be embedded in + * the PayPal Checkout review and confirm page. + */ +function commerce_paypal_checkout_embedded_checkout_panes() { + return array_filter(variable_get('commerce_paypal_checkout_review_embedded_panes', array())); +} + +/** + * Implements hook_commerce_payment_method_info(). + */ +function commerce_paypal_checkout_commerce_payment_method_info() { + $payment_methods = array(); + + $payment_methods['paypal_checkout'] = array( + 'base' => 'commerce_paypal_checkout', + 'title' => t('PayPal Checkout'), + 'display_title' => t('PayPal'), + 'short_title' => t('PayPal Checkout'), + 'description' => t('PayPal Checkout'), + 'terminal' => FALSE, + 'offsite' => TRUE, + 'offsite_autoredirect' => FALSE, + ); + + return $payment_methods; +} + +/** + * Returns the default settings for the PayPal Checkout payment method. + */ +function commerce_paypal_checkout_default_settings() { + $default_settings = array( + 'client_id' => '', + 'secret' => '', + 'server' => 'sandbox', + 'intent' => 'capture', + 'disable_funding' => array(), + 'disable_card' => array(), + 'shipping_preference' => 'get_from_file', + 'update_billing_profiles' => TRUE, + 'style' => array(), + 'enable_on_cart' => TRUE, + ); + + if (module_exists('commerce_shipping')) { + $default_settings['update_shipping_profiles'] = TRUE; + } + + return $default_settings; +} + +/** + * Payment method callback: settings form. + */ +function commerce_paypal_checkout_settings_form($settings = array()) { + $form = array(); + + // Merge default settings into the stored settings array. + $settings = (array) $settings + commerce_paypal_checkout_default_settings(); + $shipping_module_enabled = module_exists('commerce_shipping'); + + $form['client_id'] = array( + '#type' => 'textfield', + '#title' => t('Client ID'), + '#maxlength' => 255, + '#default_value' => $settings['client_id'], + '#required' => TRUE, + ); + $form['secret'] = array( + '#type' => 'textfield', + '#title' => t('Secret'), + '#maxlength' => 255, + '#default_value' => $settings['secret'], + '#required' => TRUE, + ); + $form['server'] = array( + '#type' => 'radios', + '#title' => t('PayPal server'), + '#options' => array( + 'sandbox' => ('Sandbox - use for testing, requires a PayPal Sandbox account'), + 'live' => ('Live - use for processing real transactions'), + ), + '#default_value' => $settings['server'], + ); + $form['intent'] = array( + '#type' => 'radios', + '#title' => t('Transaction type'), + '#options' => array( + 'capture' => t('Capture'), + 'authorize' => t('Authorize'), + ), + '#default_value' => $settings['intent'], + ); + $form['disable_funding'] = array( + '#type' => 'checkboxes', + '#title' => t('Disable funding sources'), + '#description' => t('The disabled funding sources for the transaction. Any funding sources passed are not displayed in the Smart payment buttons. By default, funding source eligibility is smartly decided based on a variety of factors.'), + '#options' => array( + 'card' => t('Credit or Debit Cards'), + 'credit' => t('PayPal Credit'), + 'sepa' => t('SEPA-Lastschrift'), + ), + '#default_value' => $settings['disable_funding'], + '#element_validate' => array('commerce_paypal_checkout_disable_funding_validate'), + ); + $form['disable_card'] = array( + '#title' => t('Disable card types'), + '#description' => t('The disabled cards for the transaction. Any cards passed do not display in the Smart payment buttons. By default, card eligibility is smartly decided based on a variety of factors.'), + '#type' => 'checkboxes', + '#options' => array( + 'visa' => t('Visa'), + 'mastercard' => t('Mastercard'), + 'amex' => t('American Express'), + 'discover' => t('Discover'), + 'jcb' => t('JCB'), + 'elo' => t('Elo'), + 'hiper' => t('Hiper'), + ), + '#default_value' => $settings['disable_card'], + '#element_validate' => array('commerce_paypal_checkout_disable_card_validate'), + ); + $form['shipping_preference'] = array( + '#type' => 'radios', + '#title' => t('Shipping address collection'), + '#description' => t('PayPal Checkout will only request a shipping address if the Shipping module is enabled to store the address in the order.'), + '#options' => array( + 'no_shipping' => t('Do not ask for a shipping address at PayPal.'), + ), + '#default_value' => 'no_shipping', + ); + + if ($shipping_module_enabled) { + $form['shipping_preference']['#options'] += array( + 'get_from_file' => t('Ask for a shipping address at PayPal even if the order already has one.'), + 'set_provided_address' => t('Ask for a shipping address at PayPal if the order does not have one yet.'), + ); + $form['shipping_preference']['#default_value'] = $settings['shipping_preference']; + } + + $form['update_billing_profiles'] = array( + '#type' => 'checkbox', + '#title' => t('Update billing customer profiles with address information the customer enters at PayPal.'), + '#default_value' => $settings['update_billing_profiles'], + ); + + if ($shipping_module_enabled) { + $form['update_shipping_profiles'] = array( + '#type' => 'checkbox', + '#title' => t('Update shipping customer profiles with address information the customer enters at PayPal.'), + '#default_value' => $settings['update_shipping_profiles'], + ); + } + + $form['customize_buttons'] = array( + '#type' => 'checkbox', + '#title' => t('Smart button style'), + '#default_value' => !empty($settings['style']), + '#title_display' => 'before', + '#field_suffix' => t('Customize'), + '#description_display' => 'before', + '#element_validate' => array('commerce_paypal_checkout_customize_buttons_validate'), + ); + $form['style'] = array( + '#type' => 'fieldset', + '#title' => t('Settings'), + '#description' => t('For more information, please visit customize the PayPal buttons.', array('@url' => 'https://developer.paypal.com/docs/checkout/integration-features/customize-button/#layout')), + '#states' => array( + 'visible' => array( + ':input[name="parameter[payment_method][settings][payment_method][settings][customize_buttons]"]' => array('checked' => TRUE), + ), + ), + ); + // Define some default values for the style configuration. + $settings['style'] += [ + 'layout' => 'vertical', + 'color' => 'gold', + 'shape' => 'rect', + 'label' => 'paypal', + 'tagline' => FALSE, + ]; + $form['style']['layout'] = array( + '#type' => 'select', + '#title' => t('Layout'), + '#default_value' => $settings['style']['layout'], + '#options' => array( + 'vertical' => t('Vertical (Recommended)'), + 'horizontal' => t('Horizontal'), + ), + ); + $form['style']['color'] = array( + '#type' => 'select', + '#title' => t('Color'), + '#options' => array( + 'gold' => t('Gold (Recommended)'), + 'blue' => t('Blue'), + 'silver' => t('Silver'), + ), + '#default_value' => $settings['style']['color'], + ); + $form['style']['shape'] = array( + '#type' => 'select', + '#title' => t('Shape'), + '#options' => array( + 'rect' => t('Rect (Default)'), + 'pill' => t('Pill'), + ), + '#default_value' => $settings['style']['shape'], + ); + $form['style']['label'] = array( + '#type' => 'select', + '#title' => t('Label'), + '#options' => array( + 'paypal' => t('Displays the PayPal logo (Default)'), + 'checkout' => t('Displays the PayPal Checkout button'), + 'pay' => t('Displays the Pay With PayPal button and initializes the checkout flow'), + ), + '#default_value' => $settings['style']['label'], + ); + $form['style']['tagline'] = array( + '#type' => 'checkbox', + '#title' => t('Display tagline'), + '#default_value' => $settings['style']['tagline'], + '#states' => array( + 'visible' => array( + ':input[name="parameter[payment_method][settings][payment_method][settings][style][layout]"]' => array('value' => 'horizontal'), + ), + ), + ); + + $form['enable_on_cart'] = array( + '#type' => 'checkbox', + '#title' => t('Show the Smart payment buttons on the cart form.'), + '#default_value' => $settings['enable_on_cart'], + ); + + return $form; +} + +/** + * Element validate callback for the customize_buttons payment method setting. + */ +function commerce_paypal_checkout_customize_buttons_validate($element, &$form_state, $form) { + // Make sure no style is saved when the the "customize_buttons" checkbox + // is unchecked. + $parents = $element['#parents']; + array_pop($parents); + $parents[] = 'style'; + if (empty($element['#value'])) { + drupal_array_set_nested_value($form_state['values'], $parents, array(), TRUE); + } + else { + $style = drupal_array_get_nested_value($form_state['values'], $parents); + // The tagline is not allowed for the vertical layout. + if ($style['layout'] == 'vertical') { + unset($style['tagline']); + drupal_array_set_nested_value($form_state['values'], $parents, $style, TRUE); + } + } +} + +/** + * Element validate callback for the disable_funding payment method setting. + */ +function commerce_paypal_checkout_disable_funding_validate($element, &$form_state, $form) { + form_set_value($element, array_filter($element['#value']), $form_state); +} + +/** + * Element validate callback for the disable_card payment method setting. + */ +function commerce_paypal_checkout_disable_card_validate($element, &$form_state, $form) { + $parents = $element['#parents']; + array_pop($parents); + $parents[] = 'disable_funding'; + $disable_funding = drupal_array_get_nested_value($form_state['values'], $parents); + + // When the "card" funding source is disabled, the "disable_card" setting + // cannot be specified. + if (isset($disable_funding['card'])) { + $element['#value'] = array(); + } + form_set_value($element, array_filter($element['#value']), $form_state); +} + +/** + * Page callback: Provide the createOrder() callback expected by the SDK. + */ +function commerce_paypal_checkout_create_order($order, $payment_method) { + $settings = $payment_method['settings']; + $api_client = commerce_paypal_checkout_api_client($settings); + + if (!$api_client) { + drupal_json_output(array()); + drupal_exit(); + } + try { + $request_body = commerce_paypal_checkout_prepare_order_request($order, $payment_method['settings']); + drupal_alter('commerce_paypal_checkout_create_order_request', $request_body, $order); + $json = $api_client->createOrder($request_body); + drupal_json_output(array('id' => $json['id'])); + drupal_exit(); + } + catch (\Exception $exception) { + watchdog_exception('commerce_paypal_checkout', $exception); + } + drupal_json_output(array()); + drupal_exit(); +} + +/** + * Page callback: Provide the onApprove() callback expected by the SDK. + */ +function commerce_paypal_checkout_approve_order($order, $payment_method, $flow) { + $data = drupal_json_decode(file_get_contents('php://input')); + + if (!in_array($flow, array('shortcut', 'mark')) || !isset($data['id'])) { + drupal_json_output(array()); + drupal_exit(); + } + // Store the PayPal order ID, and the "flow" used ("shortcut"|"mark"). + // Note that we don't perform any validation here, that happens inside + // commerce_paypal_checkout_redirect_form_validate(). + $order->data['commerce_paypal_checkout'] = array( + 'flow' => $flow, + 'remote_id' => $data['id'], + ); + + // The payment_redirect key is required in the payment return url. + if (empty($order->data['payment_redirect_key'])) { + $order->data['payment_redirect_key'] = drupal_hash_base64(time()); + } + // We have to manually set the payment method if empty, it's also required + // by the payment redirect form validate callback. + if (empty($order->data['payment_method'])) { + $order->data['payment_method'] = $payment_method['instance_id']; + } + + // Update the order status to the payment page for the shortcut flow. + if ($flow == 'shortcut') { + commerce_order_status_update($order, 'checkout_payment', FALSE, NULL, t('Customer clicked the Smart payment buttons on the cart page.')); + } + else { + commerce_order_save($order); + } + + $return_url = url('checkout/' . $order->order_id . '/payment/return/' . $order->data['payment_redirect_key']); + drupal_json_output(array('redirectUri' => $return_url)); + drupal_exit(); +} + +/** + * Prepare the request parameters for the create/update order request. + * + * @param $order + * The order to prepare the request for. + * @param array $settings + * The payment method settings. + */ +function commerce_paypal_checkout_prepare_order_request($order, $settings) { + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $product_line_item_types = commerce_product_line_item_types(); + $item_total = 0; + $discount_total = 0; + $shipping_total = 0; + $items = array(); + $order_total = $order_wrapper->commerce_order_total->value(); + $currency_code = $order_total['currency_code']; + foreach ($order_wrapper->commerce_line_items as $line_item_wrapper) { + if (!$line_item_wrapper->value()) { + continue; + } + $unit_price = $line_item_wrapper->commerce_unit_price->value(); + if ($line_item_wrapper->getBundle() == 'shipping') { + $shipping_total += $unit_price['amount']; + } + elseif ($line_item_wrapper->getBundle() == 'commerce_discount') { + $discount_total += -$unit_price['amount']; + } + // PayPal Checkout doesn't support passing negative items. + elseif ($unit_price['amount'] >= 0) { + $item_total += $line_item_wrapper->commerce_total->amount->value(); + $item = array( + 'name' => mb_substr(commerce_line_item_title($line_item_wrapper->value()), 0, 127), + 'unit_amount' => array( + 'currency_code' => $currency_code, + 'value' => commerce_paypal_checkout_price_amount($unit_price['amount'], $unit_price['currency_code']), + ), + 'quantity' => intval($line_item_wrapper->quantity->value()), + ); + // Pass the "SKU" for product line items. + if (in_array($line_item_wrapper->getBundle(), $product_line_item_types)) { + $item['sku'] = $line_item_wrapper->commerce_product->sku->value(); + } + $items[] = $item; + } + } + + // @todo: Support passing discount in the breakdown when + // https://github.com/paypal/paypal-checkout-components/issues/1016 is fixed. + $breakdown = array( + 'item_total' => array( + 'currency_code' => $currency_code, + 'value' => commerce_paypal_checkout_price_amount($item_total, $currency_code), + ), + ); + + if ($shipping_total) { + $breakdown['shipping'] = array( + 'currency_code' => $currency_code, + 'value' => commerce_paypal_checkout_price_amount($shipping_total, $currency_code), + ); + } + + if (module_exists('commerce_tax')) { + $tax_total = commerce_round(COMMERCE_ROUND_HALF_UP, commerce_tax_total_amount($order_total['data']['components'], FALSE, $currency_code)); + if ($tax_total) { + $breakdown['tax_total'] = array( + 'currency_code' => $currency_code, + 'value' => commerce_paypal_checkout_price_amount($tax_total, $currency_code), + ); + } + } + + if ($discount_total) { + $breakdown['discount'] = array( + 'currency_code' => $currency_code, + 'value' => commerce_paypal_checkout_price_amount($discount_total, $currency_code), + ); + } + + $payer = array(); + if (!empty($order->mail)) { + $payer['email_address'] = $order->mail; + } + + // If we have a billing address, pass it to PayPal. + if (isset($order_wrapper->commerce_customer_billing) && !empty($order_wrapper->commerce_customer_billing->commerce_customer_address)) { + $address = $order_wrapper->commerce_customer_billing->commerce_customer_address->value(); + $payer += commerce_paypal_checkout_format_address($address, 'billing'); + } + + $request_body = array( + 'intent' => strtoupper($settings['intent']), + 'purchase_units' => array( + array( + 'reference_id' => 'default', + 'custom_id' => $order->order_id, + 'invoice_id' => $order->order_id . '-' . time(), + 'amount' => array( + 'currency_code' => $currency_code, + 'value' => commerce_paypal_checkout_price_amount($order_total['amount'], $order_total['currency_code']), + 'breakdown' => $breakdown, + ), + 'items' => $items, + ), + ), + 'application_context' => array( + 'brand_name' => mb_substr(variable_get('site_name', ''), 0, 127), + ), + ); + + // Send the payer if not empty. + if ($payer) { + $request_body['payer'] = $payer; + } + + $shipping_exists = module_exists('commerce_shipping'); + $shipping_address = FALSE; + // If the shipping module is enabled... + if ($shipping_exists) { + // If we have a shipping address, pass it to PayPal. + if (isset($order_wrapper->commerce_customer_shipping) && !empty($order_wrapper->commerce_customer_shipping->commerce_customer_address)) { + $address = $order_wrapper->commerce_customer_shipping->commerce_customer_address->value(); + $shipping_address = commerce_paypal_checkout_format_address($address, 'shipping'); + } + } + $shipping_preference = $settings['shipping_preference']; + + // The shipping module isn't enabled, override the shipping preference + // configured. + if (!$shipping_exists) { + $shipping_preference = 'no_shipping'; + } + else { + // If no shipping address was already collected, override the shipping + // preference to "GET_FROM_FILE" so that the shipping address is collected + // on the PayPal site. + if ($shipping_preference == 'set_provided_address' && !$shipping_address) { + $shipping_preference = 'get_from_file'; + } + } + + // No need to pass a shipping_address if the shipping address collection + // is configured to "no_shipping". + if ($shipping_address && $shipping_preference !== 'no_shipping') { + $request_body['purchase_units'][0]['shipping'] = $shipping_address; + } + $request_body['application_context']['shipping_preference'] = strtoupper($shipping_preference); + return $request_body; +} + +/** + * Formats the given address into a format expected by PayPal. + * + * @param array $address + * The address to format. + * @param $profile_type + * The profile type ("billing"|"shipping"). + * + * @return array + * The formatted address. + */ +function commerce_paypal_checkout_format_address($address, $profile_type) { + $return = array( + 'address' => array( + 'address_line_1' => $address['thoroughfare'], + 'address_line_2' => $address['premise'], + 'admin_area_2' => mb_substr($address['locality'], 0, 120), + 'admin_area_1' => $address['administrative_area'], + 'postal_code' => mb_substr($address['postal_code'], 0, 60), + 'country_code' => $address['country'], + ), + ); + if ($profile_type == 'billing') { + $return['name'] = array( + 'given_name' => $address['first_name'], + 'surname' => $address['last_name'], + ); + } + elseif ($profile_type == 'shipping') { + $return['name'] = array( + 'full_name' => $address['name_line'], + ); + } + return $return; +} + +/** + * Implements hook_theme(). + */ +function commerce_paypal_checkout_theme($existing, $type, $theme, $path) { + return array( + 'commerce_paypal_checkout_smart_payment_buttons' => array( + 'variables' => array( + 'payment_method' => array(), + 'commit' => FALSE, + 'order' => NULL, + 'flow' => NULL, + ), + ), + ); +} + +/** + * Returns HTML for the Smart payment buttons. + * + * @param $variables + * An associative array containing: + * - payment_method: The payment method instance. + * - commit: A boolean indicating whether to commit the transaction. + * - order: The order. + * - flow: The flow ("shortcut"|"mark"). + * + * @ingroup themeable + */ +function theme_commerce_paypal_checkout_smart_payment_buttons($variables) { + $payment_method = $variables['payment_method']; + $settings = $payment_method['settings'] + commerce_paypal_checkout_default_settings(); + if (empty($settings['client_id']) || empty($variables['order']) || empty($variables['flow'])) { + return; + } + $order = $variables['order']; + $order_total = field_get_items('commerce_order', $order, 'commerce_order_total', LANGUAGE_NONE); + if (!isset($order_total[0]['currency_code'])) { + return; + } + $flow = $variables['flow']; + list(, $rule_name) = explode('|', $payment_method['instance_id']); + $options = array( + 'external' => TRUE, + 'query' => array( + 'client-id' => $settings['client_id'], + 'commit' => $variables['commit'] ? 'true' : 'false', + 'intent' => $settings['intent'], + 'currency' => $order_total[0]['currency_code'], + ), + ); + if (!empty($settings['disable_funding'])) { + $options['query']['disable-funding'] = implode(',', $settings['disable_funding']); + } + if (!empty($settings['disable_card'])) { + $options['query']['disable-card'] = implode(',', $settings['disable_card']); + } + $path = drupal_get_path('module', 'commerce_paypal_checkout'); + $js_settings = array( + 'paypalCheckout' => array( + 'src' => url('https://www.paypal.com/sdk/js', $options), + 'createOrderUri' => url("commerce-paypal-checkout/create-order/$order->order_id/$rule_name"), + 'onApproveUri' => url("commerce-paypal-checkout/approve-order/$order->order_id/$rule_name/$flow"), + 'style' => $settings['style'], + ), + ); + drupal_add_css($path . '/css/commerce_paypal_checkout.css'); + drupal_add_js($js_settings, 'setting'); + drupal_add_js($path . '/js/commerce_paypal_checkout.js'); + $id = drupal_html_id('paypal-buttons-container'); + return '
'; +} + +/** + * Returns the first configured PayPal checkout payment method instance for the + * given order, if any. + * + * @param $order + * The order that needs to be checked. + * + * @return array|bool + * The first payment method instance found for given order, FALSE otherwise. + */ +function commerce_paypal_checkout_get_payment_method_instance($order) { + if (empty($order->payment_methods)) { + $order->payment_methods = array(); + rules_invoke_all('commerce_payment_methods', $order); + + // Sort the payment methods array by the enabling Rules' weight values. + uasort($order->payment_methods, 'drupal_sort_weight'); + } + + foreach (array_keys($order->payment_methods) as $instance_id) { + // Explode the method key into its component parts. + list($method_id) = explode('|', $instance_id); + if ($method_id != 'paypal_checkout') { + continue; + } + return commerce_payment_method_instance_load($instance_id); + } + + return FALSE; +} + +/** + * Implements hook_form_alter(). + */ +function commerce_paypal_checkout_form_alter(&$form, &$form_state, $form_id) { + if (!is_string($form_id)) { + return; + } + + // If we're altering a shopping cart form. + if (strpos($form_id, 'views_form_commerce_cart_form_') === 0) { + // If the cart form View shows line items... + if (!empty($form_state['build_info']['args'][0]->result)) { + $order = $form_state['order']; + $payment_method = commerce_paypal_checkout_get_payment_method_instance($order); + + // If no PayPal checkout payment method is configured, or if the buttons + // are explicitly not shown on the cart page, stop here. + if (!$payment_method || empty($payment_method['settings']['enable_on_cart'])) { + return; + } + + $form['smart_payment_buttons'] = array( + '#theme' => 'commerce_paypal_checkout_smart_payment_buttons', + '#payment_method' => $payment_method, + '#commit' => FALSE, + '#flow' => 'shortcut', + '#order' => $order, + '#weight' => 100, + ); + } + } +} + +/** + * Returns an instantiated PayPal Checkout API client for the given settings. + * + * @param array $config + * An associative array containing at least the following keys: + * - client_id: The client ID. + * - secret: The client secret. + * - server: The API server ("sandbox or "live"). + * + * @return PayPalCheckoutClient|NULL. + * An instantiated PayPalCheckout client, NULL if no client_id/secret were + * specified. + */ +function commerce_paypal_checkout_api_client($config) { + if (!isset($config['client_id']) || !isset($config['secret'])) { + return NULL; + } + $instances = &drupal_static(__FUNCTION__, array()); + + if (isset($instances[$config['client_id']])) { + return $instances[$config['client_id']]; + } + + $instances[$config['client_id']] = new PayPalCheckoutClient($config); + return $instances[$config['client_id']]; +} + +/** + * Payment method callback: redirect form. + */ +function commerce_paypal_checkout_redirect_form($form, &$form_state, $order, $payment_method) { + $form['smart_payment_buttons'] = array( + '#theme' => 'commerce_paypal_checkout_smart_payment_buttons', + '#payment_method' => $payment_method, + '#commit' => TRUE, + '#order' => $order, + '#flow' => 'mark', + ); + return $form; +} + +/** + * Payment method callback: redirect form return validation. + */ +function commerce_paypal_checkout_redirect_form_validate($order, $payment_method) { + $payment_method['settings'] += commerce_paypal_checkout_default_settings(); + + // Check if the PayPal order ID is known, as well as the "flow". + if (empty($order->data['commerce_paypal_checkout']['remote_id'] || + !isset($order->data['commerce_paypal_checkout']['flow']))) { + return FALSE; + } + $flow = $order->data['commerce_paypal_checkout']['flow']; + $api_client = commerce_paypal_checkout_api_client($payment_method['settings']); + if (!$api_client) { + return FALSE; + } + $remote_id = $order->data['commerce_paypal_checkout']['remote_id']; + try { + $paypal_order = $api_client->getOrder($remote_id); + } + catch (\Exception $exception) { + watchdog_exception('commerce_paypal_checkout', $exception); + return FALSE; + } + $order_total = field_get_items('commerce_order', $order, 'commerce_order_total', LANGUAGE_NONE); + $paypal_amount = $paypal_order['purchase_units'][0]['amount']; + $paypal_total = commerce_currency_decimal_to_amount($paypal_amount['value'], $paypal_amount['currency_code']); + // Check the remote status, and that the PayPal total matches the order total. + if (!in_array($paypal_order['status'], ['APPROVED', 'SAVED']) || + $paypal_total != $order_total[0]['amount'] || + $paypal_amount['currency_code'] != $order_total[0]['currency_code']) { + return FALSE; + } + // Store the intent for later reuse, it can't be updated, so no risk in + // being out of sync. + $order->data['commerce_paypal_checkout']['intent'] = strtolower($paypal_order['intent']); + + $payer = $paypal_order['payer']; + // If the user is anonymous, add their PayPal e-mail to the order. + if (empty($order->mail)) { + $order->mail = $payer['email_address']; + } + // Create a billing information profile for the order with the available info. + if (!empty($payment_method['settings']['update_billing_profiles'])) { + commerce_paypal_checkout_customer_profile($order, 'billing', $paypal_order); + } + // If the shipping module exists on the site, create a shipping information + // profile for the order with the available info. + if (module_exists('commerce_shipping') && !empty($payment_method['settings']['update_shipping_profiles'])) { + commerce_paypal_checkout_customer_profile($order, 'shipping', $paypal_order); + } + + // Recalculate the price of products on the order in case taxes have + // changed or prices have otherwise been affected. + if ($flow == 'shortcut') { + commerce_cart_order_refresh($order); + } + // Save the changes to the order. + commerce_order_save($order); + + if ($flow == 'mark') { + return commerce_paypal_checkout_do_payment($order, $payment_method); + } +} + +/** + * Capture/authorize a PayPal order and create a payment transaction. + * + * @param $order + * The order the payment is for. + * @param $payment_method + * The PayPal Checkout payment method instance whose settings should + * be used to submit the request. + * + * @return + * Boolean indicating the success or failure of the payment request. + */ +function commerce_paypal_checkout_do_payment($order, $payment_method) { + if (empty($order->data['commerce_paypal_checkout']['remote_id'])) { + return FALSE; + } + $paypal_checkout_data = $order->data['commerce_paypal_checkout']; + $intent = isset($paypal_checkout_data['intent']) ? $paypal_checkout_data['intent'] : $payment_method['settings']['intent']; + $api_client = commerce_paypal_checkout_api_client($payment_method['settings']); + try { + if ($intent == 'capture') { + $response = $api_client->captureOrder($paypal_checkout_data['remote_id']); + $remote_payment = $response['purchase_units'][0]['payments']['captures'][0]; + } + else { + $response = $api_client->authorizeOrder($paypal_checkout_data['remote_id']); + $remote_payment = $response['purchase_units'][0]['payments']['authorizations'][0]; + } + } + catch (\Exception $exception) { + watchdog_exception('commerce_paypal_checkout', $exception); + // Display an error message and remain on the same page. + drupal_set_message(t('We could not complete your payment with PayPal. Please try again or contact us if the problem persists.'), 'error'); + watchdog('commerce_paypal_checkout', 'PayPal Checkout transaction failed for order @order_number.', array('@order_number' => $order->order_number), WATCHDOG_ERROR); + return FALSE; + } + $remote_status = strtolower($remote_payment['status']); + + // Prepare a transaction object to log the API response. + $transaction = commerce_payment_transaction_new('paypal_checkout', $order->order_id); + $transaction->instance_id = $payment_method['instance_id']; + $transaction->amount = commerce_currency_decimal_to_amount($remote_payment['amount']['value'], $remote_payment['amount']['currency_code']); + $transaction->currency_code = $remote_payment['amount']['currency_code']; + $transaction->payload[REQUEST_TIME] = $response; + $transaction->remote_id = $remote_payment['id']; + $transaction->remote_status = $remote_payment['status']; + + // Store the transaction ID as the parent transaction ID in case subsequent + // API operations alter this transaction's remote ID. + if (!empty($transaction->remote_id)) { + $transaction->data['commerce_paypal_checkout']['original_remote_id'] = $transaction->remote_id; + } + + if (in_array($remote_status, ['denied', 'expired', 'declined'])) { + $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE; + commerce_payment_transaction_save($transaction); + // Display an error message and remain on the same page. + drupal_set_message(t('We could not complete your payment with PayPal. Please try again or contact us if the problem persists.'), 'error'); + watchdog('commerce_paypal_checkout', 'PayPal Checkout transaction failed for order @order_number.', array('@order_number' => $order->order_number), WATCHDOG_ERROR); + return FALSE; + } + + // Map the remote status to a Drupal commerce payment status. + $status_mapping = array( + 'created' => COMMERCE_PAYMENT_STATUS_PENDING, + 'pending' => COMMERCE_PAYMENT_STATUS_PENDING, + 'completed' => COMMERCE_PAYMENT_STATUS_SUCCESS, + ); + + // If we do not know how to handle this remote payment status, set the payment + // status to failure and stop here. + if (!isset($status_mapping[$remote_status])) { + $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE; + commerce_payment_transaction_save($transaction); + // Display an error message and remain on the same page. + drupal_set_message(t('We could not complete your payment with PayPal. Please try again or contact us if the problem persists.'), 'error'); + watchdog('commerce_paypal_checkout', 'PayPal Checkout transaction failed for order @order_number.', array('@order_number' => $order->order_number), WATCHDOG_ERROR); + return FALSE; + } + $transaction->status = $status_mapping[$remote_status]; + commerce_payment_transaction_save($transaction); + return TRUE; +} + +/** + * Creates or updates a customer profile for an order based on information + * obtained from PayPal after a payment via PayPal Checkout. + * + * @param $order + * The order that was paid via PayPal Checkout. + * @param $profile_type + * The type of the customer profile that should be created or updated. + * @param array $paypal_order + * The PayPal order retrieved via the getOrder() API call. + * @param $skip_save + * Boolean indicating whether or not this function should skip saving the + * order after setting it to reference the newly created customer profile; + * defaults to TRUE, requiring the caller to save the order. + */ +function commerce_paypal_checkout_customer_profile($order, $profile_type, $paypal_order, $skip_save = TRUE) { + // First check if the order already references a customer profile of the + // specified type. + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $field_name = variable_get('commerce_customer_profile_' . $profile_type . '_field', ''); + + // If the associated order field has been set and the order currently + // references a customer profile through it... + if (!empty($field_name) && !empty($order_wrapper->{$field_name})) { + // Update the existing customer profile. + $profile = $order_wrapper->{$field_name}->value(); + } + elseif (!empty($order->data['profiles']['customer_profile_' . $profile_type])) { + // Otherwise look for an association stored in the order's data array. + $profile = commerce_customer_profile_load($order->data['profiles']['customer_profile_' . $profile_type]); + } + + // Create a new profile if we could not find an existing one. + if (empty($profile)) { + $profile = commerce_customer_profile_new($profile_type, $order->uid); + } + + // Add the order context to the profile to ensure it can be updated without + // resulting in customer profile duplication. + $profile->entity_context = array( + 'entity_type' => 'commerce_order', + 'entity_id' => $order->order_id, + ); + + // Prepare an addressfield array to set to the customer profile. + $field = field_info_field('commerce_customer_address'); + $instance = field_info_instance('commerce_customer_profile', 'commerce_customer_address', $profile_type); + $address = addressfield_default_values($field, $instance); + + $paypal_address = array(); + // Use the first name and last name if the profile is a billing profile. + if ($profile_type == 'billing') { + $address['first_name'] = $paypal_order['payer']['name']['given_name']; + $address['last_name'] = $paypal_order['payer']['name']['surname']; + $address['name_line'] = $address['first_name'] . ' ' . $address['last_name']; + if (isset($paypal_order['payer']['address'])) { + $paypal_address = $paypal_order['payer']['address']; + } + } + // Otherwise if it's a shipping profile, populate the address with all of + // the shipping information returned from PayPal. + elseif ($profile_type == 'shipping') { + // If no shipping info was returned by PayPal. + if (empty($paypal_order['purchase_units'][0]['shipping'])) { + return; + } + $shipping_info = $paypal_order['purchase_units'][0]['shipping']; + $names = explode(' ', $shipping_info['name']['full_name']); + $first_name = array_shift($names); + $last_name = implode(' ', $names); + $address['first_name'] = $first_name; + $address['last_name'] = $last_name; + $address['name_line'] = $first_name . ' ' . $last_name; + if (isset($shipping_info['address'])) { + $paypal_address = $shipping_info['address']; + } + } + + if ($paypal_address) { + // Map PayPal address keys to Address field keys. + $mapping = array( + 'address_line_1' => 'thoroughfare', + 'address_line_2' => 'premise', + 'admin_area_1' => 'administrative_area', + 'admin_area_2' => 'locality', + 'postal_code' => 'postal_code', + 'country_code' => 'country', + ); + foreach ($paypal_address as $key => $value) { + if (!isset($mapping[$key])) { + continue; + } + // PayPal address fields have a higher maximum length than ours. + $value = $key == 'country_code' ? $value : mb_substr($value, 0, 255); + $address[$mapping[$key]] = $value; + } + } + + // Add the addressfield value to the customer profile. + $profile_wrapper = entity_metadata_wrapper('commerce_customer_profile', $profile); + $profile_wrapper->commerce_customer_address = $address; + + // Save the customer profile and update the order to reference it. + $profile_wrapper->save(); + $order_wrapper->{'commerce_customer_' . $profile_type} = $profile_wrapper; + + // Save the order if specified. + if (!$skip_save) { + $order_wrapper->save(); + } +} + +/** + * Update the PayPal order. + * + * @param $order + * The order. + * @param $payment_method + * The payment method instance. + * + * @return bool + * TRUE if the update was successful, FALSE otherwise. + * + * @see commerce_paypal_checkout_review_pane_checkout_form_submit() + */ +function commerce_paypal_checkout_update_order($order, $payment_method) { + if (!isset($order->data['commerce_paypal_checkout']['remote_id'])) { + return FALSE; + } + $remote_id = $order->data['commerce_paypal_checkout']['remote_id']; + $request_body = commerce_paypal_checkout_prepare_order_request($order, $payment_method['settings']); + $update_params = array( + array( + 'op' => 'replace', + 'path' => "/purchase_units/@reference_id=='default'", + 'value' => $request_body['purchase_units'][0], + ), + ); + drupal_alter('commerce_paypal_checkout_update_order_request', $update_params, $order); + $api_client = commerce_paypal_checkout_api_client($payment_method['settings']); + try { + $api_client->updateOrder($remote_id, $update_params); + // Assume the update worked if we ended up here, if the update failed, + // an exception was thrown. + return TRUE; + } + catch (\Exception $exception) { + watchdog_exception('commerce_paypal_checkout', $exception); + return FALSE; + } +} + +/** + * Formats a price amount into a decimal value as expected by PayPal. + * + * @param $amount + * An integer price amount. + * @param $currency_code + * The currency code of the price. + * + * @return + * The decimal price amount as expected by PayPal API servers. + */ +function commerce_paypal_checkout_price_amount($amount, $currency_code) { + $rounded_amount = commerce_currency_round($amount, commerce_currency_load($currency_code)); + return number_format(commerce_currency_amount_to_decimal($rounded_amount, $currency_code), 2, '.', ''); +} diff --git a/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/css/commerce_paypal_checkout.css b/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/css/commerce_paypal_checkout.css new file mode 100644 index 000000000..deeda620b --- /dev/null +++ b/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/css/commerce_paypal_checkout.css @@ -0,0 +1,9 @@ +@media all and (min-width: 61em) { + .page-cart .paypal-buttons-container { + float: right; + width: 50%; + } +} +.page-checkout-payment .checkout-help { + display: none; +} diff --git a/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/includes/commerce_paypal_checkout.admin.inc b/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/includes/commerce_paypal_checkout.admin.inc new file mode 100644 index 000000000..668a337ed --- /dev/null +++ b/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/includes/commerce_paypal_checkout.admin.inc @@ -0,0 +1,297 @@ +instance_id); + $form_state['payment_method'] = $payment_method; + $balance = commerce_payment_order_balance($order); + + + if ($balance['amount'] > 0 && $balance['amount'] < $transaction->amount) { + $default_amount = $balance['amount']; + } + else { + $default_amount = $transaction->amount; + } + + // Convert the price amount to a user friendly decimal value. + $default_amount = number_format(commerce_currency_amount_to_decimal($default_amount, $transaction->currency_code), 2, '.', ''); + + $description = implode('
', array( + t('Authorization: @amount', array('@amount' => commerce_currency_format($transaction->amount, $transaction->currency_code))), + t('Order balance: @balance', array('@balance' => commerce_currency_format($balance['amount'], $balance['currency_code']))), + )); + + $form['amount'] = array( + '#type' => 'textfield', + '#title' => t('Capture amount'), + '#description' => $description, + '#default_value' => $default_amount, + '#field_suffix' => check_plain($transaction->currency_code), + '#size' => 16, + ); + + $form = confirm_form($form, + t('What amount do you want to capture?'), + 'admin/commerce/orders/' . $order->order_id . '/payment', + '', + t('Capture'), + t('Cancel'), + 'confirm' + ); + + return $form; +} + +/** + * Validate handler: ensure a valid amount is given. + */ +function commerce_paypal_checkout_capture_form_validate($form, &$form_state) { + $transaction = $form_state['transaction']; + $amount = $form_state['values']['amount']; + + // Ensure a positive numeric amount has been entered for capture. + if (!is_numeric($amount) || $amount <= 0) { + form_set_error('amount', t('You must specify a positive numeric amount to capture.')); + } + + // Ensure the amount is within the allowed limit for PayPal authorizations. + $authorization_amount = commerce_currency_amount_to_decimal($transaction->amount, $transaction->currency_code); + $authorization_amount_upper = commerce_currency_amount_to_decimal($transaction->amount, $transaction->currency_code) + 75; + + if ($amount > $authorization_amount * 1.15 || $amount > $authorization_amount_upper) { + form_set_error('amount', t('You cannot capture an amount $75 or 115% greater than the authorization amount in PayPal Checkout.')); + } + + // If the authorization has expired, display an error message and redirect. + if (REQUEST_TIME - $transaction->created > 86400 * 29) { + drupal_set_message(t('This authorization has passed its 29 day limit and cannot be captured.'), 'error'); + drupal_goto('admin/commerce/orders/' . $form_state['order']->order_id . '/payment'); + } +} + +/** + * Submit handler: process a prior authorization capture via PayPal Checkout. + */ +function commerce_paypal_checkout_capture_form_submit($form, &$form_state) { + $transaction = $form_state['transaction']; + $order = $form_state['order']; + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $order_total = $order_wrapper->commerce_order_total->value(); + $amount = commerce_currency_decimal_to_amount($form_state['values']['amount'], $transaction->currency_code); + $api_client = commerce_paypal_checkout_api_client($form_state['payment_method']['settings']); + + $params = array( + 'final_capture' => $order_total['amount'] == $amount, + 'amount' => array( + 'value' => commerce_paypal_checkout_price_amount($amount, $transaction->currency_code), + 'currency_code' => $transaction->currency_code, + ), + ); + + try { + // If the transaction was authorized more than 3 days ago, we need to + // re-authorize the payment. + if (time() >= ($transaction->created + (86400 * 3))) { + $api_client->reAuthorizePayment($transaction->remote_id, array('amount' => $params['amount'])); + } + + $response = $api_client->capturePayment($transaction->remote_id, $params); + if (strtolower($response['status']) == 'completed') { + $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS; + } + else { + $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE; + } + $transaction->remote_id = $response['id']; + $transaction->amount = $amount; + $transaction->remote_status = $response['status']; + // Note the capture in the transaction message. + $transaction->message .= '
' . t('Captured: @date', array('@date' => format_date(REQUEST_TIME, 'short'))); + $transaction->payload[REQUEST_TIME . '-capture'] = $response; + // Save the updated original transaction. + commerce_payment_transaction_save($transaction); + } + catch (PayPalCheckoutHttpException $exception) { + watchdog_exception('commerce_paypal_checkout', $exception); + // Display an error message but leave the transaction pending. + drupal_set_message(t('Prior authorization capture failed, so the transaction will remain in a pending status.'), 'error'); + } + + // Redirect back to the current order payment page. + $form_state['redirect'] = 'admin/commerce/orders/' . $form_state['order']->order_id . '/payment'; +} + +/** + * Form callback: allows the user to void a transaction. + */ +function commerce_paypal_checkout_void_form($form, &$form_state, $order, $transaction) { + $form_state['order'] = $order; + $form_state['transaction'] = $transaction; + + // Load and store the payment method instance for this transaction. + $payment_method = commerce_payment_method_instance_load($transaction->instance_id); + $form_state['payment_method'] = $payment_method; + + $form['markup'] = array( + '#markup' => t('Are you sure that you want to void this transaction?'), + ); + + $form = confirm_form($form, + t('Are you sure that you want to void this transaction?'), + 'admin/commerce/orders/' . $order->order_id . '/payment', + '', + t('Void'), + t('Cancel'), + 'confirm' + ); + + return $form; +} + +/** + * Submit handler: process the void request. + */ +function commerce_paypal_checkout_void_form_submit($form, &$form_state) { + $transaction = $form_state['transaction']; + $api_client = commerce_paypal_checkout_api_client($form_state['payment_method']['settings']); + + try { + // This API call doesn't return content, so nothing to check, if no + // exception is thrown, this means the void operation succeeded. + $api_client->voidPayment($transaction->remote_id); + drupal_set_message(t('Transaction successfully voided.')); + + // Set the remote and local status accordingly. + $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE; + $transaction->remote_status = COMMERCE_CREDIT_VOID; + + // Update the transaction message to show that it has been voided. + $transaction->message .= '
' . t('Voided: @date', array('@date' => format_date(REQUEST_TIME, 'short'))); + commerce_payment_transaction_save($transaction); + } + catch (PayPalCheckoutHttpException $exception) { + drupal_set_message(t('The void operation failed, so the transaction will remain in a pending status.'), 'error'); + } + $form_state['redirect'] = 'admin/commerce/orders/' . $form_state['order']->order_id . '/payment'; +} + +/** + * Form callback: allows the user to issue a credit on a prior transaction. + */ +function commerce_paypal_checkout_refund_form($form, &$form_state, $order, $transaction) { + $form_state['order'] = $order; + $form_state['transaction'] = $transaction; + + // Load and store the payment method instance for this transaction. + $payment_method = commerce_payment_method_instance_load($transaction->instance_id); + $form_state['payment_method'] = $payment_method; + + $default_amount = number_format(commerce_currency_amount_to_decimal($transaction->amount, $transaction->currency_code), 2, '.', ''); + + $form['amount'] = array( + '#type' => 'textfield', + '#title' => t('Refund amount'), + '#description' => t('Enter the amount to be refunded back to the PayPal account.'), + '#default_value' => $default_amount, + '#field_suffix' => check_plain($transaction->currency_code), + '#size' => 16, + ); + + $form = confirm_form($form, + t('What amount do you want to refund?'), + 'admin/commerce/orders/' . $order->order_id . '/payment', + '', + t('Refund'), + t('Cancel'), + 'confirm' + ); + + return $form; +} + +/** + * Validate handler: check the credit amount before attempting a refund request. + */ +function commerce_paypal_checkout_refund_form_validate($form, &$form_state) { + $transaction = $form_state['transaction']; + $amount = $form_state['values']['amount']; + + // Ensure a positive numeric amount has been entered for refund. + if (!is_numeric($amount) || $amount <= 0) { + form_set_error('amount', t('You must specify a positive numeric amount to refund.')); + } + + // Ensure the amount is less than or equal to the captured amount. + if ($amount > commerce_currency_amount_to_decimal($transaction->amount, $transaction->currency_code)) { + form_set_error('amount', t('You cannot refund more than you captured.')); + } + + // If the transaction is older than 60 days, display an error message and redirect. + if (time() - $transaction->created > 86400 * 60) { + drupal_set_message(t('This transaction has passed its 60 day limit for issuing refunds.'), 'error'); + drupal_goto('admin/commerce/orders/' . $form_state['order']->order_id . '/payment'); + } +} + +/** + * Submit handler: process a refund request. + */ +function commerce_paypal_checkout_refund_form_submit($form, &$form_state) { + $transaction = $form_state['transaction']; + $payment_method = $form_state['payment_method']; + + $order = $form_state['order']; + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $order_total = $order_wrapper->commerce_order_total->value(); + $api_client = commerce_paypal_checkout_api_client($form_state['payment_method']['settings']); + $amount = commerce_currency_decimal_to_amount($form_state['values']['amount'], $order_total['currency_code']); + + $params = array( + 'amount' => array( + 'value' => commerce_paypal_checkout_price_amount($amount, $transaction->currency_code), + 'currency_code' => $transaction->currency_code, + ), + ); + try { + $response = $api_client->refundPayment($transaction->remote_id, $params); + // Not sure this can happen... + if (strtolower($response['status']) != 'completed') { + drupal_set_message(t('Refund failed')); + } + else { + drupal_set_message(t('Refund for @amount issued successfully.', array('@amount' => commerce_currency_format($amount, $transaction->currency_code)))); + + // Create a new transaction to record the credit. + $credit_transaction = commerce_payment_transaction_new($payment_method['method_id'], $order->order_id); + $credit_transaction->instance_id = $payment_method['instance_id']; + $credit_transaction->remote_id = $response['id']; + $credit_transaction->amount = $amount * -1; + $credit_transaction->currency_code = $transaction->currency_code; + $credit_transaction->payload[REQUEST_TIME] = $response; + $credit_transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS; + $credit_transaction->remote_status = COMMERCE_CREDIT_CREDIT; + $credit_transaction->message = t('Refunded to @remote_id.', array('@remote_id' => $transaction->remote_id)); + + // Save the credit transaction. + commerce_payment_transaction_save($credit_transaction); + } + } + catch (PayPalCheckoutHttpException $exception) { + watchdog_exception('commerce_paypal_checkout', $exception); + drupal_set_message(t('Refund failed')); + } + + $form_state['redirect'] = 'admin/commerce/orders/' . $order->order_id . '/payment'; +} diff --git a/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/includes/commerce_paypal_checkout.checkout_pane.inc b/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/includes/commerce_paypal_checkout.checkout_pane.inc new file mode 100644 index 000000000..fa8d1e530 --- /dev/null +++ b/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/includes/commerce_paypal_checkout.checkout_pane.inc @@ -0,0 +1,206 @@ + $checkout_pane) { + if (!in_array($pane_id, $excluded_panes) && $checkout_pane['module'] != 'commerce_customer' ) { + $options[$pane_id] = check_plain($checkout_pane['name']); + } + } + + // If we have available checkout panes for this page... + if (!empty($options)) { + // Allow the administrator to choose which panes the customer should see + // upon returning from PayPal Checkout. + $form['commerce_paypal_checkout_review_embedded_panes'] = array( + '#type' => 'checkboxes', + '#title' => t('Checkout panes to include on the PayPal Checkout review and confirm page'), + '#options' => $options, + '#default_value' => variable_get('commerce_paypal_checkout_review_embedded_panes', array()), + ); + } + + return $form; +} + +/** + * Checkout pane callback: returns a pane allowing the customer to review the + * final details of the order and provide any final information required. + */ +function commerce_paypal_checkout_review_pane_checkout_form(&$form, &$form_state, $checkout_pane, $order) { + $form_state['build_info']['files']['pane'] = drupal_get_path('module', 'commerce_paypal_checkout') . '/includes/commerce_paypal_checkout.checkout_pane.inc'; + $form_state['order'] = $order; + + // Adjust the weights of the help text and pane form to appear before the + // embedded checkout panes. + $form['help']['#weight'] = -10; + + $pane_form = array( + '#weight' => -5, + ); + + // Duplicate the review checkout pane's contents. + $pane_form['review'] = array( + '#theme' => 'commerce_checkout_review', + '#data' => array(), + ); + + // Loop through all the pages before the review page... + foreach (commerce_checkout_pages() as $page_id => $checkout_page) { + // Exit the loop once the review page is reached. + if ($page_id == 'review') { + break; + } + + // Loop through all the panes on the current page specifying review... + foreach (commerce_checkout_panes(array('page' => $page_id, 'enabled' => TRUE, 'review' => TRUE)) as $pane_id => $checkout_pane_local) { + // If the pane has a valid review callback... + if ($callback = commerce_checkout_pane_callback($checkout_pane_local, 'review')) { + // Get the review data for this pane. + $pane_data = $callback($form, $form_state, $checkout_pane_local, $order); + + // Only display the pane if there is data in the pane. + if (!empty($pane_data)) { + // Add a row for it in the review data. + $pane_form['review']['#data'][$pane_id] = array( + 'title' => $checkout_pane_local['title'], + 'data' => $pane_data, + ); + } + } + } + } + + // Embed other specified checkout panes in this checkout pane. + foreach (commerce_paypal_checkout_embedded_checkout_panes() as $embedded_pane_id) { + $embedded_pane = commerce_checkout_pane_load($embedded_pane_id); + + // If the checkout pane defines a checkout form callback... + if ($embedded_pane && $callback = commerce_checkout_pane_callback($embedded_pane, 'checkout_form')) { + // Get the form for the embedded checkout pane. + $embedded_pane_form = $callback($form, $form_state, $embedded_pane, $order); + + // Embed it on this checkout pane if the form returned data. + if (!empty($embedded_pane_form)) { + $form[$embedded_pane_id] = array( + '#type' => $embedded_pane['fieldset'] ? 'fieldset' : 'container', + '#title' => check_plain($embedded_pane['title']), + '#collapsible' => $embedded_pane['collapsible'], + '#collapsed' => $embedded_pane['collapsed'], + '#attributes' => array('class' => array($embedded_pane_id)), + '#tree' => TRUE, + ) + $embedded_pane_form; + } + } + } + + $form['pay_now'] = array( + '#type' => 'submit', + '#value' => t('Pay Now'), + '#validate' => array('commerce_paypal_checkout_review_pane_checkout_form_validate'), + '#submit' => array('commerce_paypal_checkout_review_pane_checkout_form_submit'), + ); + + return $pane_form; +} + +/** + * Validate handler for the PayPal Checkout review and confirm page. + */ +function commerce_paypal_checkout_review_pane_checkout_form_validate($form, &$form_state) { + $order = $form_state['order']; + $form_validate = TRUE; + + // Loop through the enabled checkout panes included in this page to validate + // and submit them. + foreach (commerce_paypal_checkout_embedded_checkout_panes() as $embedded_pane_id) { + $embedded_pane_validate = TRUE; + + // Load the checkout pane to find its checkout pane validate callback. + $embedded_checkout_pane = commerce_checkout_pane_load($embedded_pane_id); + + // If it has a validate callback. + if ($callback = commerce_checkout_pane_callback($embedded_checkout_pane, 'checkout_form_validate')) { + // Give it a chance to process the submitted data. + $embedded_pane_validate &= $callback($form, $form_state, $embedded_checkout_pane, $order); + } + + // Submit the pane if it validated. + if ($embedded_pane_validate && $callback = commerce_checkout_pane_callback($embedded_checkout_pane, 'checkout_form_submit')) { + $callback($form, $form_state, $embedded_checkout_pane, $order); + } + + // A failed pane makes the form fail. + $form_validate &= $embedded_pane_validate; + } + + // Save the updated order object. + commerce_order_save($order); + + // If a pane failed validation or the form state has otherwise been altered to + // initiate a rebuild, return without moving to the next checkout page. + if (!$form_validate || $form_state['rebuild']) { + $form_state['rebuild'] = TRUE; + } +} + +/** + * Submit handler for the PayPal Checkout review and confirm page. + */ +function commerce_paypal_checkout_review_pane_checkout_form_submit($form, &$form_state) { + $order = $form_state['order']; + $payment_method = commerce_payment_method_instance_load($order->data['payment_method']); + // Update the order in PayPal to make sure it reflects the current state of + // the order in Drupal. + // If the order could not be updated in PayPal, stop here. + if (!commerce_paypal_checkout_update_order($order, $payment_method)) { + drupal_set_message(t('We could not complete your payment with PayPal. Please try again or contact us if the problem persists.'), 'error'); + watchdog('commerce_paypal_checkout', 'Could not update the PayPal order for order @order_number.', array('@order_number' => $order->order_number), WATCHDOG_ERROR); + return; + } + + // Attempt to process the payment. + if (commerce_paypal_checkout_do_payment($order, $payment_method)) { + // Proceed to the next page if it succeeded. + $order_status = commerce_order_status_load($order->status); + $checkout_page = commerce_checkout_page_load($order_status['checkout_page']); + $next_page = $checkout_page['next_page']; + + // Update the order status to the next checkout page. + $order = commerce_order_status_update($order, 'checkout_' . $next_page, FALSE, FALSE); + + // Inform modules of checkout completion if the next page is completed. + if ($next_page == 'complete') { + commerce_checkout_complete($order); + } + + // Redirect to the URL for the new checkout page. + $form_state['redirect'] = commerce_checkout_order_uri($order); + } +} diff --git a/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/js/commerce_paypal_checkout.js b/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/js/commerce_paypal_checkout.js new file mode 100644 index 000000000..308429faf --- /dev/null +++ b/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/js/commerce_paypal_checkout.js @@ -0,0 +1,66 @@ +/** + * @file + * Renders the PayPal Smart payment buttons. + */ + +(function($) { + + Drupal.paypalCheckout = { + renderButtons: function(settings) { + $('.paypal-buttons-container').once('rendered').each(function() { + paypal.Buttons({ + createOrder: function() { + return fetch(settings.createOrderUri) + .then(function(res) { + return res.json(); + }).then(function(data) { + return data.id ? data.id : ''; + }); + }, + onApprove: function (data) { + return fetch(settings.onApproveUri, { + method: 'post', + body: JSON.stringify({ + id: data.orderID + }) + }).then(function(res) { + return res.json(); + }).then(function(data) { + if (data.hasOwnProperty('redirectUri')) { + window.location.href = data.redirectUri; + } + }); + }, + style: settings['style'] + }).render('#' + $(this).attr('id')); + }); + }, + initialize: function (context, settings) { + if (context === document) { + var script = document.createElement('script'); + script.src = settings.src; + script.type = 'text/javascript'; + script.setAttribute('data-partner-attribution-id', 'CommerceGuys_Cart_SPB'); + document.getElementsByTagName('head')[0].appendChild(script); + } + var waitForSdk = function(settings) { + if (typeof paypal !== 'undefined') { + Drupal.paypalCheckout.renderButtons(settings); + } + else { + setTimeout(function() { + waitForSdk(settings) + }, 100); + } + }; + waitForSdk(settings); + } + }; + + Drupal.behaviors.commercePaypalCheckout = { + attach: function(context, settings) { + Drupal.paypalCheckout.initialize(context, settings.paypalCheckout); + } + }; + +}(jQuery)); diff --git a/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/lib/PayPalCheckoutClient.php b/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/lib/PayPalCheckoutClient.php new file mode 100644 index 000000000..335960d7e --- /dev/null +++ b/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/lib/PayPalCheckoutClient.php @@ -0,0 +1,393 @@ +config = $config; + $this->logger = $logger; + $this->retryCount = 0; + $this->headers = array( + 'Accept' => 'application/json', + // Defaults the "Content-Type" header to application/json. + 'Content-Type' => 'application/json', + 'PayPal-Partner-Attribution-Id' => 'CommerceGuys_Cart_SPB', + ); + } + + /** + * Sets the default cURL options. + */ + public static function setDefaultCurlOptions($ch) { + curl_setopt($ch, CURLOPT_HEADER, FALSE); + curl_setopt($ch, CURLINFO_HEADER_OUT, TRUE); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, TRUE); + curl_setopt($ch, CURLOPT_VERBOSE, FALSE); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); + curl_setopt($ch, CURLOPT_TIMEOUT, 180); + } + + /** + * Send a message to the logger. + * + * @param string $message + * The message to log. + */ + public function logMessage($message) { + if (is_callable($this->logger)) { + call_user_func($this->logger, $message); + } + } + + /** + * Returns the base URL for the API, based on the specified mode. + * + * @return string + * The base URL for the API that query parameters will be appended to when + * submitting API requests. + */ + public function baseUrl() { + switch ($this->config['server']) { + case 'live': + return 'https://api.paypal.com'; + + case 'sandbox': + default: + return 'https://api.sandbox.paypal.com'; + } + } + + /** + * Acquire an access token from PayPal. + * + * @return string[] + * The API response JSON converted to an associative array. + * + * @throws PayPalCheckoutAuthenticationException + */ + protected function acquireAccessToken() { + $ch = curl_init(); + static::setDefaultCurlOptions($ch); + $headers = array( + 'Authorization' => 'Basic ' . base64_encode($this->config['client_id'] . ':' . $this->config['secret']), + 'Content-Type' => 'application/x-www-form-urlencoded', + ) + $this->headers; + $url = $this->baseUrl() . '/v1/oauth2/token'; + curl_setopt($ch, CURLOPT_HTTPHEADER, static::formatHeaders($headers)); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=client_credentials'); + + // Submit the request to the PayPal server. + $response = curl_exec($ch); + + // Ensure we got a successful response by inspecting the HTTP status code. + $response_info = curl_getinfo($ch); + + if (is_resource($ch)) { + curl_close($ch); + } + + $json = drupal_json_decode($response); + + if ($response_info['http_code'] == 401) { + // Throw an exception indicating authentication failed. + $message = 'Authentication failed.'; + if (isset($json['error_description'])) { + $message = sprintf('Error description: %s.', $json['error_description']); + } + throw new PayPalCheckoutAuthenticationException($message); + } + + return $json; + } + + /** + * Returns the access token stored locally if any, or get one from PayPal. + * + * @return string + * The Oauth2 access token. + * + * @throws PayPalCheckoutAuthenticationException + */ + public function getAccessToken() { + $access_token = variable_get('commerce_paypal_checkout_access_token', array()); + if (!empty($access_token['token']) && $access_token['expires'] > time()) { + return $access_token['token']; + } + $response = $this->acquireAccessToken(); + variable_set('commerce_paypal_checkout_access_token', array( + 'token' => $response['access_token'], + 'expires' => time() + $response['expires_in'], + )); + return $response['access_token']; + } + + /** + * Creates an order in PayPal. + * + * @param array $parameters + * An array of parameters to include with the request. + * + * @return string[] + * The API response JSON converted to an associative array. + */ + public function createOrder($parameters) { + return $this->submitRequest('POST', 'v2/checkout/orders', $parameters); + } + + /** + * Get an existing order from PayPal. + * + * @param $remote_id + * The PayPal order ID. + * + * @return string[] + * The API response JSON converted to an associative array. + */ + public function getOrder($remote_id) { + return $this->submitRequest('GET', sprintf('v2/checkout/orders/%s', $remote_id)); + } + + /** + * Updates an existing PayPal order. + * + * @param $remote_id + * The PayPal order ID. + * @param array $parameters + * An array of parameters to include with the request. + * + * @return string[] + * The API response JSON converted to an associative array. + */ + public function updateOrder($remote_id, $parameters) { + return $this->submitRequest('PATCH', sprintf('v2/checkout/orders/%s', $remote_id), $parameters); + } + + /** + * Authorize payment for order. + * + * @param $remote_id + * The PayPal order ID. + * + * @return string[] + * The API response JSON converted to an associative array. + */ + public function authorizeOrder($remote_id) { + return $this->submitRequest('POST', sprintf('v2/checkout/orders/%s/authorize', $remote_id)); + + } + + /** + * Capture payment for order. + * + * @param $remote_id + * The PayPal order ID. + * + * @return string[] + * The API response JSON converted to an associative array. + */ + public function captureOrder($remote_id) { + return $this->submitRequest('POST', sprintf('v2/checkout/orders/%s/capture', $remote_id)); + } + + /** + * Captures an authorized payment, by ID. + * + * @param $authorization_id + * The PayPal-generated ID for the authorized payment to capture. + * @param array $parameters + * (optional An array of parameters to pass as the request body. + * + * @return string[] + * The API response JSON converted to an associative array. + */ + public function capturePayment($authorization_id, array $parameters = array()) { + return $this->submitRequest('POST', sprintf('v2/payments/authorizations/%s/capture', $authorization_id), $parameters); + } + + /** + * Reauthorizes an authorized PayPal account payment, by ID. + * + * @param $authorization_id + * The PayPal-generated ID of the authorized payment to reauthorize. + * @param array $parameters + * (optional An array of parameters to pass as the request body. + * + * @return string[] + * The API response JSON converted to an associative array. + */ + public function reAuthorizePayment($authorization_id, array $parameters = array()) { + return $this->submitRequest('POST', sprintf('v2/payments/authorizations/%s/reauthorize', $authorization_id), $parameters); + } + + /** + * Refunds a captured payment, by ID. + * + * @param $capture_id + * The PayPal-generated ID for the captured payment to refund. + * @param array $parameters + * (optional An array of parameters to pass as the request body. + * + * @return string[] + * The API response JSON converted to an associative array. + */ + public function refundPayment($capture_id, array $parameters = array()) { + return $this->submitRequest('POST', sprintf('v2/payments/captures/%s/refund', $capture_id), $parameters); + } + + /** + * Voids, or cancels, an authorized payment, by ID. + * + * @param $authorization_id + * The PayPal-generated ID of the authorized payment to void. + */ + public function voidPayment($authorization_id) { + return $this->submitRequest('POST', sprintf('v2/payments/authorizations/%s/void', $authorization_id)); + } + + /** + * Submits an API request to the PayPal server. + * + * @param string $method + * The HTTP method to use. One of: 'GET', 'POST', 'PATCH', 'PUT', DELETE'. + * @param string $path + * The remote path. The base URL will be automatically appended. + * @param array $parameters + * An array of parameters to include with the request. Optional. + * + * @throws PayPalCheckoutAuthenticationException if the request fails authentication. + * @throws PayPalCheckoutHttpServerErrorException if the response status code is 5xx. + * @throws PayPalCheckoutHttpClientErrorException if the response status code is 4xx. + * @throws PayPalCheckoutHttpRedirectionException if the response status code is 3xx. + * @throws PayPalCheckoutInvalidResponseJsonException if the response is not valid JSON. + * + * @return string[] + * The API response JSON converted to an associative array. + */ + public function submitRequest($method, $path, $parameters = array()) { + $this->headers['Authorization'] = 'Bearer ' . $this->getAccessToken(); + $url = $this->baseUrl() . '/' . $path; + $ch = curl_init(); + static::setDefaultCurlOptions($ch); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + if (!empty($parameters)) { + if ($this->headers['Content-Type'] == 'application/json') { + // JSON encode the fields and set them to the request body. + curl_setopt($ch, CURLOPT_POSTFIELDS, drupal_json_encode($parameters)); + } + else { + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($parameters, '', '&')); + } + } + curl_setopt($ch, CURLOPT_HTTPHEADER, static::formatHeaders($this->headers)); + + // Submit the request to the PayPal server. + $this->logMessage(sprintf('Request URL: %s', $url)); + curl_setopt($ch, CURLOPT_URL, $url); + $response = curl_exec($ch); + + // Ensure we got a successful response by inspecting the HTTP status code. + $response_info = curl_getinfo($ch); + + if (is_resource($ch)) { + curl_close($ch); + } + + if ($response_info['http_code'] == 401) { + // Attempt to get a new access token if the authentication failed. + // This might happen if we're sending an expired access token to PayPal. + if ($this->retryCount < static::RETRY_LIMIT) { + $this->retryCount++; + // Ensure we get a fresh access token next time. + variable_del('commerce_paypal_checkout_access_token'); + return $this->submitRequest($method, $path, $parameters); + } + + $json = drupal_json_decode($response); + // Throw an exception indicating authentication failed. + $message = 'Authentication failed.'; + if (isset($json['error_description'])) { + $message = sprintf('Error description: %s.', $json['error_description']); + } + throw new PayPalCheckoutAuthenticationException($message); + } + elseif ($response_info['http_code'] >= 500) { + // Throw an exception indicating a server error. + throw new PayPalCheckoutHttpServerErrorException('', $response_info['http_code']); + } + elseif ($response_info['http_code'] >= 400) { + // Throw an exception indicating a client error. + throw new PayPalCheckoutHttpClientErrorException('', $response_info['http_code']); + } + elseif ($response_info['http_code'] >= 300) { + // Throw an exception indicating a redirection that this library is not + // going to automatically follow. + throw new PayPalCheckoutHttpRedirectionException('', $response_info['http_code']); + } + + // Attempt to convert the response body to an associative array. + try { + $json = drupal_json_decode($response); + } + catch (\Exception $e) { + throw new PayPalCheckoutInvalidResponseJsonException('The API response string could not be parsed as JSON.'); + } + + return $json; + } + + protected static function formatHeaders(array $headers) { + $http_headers = array(); + foreach ($headers as $key => $value) { + $http_headers[] = "$key: $value"; + } + return $http_headers; + } + +} diff --git a/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/lib/PayPalCheckoutExceptions.php b/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/lib/PayPalCheckoutExceptions.php new file mode 100644 index 000000000..5006d9d69 --- /dev/null +++ b/www7/sites/all/modules/contrib/commerce_paypal/modules/checkout/lib/PayPalCheckoutExceptions.php @@ -0,0 +1,40 @@ +data['commerce_paypal_ec']['parenttransactionid'] = $transaction->remote_id; } + // Check if there is an error code in the response. + if (!empty($response['L_ERRORCODE0'])) { + // Log the error in a payment transaction. + $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE; + $transaction->remote_status = ''; + $message = array(); + $message[] = '' . t('Payment failed') . ''; + $message[] = t('Error @code: @message', array('@code' => $response['L_ERRORCODE0'], '@message' => $response['L_SHORTMESSAGE0'])); + $transaction->message = implode('
', $message); + commerce_payment_transaction_save($transaction); + + // If the response error indicates a funding failure, redirect the customer + // back to PayPal for another attempt. + // @see: https://developer.paypal.com/docs/classic/express-checkout/ht_ec_fundingfailure10486/ + if ($response['L_ERRORCODE0'] === '10486') { + // Log the error in watchdog. + watchdog('commerce_paypal_ec', 'PayPal Express Checkout transaction funding failed for order @order_number. Redirecting the user back to PayPal.', array('@order_number' => $order->order_number), WATCHDOG_NOTICE); + // Update order. + commerce_order_status_update($order, 'checkout_payment', FALSE, NULL, t('Customer payment transaction funding failed. Redirecting customer back to the PayPal Express Checkout page.')); + // Redirect the user back to PayPal. + drupal_goto(commerce_paypal_ec_checkout_url($payment_method['settings']['server'], $order->data['commerce_paypal_ec']['token'])); + } + + // Log the error in watchdog. + watchdog('commerce_paypal_ec', 'PayPal Express Checkout transaction failed for order @order_number.', array('@order_number' => $order->order_number), WATCHDOG_ERROR); + + return FALSE; + } + // If we received an unknown response status... if (!isset($response['PAYMENTINFO_0_PAYMENTSTATUS']) || !in_array($response['PAYMENTINFO_0_PAYMENTSTATUS'], array('Failed', 'Voided', 'Pending', 'Completed', 'Refunded'))) { // Display an error message and remain on the same page. diff --git a/www7/sites/all/modules/contrib/commerce_paypal/modules/payflow/commerce_payflow.info b/www7/sites/all/modules/contrib/commerce_paypal/modules/payflow/commerce_payflow.info index 9b1ce3718..954e25c0b 100644 --- a/www7/sites/all/modules/contrib/commerce_paypal/modules/payflow/commerce_payflow.info +++ b/www7/sites/all/modules/contrib/commerce_paypal/modules/payflow/commerce_payflow.info @@ -11,8 +11,8 @@ core = 7.x ; Simple tests ; files[] = tests/commerce_paypal_wps.test -; Information added by Drupal.org packaging script on 2018-10-22 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2019-05-06 +version = "7.x-2.7" core = "7.x" project = "commerce_paypal" -datestamp = "1540244884" +datestamp = "1557130694" diff --git a/www7/sites/all/modules/contrib/commerce_paypal/modules/wpp/commerce_paypal_wpp.info b/www7/sites/all/modules/contrib/commerce_paypal/modules/wpp/commerce_paypal_wpp.info index 5651b4993..ee8da4976 100644 --- a/www7/sites/all/modules/contrib/commerce_paypal/modules/wpp/commerce_paypal_wpp.info +++ b/www7/sites/all/modules/contrib/commerce_paypal/modules/wpp/commerce_paypal_wpp.info @@ -11,8 +11,8 @@ core = 7.x ; Simple tests ; files[] = tests/commerce_paypal_wps.test -; Information added by Drupal.org packaging script on 2018-10-22 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2019-05-06 +version = "7.x-2.7" core = "7.x" project = "commerce_paypal" -datestamp = "1540244884" +datestamp = "1557130694" diff --git a/www7/sites/all/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.info b/www7/sites/all/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.info index e79cb283b..7711fd3e4 100644 --- a/www7/sites/all/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.info +++ b/www7/sites/all/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.info @@ -11,8 +11,8 @@ core = 7.x ; Simple tests ; files[] = tests/commerce_paypal_wps.test -; Information added by Drupal.org packaging script on 2018-10-22 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2019-05-06 +version = "7.x-2.7" core = "7.x" project = "commerce_paypal" -datestamp = "1540244884" +datestamp = "1557130694" diff --git a/www7/sites/all/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.module b/www7/sites/all/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.module index 818e42893..021358ef2 100644 --- a/www7/sites/all/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.module +++ b/www7/sites/all/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.module @@ -545,26 +545,213 @@ function commerce_paypal_wps_server_url($server) { function commerce_paypal_wps_languages() { return array( t('By country') => array( + 'AL' => t('Albania'), + 'DZ' => t('Algeria'), + 'AD' => t('Andorra'), + 'AO' => t('Angola'), + 'AI' => t('Anguilla'), + 'AG' => t('Antigua and Barbuda'), + 'AR' => t('Argentina'), + 'AM' => t('Armenia'), + 'AW' => t('Aruba'), 'AU' => t('Australia'), 'AT' => t('Austria'), + 'AZ' => t('Azerbaijan Republic'), + 'BS' => t('Bahamas'), + 'BH' => t('Bahrain'), + 'BB' => t('Barbados'), + 'BY' => t('Belarus'), 'BE' => t('Belgium'), + 'BZ' => t('Belize'), + 'BJ' => t('Benin'), + 'BM' => t('Bermuda'), + 'BT' => t('Bhutan'), + 'BO' => t('Bolivia'), + 'BA' => t('Bosnia and Herzegovina'), + 'BW' => t('Botswana'), 'BR' => t('Brazil'), + 'BN' => t('Brunei'), + 'BG' => t('Bulgaria'), + 'BF' => t('Burkina Faso'), + 'BI' => t('Burundi'), + 'KH' => t('Cambodia'), + 'CM' => t('Cameroon'), 'CA' => t('Canada'), - 'CN' => t('China'), + 'CV' => t('Cape Verde'), + 'KY' => t('Cayman Islands'), + 'TD' => t('Chad'), + 'CL' => t('Chile'), + 'C2' => t('China'), + 'CO' => t('Colombia'), + 'KM' => t('Comoros'), + 'CK' => t('Cook Islands'), + 'CR' => t('Costa Rica'), + 'CI' => t('Cote D\'Ivoire'), + 'HR' => t('Croatia'), + 'CY' => t('Cyprus'), + 'CZ' => t('Czech Republic'), + 'CD' => t('Democratic Republic of the Congo'), + 'DK' => t('Denmark'), + 'DJ' => t('Djibouti'), + 'DM' => t('Dominica'), + 'DO' => t('Dominican Republic'), + 'EC' => t('Ecuador'), + 'EG' => t('Egypt'), + 'SV' => t('El Salvador'), + 'ER' => t('Eritrea'), + 'EE' => t('Estonia'), + 'ET' => t('Ethiopia'), + 'FK' => t('Falkland Islands'), + 'FO' => t('Faroe Islands'), + 'FJ' => t('Fiji'), + 'FI' => t('Finland'), 'FR' => t('France'), + 'GF' => t('French Guiana'), + 'PF' => t('French Polynesia'), + 'GA' => t('Gabon Republic'), + 'GM' => t('Gambia'), + 'GE' => t('Georgia'), 'DE' => t('Germany'), + 'GI' => t('Gibraltar'), + 'GR' => t('Greece'), + 'GL' => t('Greenland'), + 'GD' => t('Grenada'), + 'GP' => t('Guadeloupe'), + 'GT' => t('Guatemala'), + 'GN' => t('Guinea'), + 'GW' => t('Guinea Bissau'), + 'GY' => t('Guyana'), + 'HN' => t('Honduras'), + 'HK' => t('Hong Kong'), + 'HU' => t('Hungary'), + 'IS' => t('Iceland'), + 'IN' => t('India'), + 'ID' => t('Indonesia'), + 'IE' => t('Ireland'), + 'IL' => t('Israel'), 'IT' => t('Italy'), + 'JM' => t('Jamaica'), + 'JP' => t('Japan'), + 'JO' => t('Jordan'), + 'KZ' => t('Kazakhstan'), + 'KE' => t('Kenya'), + 'KI' => t('Kiribati'), + 'KW' => t('Kuwait'), + 'KG' => t('Kyrgyzstan'), + 'LA' => t('Laos'), + 'LV' => t('Latvia'), + 'LS' => t('Lesotho'), + 'LI' => t('Liechtenstein'), + 'LT' => t('Lithuania'), + 'LU' => t('Luxembourg'), + 'MK' => t('Macedonia'), + 'MG' => t('Madagascar'), + 'MW' => t('Malawi'), + 'MY' => t('Malaysia'), + 'MV' => t('Maldives'), + 'ML' => t('Mali'), + 'MT' => t('Malta'), + 'MH' => t('Marshall Islands'), + 'MQ' => t('Martinique'), + 'MR' => t('Mauritania'), + 'MU' => t('Mauritius'), + 'YT' => t('Mayotte'), + 'MX' => t('Mexico'), + 'FM' => t('Micronesia'), + 'MD' => t('Moldova'), + 'MC' => t('Monaco'), + 'MN' => t('Mongolia'), + 'ME' => t('Montenegro'), + 'MS' => t('Montserrat'), + 'MA' => t('Morocco'), + 'MZ' => t('Mozambique'), + 'NA' => t('Namibia'), + 'NR' => t('Nauru'), + 'NP' => t('Nepal'), 'NL' => t('Netherlands'), + 'AN' => t('Netherlands Antilles'), + 'NC' => t('New Caledonia'), + 'NZ' => t('New Zealand'), + 'NI' => t('Nicaragua'), + 'NE' => t('Niger'), + 'NG' => t('Nigeria'), + 'NU' => t('Niue'), + 'NF' => t('Norfolk Island'), + 'NO' => t('Norway'), + 'OM' => t('Oman'), + 'PW' => t('Palau'), + 'PA' => t('Panama'), + 'PG' => t('Papua New Guinea'), + 'PY' => t('Paraguay'), + 'PE' => t('Peru'), + 'PH' => t('Philippines'), + 'PN' => t('Pitcairn Islands'), 'PL' => t('Poland'), 'PT' => t('Portugal'), + 'QA' => t('Qatar'), + 'CG' => t('Republic of the Congo'), + 'RE' => t('Reunion'), + 'RO' => t('Romania'), 'RU' => t('Russia'), + 'RW' => t('Rwanda'), + 'KN' => t('Saint Kitts and Nevis Anguilla'), + 'PM' => t('Saint Pierre and Miquelon'), + 'VC' => t('Saint Vincent and Grenadines'), + 'WS' => t('Samoa'), + 'SM' => t('San Marino'), + 'ST' => t('São Tomé and Príncipe'), + 'SA' => t('Saudi Arabia'), + 'SN' => t('Senegal'), + 'RS' => t('Serbia'), + 'SC' => t('Seychelles'), + 'SL' => t('Sierra Leone'), + 'SG' => t('Singapore'), + 'SK' => t('Slovakia'), + 'SI' => t('Slovenia'), + 'SB' => t('Solomon Islands'), + 'SO' => t('Somalia'), + 'ZA' => t('South Africa'), + 'KR' => t('South Korea'), 'ES' => t('Spain'), + 'LK' => t('Sri Lanka'), + 'SH' => t('St. Helena'), + 'LC' => t('St. Lucia'), + 'SR' => t('Suriname'), + 'SJ' => t('Svalbard and Jan Mayen Islands'), + 'SZ' => t('Swaziland'), + 'SE' => t('Sweden'), 'CH' => t('Switzerland'), + 'TW' => t('Taiwan'), + 'TJ' => t('Tajikistan'), + 'TZ' => t('Tanzania'), + 'TH' => t('Thailand'), + 'TG' => t('Togo'), + 'TO' => t('Tonga'), + 'TT' => t('Trinidad and Tobago'), + 'TN' => t('Tunisia'), + 'TR' => t('Turkey'), + 'TM' => t('Turkmenistan'), + 'TC' => t('Turks and Caicos Islands'), + 'TV' => t('Tuvalu'), + 'UG' => t('Uganda'), + 'UA' => t('Ukraine'), + 'AE' => t('United Arab Emirates'), 'GB' => t('United Kingdom'), 'US' => t('United States'), + 'UY' => t('Uruguay'), + 'VU' => t('Vanuatu'), + 'VA' => t('Vatican City State'), + 'VE' => t('Venezuela'), + 'VN' => t('Vietnam'), + 'VG' => t('Virgin Islands (British)'), + 'WF' => t('Wallis and Futuna Islands'), + 'YE' => t('Yemen'), + 'ZM' => t('Zambia'), + 'ZW' => t('Zimbabwe'), ), t('By language') => array( 'da_DK' => t('Danish (for Denmark only)'), + 'fr_CA' => t('French (for Canada only)'), 'he_IL' => t('Hebrew (for all)'), 'id_ID' => t('Indonesian (for Indonesia only)'), 'jp_JP' => t('Japanese (for Japan only)'), diff --git a/www7/sites/all/modules/contrib/context/context.info b/www7/sites/all/modules/contrib/context/context.info index 905805676..eef3a9c10 100644 --- a/www7/sites/all/modules/contrib/context/context.info +++ b/www7/sites/all/modules/contrib/context/context.info @@ -8,8 +8,8 @@ files[] = tests/context.test files[] = tests/context.conditions.test files[] = tests/context.reactions.test -; Information added by Drupal.org packaging script on 2018-11-30 -version = "7.x-3.9" +; Information added by Drupal.org packaging script on 2019-02-26 +version = "7.x-3.10" core = "7.x" project = "context" -datestamp = "1543594688" +datestamp = "1551220089" diff --git a/www7/sites/all/modules/contrib/context/context_layouts/context_layouts.info b/www7/sites/all/modules/contrib/context/context_layouts/context_layouts.info index 5bd844f0c..65880e389 100644 --- a/www7/sites/all/modules/contrib/context/context_layouts/context_layouts.info +++ b/www7/sites/all/modules/contrib/context/context_layouts/context_layouts.info @@ -6,8 +6,8 @@ core = 7.x files[] = plugins/context_layouts_reaction_block.inc -; Information added by Drupal.org packaging script on 2018-11-30 -version = "7.x-3.9" +; Information added by Drupal.org packaging script on 2019-02-26 +version = "7.x-3.10" core = "7.x" project = "context" -datestamp = "1543594688" +datestamp = "1551220089" diff --git a/www7/sites/all/modules/contrib/context/context_ui/context_ui.info b/www7/sites/all/modules/contrib/context/context_ui/context_ui.info index b17a8e704..e5d17f8fc 100644 --- a/www7/sites/all/modules/contrib/context/context_ui/context_ui.info +++ b/www7/sites/all/modules/contrib/context/context_ui/context_ui.info @@ -8,8 +8,8 @@ configure = admin/structure/context files[] = context.module files[] = tests/context_ui.test -; Information added by Drupal.org packaging script on 2018-11-30 -version = "7.x-3.9" +; Information added by Drupal.org packaging script on 2019-02-26 +version = "7.x-3.10" core = "7.x" project = "context" -datestamp = "1543594688" +datestamp = "1551220089" diff --git a/www7/sites/all/modules/contrib/context/plugins/context_reaction_block.js b/www7/sites/all/modules/contrib/context/plugins/context_reaction_block.js index fb0f3f162..192b956ff 100644 --- a/www7/sites/all/modules/contrib/context/plugins/context_reaction_block.js +++ b/www7/sites/all/modules/contrib/context/plugins/context_reaction_block.js @@ -68,7 +68,7 @@ DrupalContextBlockForm = function(blockForm) { // Hide enabled blocks from selector that are used $('table.context-blockform-region tr').each(function() { - var bid = $(this).attr('id'); + var bid = Drupal.checkPlain($(this).attr('id')); $('div.context-blockform-selector input[value="'+bid+'"]').parents('div.form-item').eq(0).hide(); }); // Show blocks in selector that are unused diff --git a/www7/sites/all/modules/contrib/ctools/bulk_export/bulk_export.info b/www7/sites/all/modules/contrib/ctools/bulk_export/bulk_export.info index 388cd6f73..9bb339445 100644 --- a/www7/sites/all/modules/contrib/ctools/bulk_export/bulk_export.info +++ b/www7/sites/all/modules/contrib/ctools/bulk_export/bulk_export.info @@ -3,11 +3,9 @@ description = Performs bulk exporting of data objects known about by Chaos tools core = 7.x dependencies[] = ctools package = Chaos tool suite -version = CTOOLS_MODULE_VERSION -; Information added by Drupal.org packaging script on 2018-02-24 -version = "7.x-1.14" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1519455788" - +datestamp = "1549603691" diff --git a/www7/sites/all/modules/contrib/ctools/css/dropbutton.css b/www7/sites/all/modules/contrib/ctools/css/dropbutton.css index 376fc674d..486ac3eda 100644 --- a/www7/sites/all/modules/contrib/ctools/css/dropbutton.css +++ b/www7/sites/all/modules/contrib/ctools/css/dropbutton.css @@ -19,7 +19,6 @@ height: auto; position: absolute; right: 0; - text-indent: -9999px; /* LTR */ top: 0; width: 17px; } @@ -50,6 +49,7 @@ right: 6px; position: absolute; top: 0.75em; + white-space: nowrap; } .ctools-dropbutton-processed.open .ctools-twisty { diff --git a/www7/sites/all/modules/contrib/ctools/ctools.info b/www7/sites/all/modules/contrib/ctools/ctools.info index c9d0c63a5..188b55dde 100644 --- a/www7/sites/all/modules/contrib/ctools/ctools.info +++ b/www7/sites/all/modules/contrib/ctools/ctools.info @@ -19,9 +19,8 @@ files[] = tests/object_cache.test files[] = tests/object_cache_unit.test files[] = tests/page_tokens.test -; Information added by Drupal.org packaging script on 2018-02-24 -version = "7.x-1.14" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1519455788" - +datestamp = "1549603691" diff --git a/www7/sites/all/modules/contrib/ctools/ctools.module b/www7/sites/all/modules/contrib/ctools/ctools.module index 3a805808b..62f291923 100644 --- a/www7/sites/all/modules/contrib/ctools/ctools.module +++ b/www7/sites/all/modules/contrib/ctools/ctools.module @@ -22,6 +22,9 @@ define('CTOOLS_API_VERSION', '2.0.9'); * simply include a dependency line in that module's info file, e.g.: * ; Requires CTools v7.x-1.4 or newer. * dependencies[] = ctools (>=1.4) + * + * @deprecated in CTools 1.15 and will be removed before CTools 2.0.0. + * Use the version provided by the drupal.org packaging system. */ define('CTOOLS_MODULE_VERSION', '7.x-1.13'); diff --git a/www7/sites/all/modules/contrib/ctools/ctools_access_ruleset/ctools_access_ruleset.info b/www7/sites/all/modules/contrib/ctools/ctools_access_ruleset/ctools_access_ruleset.info index 84a922fb5..3fc355649 100644 --- a/www7/sites/all/modules/contrib/ctools/ctools_access_ruleset/ctools_access_ruleset.info +++ b/www7/sites/all/modules/contrib/ctools/ctools_access_ruleset/ctools_access_ruleset.info @@ -2,12 +2,10 @@ name = Custom rulesets description = Create custom, exportable, reusable access rulesets for applications like Panels. core = 7.x package = Chaos tool suite -version = CTOOLS_MODULE_VERSION dependencies[] = ctools -; Information added by Drupal.org packaging script on 2018-02-24 -version = "7.x-1.14" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1519455788" - +datestamp = "1549603691" diff --git a/www7/sites/all/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.info b/www7/sites/all/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.info index 4bb8943cd..c2a52df40 100644 --- a/www7/sites/all/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.info +++ b/www7/sites/all/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.info @@ -1,13 +1,11 @@ name = Chaos Tools (CTools) AJAX Example description = Shows how to use the power of Chaos AJAX. package = Chaos tool suite -version = CTOOLS_MODULE_VERSION dependencies[] = ctools core = 7.x -; Information added by Drupal.org packaging script on 2018-02-24 -version = "7.x-1.14" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1519455788" - +datestamp = "1549603691" diff --git a/www7/sites/all/modules/contrib/ctools/ctools_custom_content/ctools_custom_content.info b/www7/sites/all/modules/contrib/ctools/ctools_custom_content/ctools_custom_content.info index 637bf25d5..2bc73489c 100644 --- a/www7/sites/all/modules/contrib/ctools/ctools_custom_content/ctools_custom_content.info +++ b/www7/sites/all/modules/contrib/ctools/ctools_custom_content/ctools_custom_content.info @@ -2,12 +2,10 @@ name = Custom content panes description = Create custom, exportable, reusable content panes for applications like Panels. core = 7.x package = Chaos tool suite -version = CTOOLS_MODULE_VERSION dependencies[] = ctools -; Information added by Drupal.org packaging script on 2018-02-24 -version = "7.x-1.14" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1519455788" - +datestamp = "1549603691" diff --git a/www7/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.info b/www7/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.info index 40f133bc8..3f268dbc0 100644 --- a/www7/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.info +++ b/www7/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.info @@ -1,16 +1,14 @@ name = Chaos Tools (CTools) Plugin Example description = Shows how an external module can provide ctools plugins (for Panels, etc.). package = Chaos tool suite -version = CTOOLS_MODULE_VERSION dependencies[] = ctools dependencies[] = panels dependencies[] = page_manager dependencies[] = advanced_help core = 7.x -; Information added by Drupal.org packaging script on 2018-02-24 -version = "7.x-1.14" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1519455788" - +datestamp = "1549603691" diff --git a/www7/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc b/www7/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc index bbabf2271..ed3ffd776 100644 --- a/www7/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc +++ b/www7/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc @@ -95,6 +95,7 @@ function ctools_plugin_example_default_page_manager_pages() { 'access' => array( 'logic' => 'and', ), + 'pipeline' => 'standard', ); $display = new panels_display(); $display->layout = 'threecol_33_34_33_stacked'; @@ -154,7 +155,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->configuration = array( 'title' => 'Long Arg Visibility Block', 'body' => 'This block will be here when the argument is longer than configured arg length. It uses the \'arg_length\' access plugin to test against the length of the argument used for Simplecontext.', - 'format' => '1', + 'format' => 'filtered_html', 'substitute' => 1, ); $pane->cache = array(); @@ -185,7 +186,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->configuration = array( 'title' => 'Short Arg Visibility', 'body' => 'This block appears when the simplecontext argument is less than the configured length.', - 'format' => '1', + 'format' => 'filtered_html', 'substitute' => 1, ); $pane->cache = array(); @@ -297,7 +298,7 @@ function ctools_plugin_example_default_page_manager_pages() { item1 is %sc:item1 item2 is %sc:item2 description is %sc:description', - 'format' => '1', + 'format' => 'filtered_html', 'substitute' => 1, ); $pane->cache = array(); @@ -363,7 +364,7 @@ function ctools_plugin_example_default_page_manager_pages() { 'body' => 'The CTools Plugin Example module (and this panel page) are just here to demonstrate how to build CTools plugins. ', - 'format' => '2', + 'format' => 'full_html', 'substitute' => 1, ); $pane->cache = array(); @@ -408,6 +409,7 @@ function ctools_plugin_example_default_page_manager_pages() { 'css' => '', 'contexts' => array(), 'relationships' => array(), + 'pipeline' => 'standard', ); $display = new panels_display(); $display->layout = 'onecol'; @@ -428,7 +430,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->configuration = array( 'title' => 'Use this page with an argument', 'body' => 'This demo page works if you use an argument, like ctools_plugin_example/xxxxx.', - 'format' => '1', + 'format' => 'filtered_html', 'substitute' => NULL, ); $pane->cache = array(); diff --git a/www7/sites/all/modules/contrib/ctools/drush/ctools.drush.inc b/www7/sites/all/modules/contrib/ctools/drush/ctools.drush.inc index 3677b1712..34fd4d3a5 100644 --- a/www7/sites/all/modules/contrib/ctools/drush/ctools.drush.inc +++ b/www7/sites/all/modules/contrib/ctools/drush/ctools.drush.inc @@ -452,6 +452,7 @@ function ctools_drush_export_info() { * all arg handling etc in one place. */ function ctools_drush_export_op_command() { + $args = func_get_args(); // Get all info for the current drush command. $command = drush_get_command(); $op = ''; @@ -495,7 +496,6 @@ function ctools_drush_export_op_command() { } } else { - $args = func_get_args(); // Table name should always be first arg... $table_name = array_shift($args); // Any additional args are assumed to be exportable names. diff --git a/www7/sites/all/modules/contrib/ctools/includes/dropbutton.theme.inc b/www7/sites/all/modules/contrib/ctools/includes/dropbutton.theme.inc index d69c98831..4b95ef0df 100644 --- a/www7/sites/all/modules/contrib/ctools/includes/dropbutton.theme.inc +++ b/www7/sites/all/modules/contrib/ctools/includes/dropbutton.theme.inc @@ -104,10 +104,10 @@ function theme_links__ctools_dropbutton($vars) { $output .= ''; diff --git a/www7/sites/all/modules/contrib/ctools/includes/export-ui.inc b/www7/sites/all/modules/contrib/ctools/includes/export-ui.inc index 1eb87ae77..72e5a2fdf 100644 --- a/www7/sites/all/modules/contrib/ctools/includes/export-ui.inc +++ b/www7/sites/all/modules/contrib/ctools/includes/export-ui.inc @@ -333,7 +333,7 @@ function ctools_export_ui_process(&$plugin, $info) { $plugin['strings']['confirmation']['delete'] += array( 'question' => t('Are you sure you want to delete %title?'), - 'information' => t('This action will permanently remove this item from your database..'), + 'information' => t('This action will permanently remove this item from your database.'), 'success' => t('The item has been deleted.'), ); diff --git a/www7/sites/all/modules/contrib/ctools/includes/wizard.inc b/www7/sites/all/modules/contrib/ctools/includes/wizard.inc index 8b0d061c4..e0b8fbd14 100644 --- a/www7/sites/all/modules/contrib/ctools/includes/wizard.inc +++ b/www7/sites/all/modules/contrib/ctools/includes/wizard.inc @@ -503,7 +503,7 @@ function ctools_wizard_defaults(&$form_info) { $form_info = $form_info + $defaults; // Set form callbacks if they aren't defined. foreach ($form_info['forms'] as $step => $params) { - if (!$params['form id']) { + if (empty($params['form id'])) { $form_callback = $hook . '_' . $step . '_form'; $form_info['forms'][$step]['form id'] = $form_callback; } diff --git a/www7/sites/all/modules/contrib/ctools/js/modal.js b/www7/sites/all/modules/contrib/ctools/js/modal.js index e65f51afa..ec7b02aea 100644 --- a/www7/sites/all/modules/contrib/ctools/js/modal.js +++ b/www7/sites/all/modules/contrib/ctools/js/modal.js @@ -378,7 +378,7 @@ } } - if (!speed) { + if (!speed && 0 !== speed) { speed = 'fast'; } @@ -555,7 +555,7 @@ // Create our content div, get the dimensions, and hide it var modalContent = $('#modalContent').css('top','-1000px'); var $modalHeader = modalContent.find('.modal-header'); - var mdcTop = wt + ( winHeight / 2 ) - ( modalContent.outerHeight() / 2); + var mdcTop = wt + Math.max((winHeight / 2) - (modalContent.outerHeight() / 2), 0); var mdcLeft = ( winWidth / 2 ) - ( modalContent.outerWidth() / 2); $('#modalBackdrop').css(css).css('top', 0).css('height', docHeight + 'px').css('width', docWidth + 'px').show(); modalContent.css({top: mdcTop + 'px', left: mdcLeft + 'px'}).hide()[animation](speed); @@ -588,35 +588,43 @@ $('body').unbind( 'keypress', modalEventHandler ); $('body').unbind( 'keydown', modalTabTrapHandler ); $('.close', $modalHeader).unbind('click', modalContentClose); - $('body').unbind('keypress', modalEventEscapeCloseHandler); + $(document).unbind('keydown', modalEventEscapeCloseHandler); $(document).trigger('CToolsDetachBehaviors', $('#modalContent')); - // Set our animation parameters and use them - if ( animation == 'fadeIn' ) animation = 'fadeOut'; - if ( animation == 'slideDown' ) animation = 'slideUp'; - if ( animation == 'show' ) animation = 'hide'; + // Closing animation. + switch (animation) { + case 'fadeIn': + modalContent.fadeOut(speed, modalContentRemove); + break; - // Close the content - modalContent.hide()[animation](speed); + case 'slideDown': + modalContent.slideUp(speed, modalContentRemove); + break; - // Remove the content + case 'show': + modalContent.hide(speed, modalContentRemove); + break; + } + } + + // Remove the content. + modalContentRemove = function () { $('#modalContent').remove(); $('#modalBackdrop').remove(); - // Restore focus to where it was before opening the dialog + // Restore focus to where it was before opening the dialog. $(oldFocus).focus(); }; // Move and resize the modalBackdrop and modalContent on window resize. - modalContentResize = function(){ - + modalContentResize = function () { // Reset the backdrop height/width to get accurate document size. $('#modalBackdrop').css('height', '').css('width', ''); // Position code lifted from: // http://www.quirksmode.org/viewport/compatibility.html if (self.pageYOffset) { // all except Explorer - var wt = self.pageYOffset; + var wt = self.pageYOffset; } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict var wt = document.documentElement.scrollTop; } else if (document.body) { // all other Explorers @@ -632,7 +640,7 @@ // Get where we should move content to var modalContent = $('#modalContent'); - var mdcTop = wt + ( winHeight / 2 ) - ( modalContent.outerHeight() / 2); + var mdcTop = wt + Math.max((winHeight / 2) - (modalContent.outerHeight() / 2), 0); var mdcLeft = ( winWidth / 2 ) - ( modalContent.outerWidth() / 2); // Apply the changes diff --git a/www7/sites/all/modules/contrib/ctools/page_manager/page_manager.info b/www7/sites/all/modules/contrib/ctools/page_manager/page_manager.info index 586613851..a56663b8d 100644 --- a/www7/sites/all/modules/contrib/ctools/page_manager/page_manager.info +++ b/www7/sites/all/modules/contrib/ctools/page_manager/page_manager.info @@ -3,13 +3,11 @@ description = Provides a UI and API to manage pages within the site. core = 7.x dependencies[] = ctools package = Chaos tool suite -version = CTOOLS_MODULE_VERSION files[] = tests/head_links.test -; Information added by Drupal.org packaging script on 2018-02-24 -version = "7.x-1.14" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1519455788" - +datestamp = "1549603691" diff --git a/www7/sites/all/modules/contrib/ctools/page_manager/plugins/tasks/page.inc b/www7/sites/all/modules/contrib/ctools/page_manager/plugins/tasks/page.inc index 8d24d3309..100dfd817 100644 --- a/www7/sites/all/modules/contrib/ctools/page_manager/plugins/tasks/page.inc +++ b/www7/sites/all/modules/contrib/ctools/page_manager/plugins/tasks/page.inc @@ -261,6 +261,7 @@ function page_manager_page_theme(&$items, $task) { * creating named arguments in the path. */ function page_manager_page_execute($subtask_id) { + $func_args = func_get_args(); $page = page_manager_page_load($subtask_id); $task = page_manager_get_task($page->task); $subtask = page_manager_get_task_subtask($task, $subtask_id); @@ -268,7 +269,7 @@ function page_manager_page_execute($subtask_id) { // Turn the contexts into a properly keyed array. $contexts = array(); $args = array(); - foreach (func_get_args() as $count => $arg) { + foreach ($func_args as $count => $arg) { if (is_object($arg) && get_class($arg) == 'ctools_context') { $contexts[$arg->id] = $arg; $args[] = $arg->original_argument; @@ -430,7 +431,7 @@ function page_manager_page_save(&$page) { /** * Remove a page subtask. */ -function page_manager_page_delete($page) { +function page_manager_page_delete($page, $skip_menu_rebuild = FALSE) { $task = page_manager_get_task($page->task); if ($function = ctools_plugin_get_function($task, 'delete')) { $function($page); @@ -448,7 +449,11 @@ function page_manager_page_delete($page) { // rebuild this page again. ctools_include('export'); ctools_export_load_object_reset('page_manager_pages'); - menu_rebuild(); + // Allow menu rebuild to be skipped when calling code is deleting multiple + // pages. + if (!$skip_menu_rebuild) { + menu_rebuild(); + } } /** diff --git a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_attachments.inc b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_attachments.inc index c55e0752d..51c95a5e9 100644 --- a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_attachments.inc +++ b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_attachments.inc @@ -21,7 +21,7 @@ if (module_exists('upload')) { function ctools_node_form_attachments_content_type_render($subtype, $conf, $panel_args, &$context) { $block = new stdClass(); - $block->module = t('node_form'); + $block->module = 'node_form'; $block->title = t('Attach files'); $block->delta = 'url-path-options'; diff --git a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_author.inc b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_author.inc index f9e937198..86f3d7314 100644 --- a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_author.inc +++ b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_author.inc @@ -17,7 +17,7 @@ $plugin = array( function ctools_node_form_author_content_type_render($subtype, $conf, $panel_args, &$context) { $block = new stdClass(); - $block->module = t('node_form'); + $block->module = 'node_form'; $block->title = t('Authoring information'); $block->delta = 'author-options'; diff --git a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_book.inc b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_book.inc index af022bfd6..b7ad36bdf 100644 --- a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_book.inc +++ b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_book.inc @@ -21,7 +21,7 @@ if (module_exists('book')) { function ctools_node_form_book_content_type_render($subtype, $conf, $panel_args, &$context) { $block = new stdClass(); - $block->module = t('node_form'); + $block->module = 'node_form'; $block->title = t('Book outline'); $block->delta = 'book-outline'; diff --git a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_buttons.inc b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_buttons.inc index 560a01e4f..ed553a67d 100644 --- a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_buttons.inc +++ b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_buttons.inc @@ -17,7 +17,7 @@ $plugin = array( function ctools_node_form_buttons_content_type_render($subtype, $conf, $panel_args, &$context) { $block = new stdClass(); - $block->module = t('node_form'); + $block->module = 'node_form'; $block->title = ''; $block->delta = 'buttons'; diff --git a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_comment.inc b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_comment.inc index 352681cc6..eaf5c954c 100644 --- a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_comment.inc +++ b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_comment.inc @@ -21,7 +21,7 @@ if (module_exists('comment')) { function ctools_node_form_comment_content_type_render($subtype, $conf, $panel_args, &$context) { $block = new stdClass(); - $block->module = t('node_form'); + $block->module = 'node_form'; $block->title = t('Comment options'); $block->delta = 'comment-options'; diff --git a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_language.inc b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_language.inc index 76ab3f824..dfef295f5 100644 --- a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_language.inc +++ b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_language.inc @@ -17,7 +17,7 @@ $plugin = array( function ctools_node_form_language_content_type_render($subtype, $conf, $panel_args, &$context) { $block = new stdClass(); - $block->module = t('node_form'); + $block->module = 'node_form'; $block->delta = 'language-options'; diff --git a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_log.inc b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_log.inc index 329ea8990..aac42a645 100644 --- a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_log.inc +++ b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_log.inc @@ -17,7 +17,7 @@ $plugin = array( function ctools_node_form_log_content_type_render($subtype, $conf, $panel_args, &$context) { $block = new stdClass(); - $block->module = t('node_form'); + $block->module = 'node_form'; $block->title = t('Revision information'); if (isset($context->form)) { diff --git a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_menu.inc b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_menu.inc index 576dcd438..9b4df4345 100644 --- a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_menu.inc +++ b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_menu.inc @@ -21,7 +21,7 @@ if (module_exists('menu')) { function ctools_node_form_menu_content_type_render($subtype, $conf, $panel_args, &$context) { $block = new stdClass(); - $block->module = t('node_form'); + $block->module = 'node_form'; $block->title = t('Menu options'); $block->delta = 'menu-options'; diff --git a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_path.inc b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_path.inc index 9aeeba00a..262851f0a 100644 --- a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_path.inc +++ b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_path.inc @@ -21,7 +21,7 @@ if (module_exists('path')) { function ctools_node_form_path_content_type_render($subtype, $conf, $panel_args, &$context) { $block = new stdClass(); - $block->module = t('node_form'); + $block->module = 'node_form'; $block->title = t('URL path options'); $block->delta = 'url-path-options'; diff --git a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_publishing.inc b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_publishing.inc index f8b92aec9..53b584f90 100644 --- a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_publishing.inc +++ b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_publishing.inc @@ -23,7 +23,7 @@ function ctools_node_form_publishing_content_type_render($subtype, $conf, $panel $block = new stdClass(); $block->title = t('Publishing options'); - $block->module = t('node_form'); + $block->module = 'node_form'; $block->delta = 'publishing-options'; if (isset($context->form)) { diff --git a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_title.inc b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_title.inc index 2f58ee309..14e79abae 100644 --- a/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_title.inc +++ b/www7/sites/all/modules/contrib/ctools/plugins/content_types/node_form/node_form_title.inc @@ -17,7 +17,7 @@ $plugin = array( function ctools_node_form_title_content_type_render($subtype, $conf, $panel_args, &$context) { $block = new stdClass(); - $block->module = t('node_form'); + $block->module = 'node_form'; $block->delta = 'title-options'; diff --git a/www7/sites/all/modules/contrib/ctools/plugins/contexts/user.inc b/www7/sites/all/modules/contrib/ctools/plugins/contexts/user.inc index 362cfe3b1..bbdbd1a27 100644 --- a/www7/sites/all/modules/contrib/ctools/plugins/contexts/user.inc +++ b/www7/sites/all/modules/contrib/ctools/plugins/contexts/user.inc @@ -152,6 +152,7 @@ function ctools_context_user_settings_form_submit($form, &$form_state) { */ function ctools_context_user_convert_list() { $tokens = token_info(); + $list = array(); foreach ($tokens['tokens']['user'] as $id => $info) { if (!isset($list[$id])) { $list[$id] = $info['name']; diff --git a/www7/sites/all/modules/contrib/ctools/plugins/export_ui/ctools_export_ui.class.php b/www7/sites/all/modules/contrib/ctools/plugins/export_ui/ctools_export_ui.class.php index 3e1aed4ba..0c8145e4b 100644 --- a/www7/sites/all/modules/contrib/ctools/plugins/export_ui/ctools_export_ui.class.php +++ b/www7/sites/all/modules/contrib/ctools/plugins/export_ui/ctools_export_ui.class.php @@ -644,6 +644,7 @@ public function redirect($op, $item = NULL) { } public function add_page($js, $input, $step = NULL) { + $args = func_get_args(); drupal_set_title($this->get_page_title('add'), PASS_THROUGH); // If a step not set, they are trying to create a new item. If a step @@ -666,7 +667,7 @@ public function add_page($js, $input, $step = NULL) { 'no_redirect' => TRUE, 'step' => $step, // Store these in case additional args are needed. - 'function args' => func_get_args(), + 'function args' => $args, ); $output = $this->edit_execute_form($form_state); @@ -681,6 +682,7 @@ public function add_page($js, $input, $step = NULL) { * Main entry point to edit an item. */ public function edit_page($js, $input, $item, $step = NULL) { + $args = func_get_args(); drupal_set_title($this->get_page_title('edit', $item), PASS_THROUGH); // Check to see if there is a cached item to get if we're using the wizard. @@ -702,7 +704,7 @@ public function edit_page($js, $input, $item, $step = NULL) { 'no_redirect' => TRUE, 'step' => $step, // Store these in case additional args are needed. - 'function args' => func_get_args(), + 'function args' => $args, ); $output = $this->edit_execute_form($form_state); @@ -717,6 +719,7 @@ public function edit_page($js, $input, $item, $step = NULL) { * Main entry point to clone an item. */ public function clone_page($js, $input, $original, $step = NULL) { + $args = func_get_args(); drupal_set_title($this->get_page_title('clone', $original), PASS_THROUGH); // If a step not set, they are trying to create a new clone. If a step @@ -756,7 +759,7 @@ public function clone_page($js, $input, $original, $step = NULL) { 'no_redirect' => TRUE, 'step' => $step, // Store these in case additional args are needed. - 'function args' => func_get_args(), + 'function args' => $args, ); $output = $this->edit_execute_form($form_state); @@ -1237,6 +1240,7 @@ public function export_page($js, $input, $item) { * Page callback to import information for an exportable item. */ public function import_page($js, $input, $step = NULL) { + $args = func_get_args(); drupal_set_title($this->get_page_title('import'), PASS_THROUGH); // Import is basically a multi step wizard form, so let's go ahead and // use CTools' wizard.inc for it. @@ -1261,7 +1265,7 @@ public function import_page($js, $input, $step = NULL) { 'no_redirect' => TRUE, 'step' => $step, // Store these in case additional args are needed. - 'function args' => func_get_args(), + 'function args' => $args, ); // import always uses the wizard. diff --git a/www7/sites/all/modules/contrib/ctools/stylizer/stylizer.info b/www7/sites/all/modules/contrib/ctools/stylizer/stylizer.info index f8b442e49..90204a3de 100644 --- a/www7/sites/all/modules/contrib/ctools/stylizer/stylizer.info +++ b/www7/sites/all/modules/contrib/ctools/stylizer/stylizer.info @@ -2,13 +2,11 @@ name = Stylizer description = Create custom styles for applications such as Panels. core = 7.x package = Chaos tool suite -version = CTOOLS_MODULE_VERSION dependencies[] = ctools dependencies[] = color -; Information added by Drupal.org packaging script on 2018-02-24 -version = "7.x-1.14" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1519455788" - +datestamp = "1549603691" diff --git a/www7/sites/all/modules/contrib/ctools/term_depth/term_depth.info b/www7/sites/all/modules/contrib/ctools/term_depth/term_depth.info index 326b2aef3..4baee51d8 100644 --- a/www7/sites/all/modules/contrib/ctools/term_depth/term_depth.info +++ b/www7/sites/all/modules/contrib/ctools/term_depth/term_depth.info @@ -3,11 +3,9 @@ description = Controls access to context based upon term depth core = 7.x dependencies[] = ctools package = Chaos tool suite -version = CTOOLS_MODULE_VERSION -; Information added by Drupal.org packaging script on 2018-02-24 -version = "7.x-1.14" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1519455788" - +datestamp = "1549603691" diff --git a/www7/sites/all/modules/contrib/ctools/tests/ctools_export_test/ctools_export_test.info b/www7/sites/all/modules/contrib/ctools/tests/ctools_export_test/ctools_export_test.info index 7abac58cb..a2d0efbd9 100644 --- a/www7/sites/all/modules/contrib/ctools/tests/ctools_export_test/ctools_export_test.info +++ b/www7/sites/all/modules/contrib/ctools/tests/ctools_export_test/ctools_export_test.info @@ -2,15 +2,13 @@ name = CTools export test description = CTools export test module core = 7.x package = Chaos tool suite -version = CTOOLS_MODULE_VERSION dependencies[] = ctools hidden = TRUE files[] = ctools_export.test -; Information added by Drupal.org packaging script on 2018-02-24 -version = "7.x-1.14" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1519455788" - +datestamp = "1549603691" diff --git a/www7/sites/all/modules/contrib/ctools/tests/ctools_plugin_test.info b/www7/sites/all/modules/contrib/ctools/tests/ctools_plugin_test.info index 649e8912b..d51a52614 100644 --- a/www7/sites/all/modules/contrib/ctools/tests/ctools_plugin_test.info +++ b/www7/sites/all/modules/contrib/ctools/tests/ctools_plugin_test.info @@ -1,14 +1,12 @@ name = Chaos tools plugins test description = Provides hooks for testing ctools plugins. package = Chaos tool suite -version = CTOOLS_MODULE_VERSION core = 7.x dependencies[] = ctools hidden = TRUE -; Information added by Drupal.org packaging script on 2018-02-24 -version = "7.x-1.14" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1519455788" - +datestamp = "1549603691" diff --git a/www7/sites/all/modules/contrib/ctools/views_content/plugins/relationships/node_from_view.inc b/www7/sites/all/modules/contrib/ctools/views_content/plugins/relationships/node_from_view.inc index 0f3fa8e0a..f54cbc6b5 100644 --- a/www7/sites/all/modules/contrib/ctools/views_content/plugins/relationships/node_from_view.inc +++ b/www7/sites/all/modules/contrib/ctools/views_content/plugins/relationships/node_from_view.inc @@ -33,7 +33,7 @@ function views_content_node_from_view_context($context, $conf, $placeholder = FA views_content_context_get_output($context); $row = intval($conf['row']) - 1; - if (isset($view->result[$row])) { + if (isset($view->result[$row]) && isset($view->base_field) && isset($view->result[$row]->{$view->base_field})) { $nid = $view->result[$row]->{$view->base_field}; if ($nid) { $node = node_load($nid); diff --git a/www7/sites/all/modules/contrib/ctools/views_content/views_content.info b/www7/sites/all/modules/contrib/ctools/views_content/views_content.info index 254d474b8..68f4d8756 100644 --- a/www7/sites/all/modules/contrib/ctools/views_content/views_content.info +++ b/www7/sites/all/modules/contrib/ctools/views_content/views_content.info @@ -5,14 +5,12 @@ dependencies[] = ctools dependencies[] = views core = 7.x package = Chaos tool suite -version = CTOOLS_MODULE_VERSION files[] = plugins/views/views_content_plugin_display_ctools_context.inc files[] = plugins/views/views_content_plugin_display_panel_pane.inc files[] = plugins/views/views_content_plugin_style_ctools_context.inc -; Information added by Drupal.org packaging script on 2018-02-24 -version = "7.x-1.14" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1519455788" - +datestamp = "1549603691" diff --git a/www7/sites/all/modules/contrib/link/link.info b/www7/sites/all/modules/contrib/link/link.info index 06377f758..351e1782b 100644 --- a/www7/sites/all/modules/contrib/link/link.info +++ b/www7/sites/all/modules/contrib/link/link.info @@ -18,8 +18,8 @@ files[] = tests/link.validate.test files[] = views/link_views_handler_argument_target.inc files[] = views/link_views_handler_filter_protocol.inc -; Information added by Drupal.org packaging script on 2018-05-13 -version = "7.x-1.5" +; Information added by Drupal.org packaging script on 2019-02-20 +version = "7.x-1.6" core = "7.x" project = "link" -datestamp = "1526190487" +datestamp = "1550680687" diff --git a/www7/sites/all/modules/contrib/link/link.module b/www7/sites/all/modules/contrib/link/link.module index 059ec4347..8b3094f36 100644 --- a/www7/sites/all/modules/contrib/link/link.module +++ b/www7/sites/all/modules/contrib/link/link.module @@ -315,6 +315,16 @@ function link_field_validate($entity_type, $entity, $field, $instance, $langcode } } + foreach ($items as $delta => $value) { + if (isset($value['attributes']) && is_string($value['attributes'])) { + $errors[$field['field_name']][$langcode][$delta][] = array( + 'error' => 'link_required', + 'message' => t('String values are not acceptable for attributes.'), + 'error_element' => array('url' => TRUE, 'title' => FALSE), + ); + } + } + if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] && !$optional_field_found) { $errors[$field['field_name']][$langcode][0][] = array( 'error' => 'link_required', diff --git a/www7/sites/all/modules/contrib/module_filter/js/module_filter_tab.js b/www7/sites/all/modules/contrib/module_filter/js/module_filter_tab.js index 967eca603..c62b9ef64 100644 --- a/www7/sites/all/modules/contrib/module_filter/js/module_filter_tab.js +++ b/www7/sites/all/modules/contrib/module_filter/js/module_filter_tab.js @@ -57,7 +57,7 @@ Drupal.behaviors.moduleFilterTabs = { // Build tabs from package title rows. var tabs = '