diff --git a/core/INSTALL.txt b/core/INSTALL.txt index 8c5ee87..c839761 100644 --- a/core/INSTALL.txt +++ b/core/INSTALL.txt @@ -90,6 +90,19 @@ INSTALLATION mv drupal-x.y.z/* drupal-x.y.z/.htaccess drupal-x.y.z/.csslintrc drupal-x.y.z/.editorconfig drupal-x.y.z/.eslintignore drupal-x.y.z/.eslintrc drupal-x.y.z/.gitattributes /path/to/your/installation + You can also download the latest version of Drupal using Git on the command + line and set up a repository by following the instructions at + https://www.drupal.org/project/drupal/git-instructions for "Setting up + repository for the first time". + + Once you have downloaded Drupal successfully, you may install Composer + globally using the instructions at + https://getcomposer.org/doc/00-intro.md#globally + + With Composer installed, run the following command from the Drupal web root: + + composer install + 2. Create the Drupal database. Because Drupal stores all site information in a database, the Drupal diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt index 7d23eea..a74afa6 100644 --- a/core/MAINTAINERS.txt +++ b/core/MAINTAINERS.txt @@ -23,9 +23,10 @@ Drupal 8 (Release Manager) - Alex Pott 'alexpott' https://www.drupal.org/u/alexpott (Framework Manager) - -Provisional membership: - Scott Reeves 'Cottser' https://www.drupal.org/u/cottser + (Framework Manager - Frontend) + +Provisional membership: None at this time. Drupal 7 - Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx @@ -323,6 +324,10 @@ DateTime module - Jonathan Hedstrom 'jhedstrom' https://www.drupal.org/u/jhedstrom - Matthew Donadio 'mpdonadio' https://www.drupal.org/u/mpdonadio +DateTime range module +- Jonathan Hedstrom 'jhedstrom' https://www.drupal.org/u/jhedstrom +- Matthew Donadio 'mpdonadio' https://www.drupal.org/u/mpdonadio + Dynamic Page Cache module - Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx - Wim Leers 'Wim Leers' https://www.drupal.org/u/wim-leers diff --git a/core/composer.json b/core/composer.json index de2feb8..53ab8fa 100644 --- a/core/composer.json +++ b/core/composer.json @@ -88,6 +88,7 @@ "drupal/core-utility": "self.version", "drupal/core-uuid": "self.version", "drupal/datetime": "self.version", + "drupal/datetime_range": "self.version", "drupal/dblog": "self.version", "drupal/dynamic_page_cache": "self.version", "drupal/editor": "self.version", diff --git a/core/core.api.php b/core/core.api.php index 6a34823..7b90ae2 100644 --- a/core/core.api.php +++ b/core/core.api.php @@ -2047,7 +2047,7 @@ function hook_mail_alter(&$message) { * An array of parameters supplied by the caller of * MailManagerInterface->mail(). * - * @see \Drupal\Core\Mail\MailManagerInterface->mail() + * @see \Drupal\Core\Mail\MailManagerInterface::mail() */ function hook_mail($key, &$message, $params) { $account = $params['account']; diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index ec12097..292e918 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -599,7 +599,7 @@ function _drupal_exception_handler_additional($exception, $exception2) { * @param string $new_prefix * Internal use only. A new prefix to be stored. * - * @return string|FALSE + * @return string|false * Either the simpletest prefix (the string "simpletest" followed by any * number of digits) or FALSE if the user agent does not contain a valid * HMAC and timestamp. @@ -640,11 +640,15 @@ function drupal_valid_test_ua($new_prefix = NULL) { $key = $private_key . filectime(__FILE__) . fileinode(__FILE__); $time_diff = REQUEST_TIME - $time; $test_hmac = Crypt::hmacBase64($check_string, $key); - // Since we are making a local request a 5 second time window is allowed, + // Since we are making a local request a 600 second time window is allowed, // and the HMAC must match. - if ($time_diff >= 0 && $time_diff <= 5 && $hmac === $test_hmac) { + if ($time_diff >= 0 && $time_diff <= 600 && $hmac === $test_hmac) { $test_prefix = $prefix; } + else { + header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden (SIMPLETEST_USER_AGENT invalid)'); + exit; + } } return $test_prefix; } diff --git a/core/includes/update.inc b/core/includes/update.inc index 2c020db..a9b5de9 100644 --- a/core/includes/update.inc +++ b/core/includes/update.inc @@ -596,7 +596,7 @@ function update_already_performed($module, $number) { * An array of return values obtained by merging the results of the * hook_update_dependencies() implementations in all installed modules. * - * @see \Drupal::moduleHandler()->invokeAll() + * @see \Drupal\Core\Extension\ModuleHandlerInterface::invokeAll() * @see hook_update_dependencies() */ function update_retrieve_dependencies() { @@ -707,7 +707,7 @@ function update_language_list($flags = LanguageInterface::STATE_CONFIGURABLE) { // Initialize master language list. if (!isset($languages)) { // Initialize local language list cache. - $languages = array(); + $languages = array(); // Fill in master language list based on current configuration. $default = \Drupal::languageManager()->getDefaultLanguage(); diff --git a/core/lib/Drupal/Component/Assertion/Handle.php b/core/lib/Drupal/Component/Assertion/Handle.php index dd9911e..5069b6b 100644 --- a/core/lib/Drupal/Component/Assertion/Handle.php +++ b/core/lib/Drupal/Component/Assertion/Handle.php @@ -1,41 +1,6 @@ file = $file; - $this->line = $line; - } - - } -} - -} - -namespace Drupal\Component\Assertion { +namespace Drupal\Component\Assertion; /** * Handler for runtime assertion failures. @@ -56,6 +21,9 @@ public static function register() { assert_options(ASSERT_WARNING, FALSE); if (version_compare(PHP_VERSION, '7.0.0-dev') < 0) { + if (!class_exists('AssertionError', FALSE)) { + require __DIR__ . '/global_namespace_php5.php'; + } // PHP 5 - create a handler to throw the exception directly. assert_options(ASSERT_CALLBACK, function($file = '', $line = 0, $code = '', $message = '') { if (empty($message)) { @@ -71,5 +39,3 @@ public static function register() { } } - -} diff --git a/core/lib/Drupal/Component/Assertion/Inspector.php b/core/lib/Drupal/Component/Assertion/Inspector.php index 6b96a7a..9b04e33 100644 --- a/core/lib/Drupal/Component/Assertion/Inspector.php +++ b/core/lib/Drupal/Component/Assertion/Inspector.php @@ -108,7 +108,7 @@ public static function assertAllStringable($traversable) { * Use this instead of is_string() alone unless the argument being an object * in any way will cause a problem. * - * @param mixed string + * @param mixed $string * Variable to be examined * * @return bool diff --git a/core/lib/Drupal/Component/Assertion/global_namespace_php5.php b/core/lib/Drupal/Component/Assertion/global_namespace_php5.php new file mode 100644 index 0000000..1b5caf4 --- /dev/null +++ b/core/lib/Drupal/Component/Assertion/global_namespace_php5.php @@ -0,0 +1,28 @@ +file = $file; + $this->line = $line; + } + +} diff --git a/core/lib/Drupal/Component/Gettext/PoItem.php b/core/lib/Drupal/Component/Gettext/PoItem.php index c557964..6cedff1 100644 --- a/core/lib/Drupal/Component/Gettext/PoItem.php +++ b/core/lib/Drupal/Component/Gettext/PoItem.php @@ -169,7 +169,7 @@ function setComment($comment) { /** * Create the PoItem from a structured array. * - * @param array values + * @param array $values */ public function setFromArray(array $values = array()) { if (isset($values['context'])) { diff --git a/core/lib/Drupal/Component/PhpStorage/PhpStorageInterface.php b/core/lib/Drupal/Component/PhpStorage/PhpStorageInterface.php index 71f4700..735341d 100644 --- a/core/lib/Drupal/Component/PhpStorage/PhpStorageInterface.php +++ b/core/lib/Drupal/Component/PhpStorage/PhpStorageInterface.php @@ -80,7 +80,7 @@ public function deleteAll(); * @param string $name * The virtual file name. Can be a relative path. * - * @return string|FALSE + * @return string|false * The full file path for the provided name. Return FALSE if the * implementation needs to prevent access to the file. */ diff --git a/core/lib/Drupal/Component/Utility/SortArray.php b/core/lib/Drupal/Component/Utility/SortArray.php index 044a214..2ef6223 100644 --- a/core/lib/Drupal/Component/Utility/SortArray.php +++ b/core/lib/Drupal/Component/Utility/SortArray.php @@ -81,9 +81,9 @@ public static function sortByTitleElement($a, $b) { * @return int * The comparison result for uasort(). */ - public static function sortByTitleProperty($a, $b) { - return static::sortByKeyString($a, $b, '#title'); - } + public static function sortByTitleProperty($a, $b) { + return static::sortByKeyString($a, $b, '#title'); + } /** * Sorts a string array item by an arbitrary key. diff --git a/core/lib/Drupal/Core/Access/AccessResultReasonInterface.php b/core/lib/Drupal/Core/Access/AccessResultReasonInterface.php index 468d998..850194c 100644 --- a/core/lib/Drupal/Core/Access/AccessResultReasonInterface.php +++ b/core/lib/Drupal/Core/Access/AccessResultReasonInterface.php @@ -17,7 +17,7 @@ /** * Gets the reason for this access result. * - * @return string|NULL + * @return string|null * The reason of this access result or NULL if no reason is provided. */ public function getReason(); @@ -25,7 +25,7 @@ public function getReason(); /** * Sets the reason for this access result. * - * @param $reason string|NULL + * @param $reason string|null * The reason of this access result or NULL if no reason is provided. * * @return \Drupal\Core\Access\AccessResultInterface diff --git a/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php b/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php index 9beddd1..eb47cb2 100644 --- a/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php +++ b/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php @@ -40,13 +40,13 @@ class CssCollectionOptimizer implements AssetCollectionOptimizerInterface { /** * Constructs a CssCollectionOptimizer. * - * @param \Drupal\Core\Asset\AssetCollectionGrouperInterface + * @param \Drupal\Core\Asset\AssetCollectionGrouperInterface $grouper * The grouper for CSS assets. - * @param \Drupal\Core\Asset\AssetOptimizerInterface + * @param \Drupal\Core\Asset\AssetOptimizerInterface $optimizer * The optimizer for a single CSS asset. - * @param \Drupal\Core\Asset\AssetDumperInterface + * @param \Drupal\Core\Asset\AssetDumperInterface $dumper * The dumper for optimized CSS assets. - * @param \Drupal\Core\State\StateInterface + * @param \Drupal\Core\State\StateInterface $state * The state key/value store. */ public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptimizerInterface $optimizer, AssetDumperInterface $dumper, StateInterface $state) { diff --git a/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php b/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php index 869abe3..63721e6 100644 --- a/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php +++ b/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php @@ -63,7 +63,7 @@ class CssCollectionRenderer implements AssetCollectionRendererInterface { /** * Constructs a CssCollectionRenderer. * - * @param \Drupal\Core\State\StateInterface + * @param \Drupal\Core\State\StateInterface $state * The state key/value store. */ public function __construct(StateInterface $state) { diff --git a/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php b/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php index aef10bf..566722d 100644 --- a/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php +++ b/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php @@ -41,13 +41,13 @@ class JsCollectionOptimizer implements AssetCollectionOptimizerInterface { /** * Constructs a JsCollectionOptimizer. * - * @param \Drupal\Core\Asset\AssetCollectionGrouperInterface + * @param \Drupal\Core\Asset\AssetCollectionGrouperInterface $grouper * The grouper for JS assets. - * @param \Drupal\Core\Asset\AssetOptimizerInterface + * @param \Drupal\Core\Asset\AssetOptimizerInterface $optimizer * The optimizer for a single JS asset. - * @param \Drupal\Core\Asset\AssetDumperInterface + * @param \Drupal\Core\Asset\AssetDumperInterface $dumper * The dumper for optimized JS assets. - * @param \Drupal\Core\State\StateInterface + * @param \Drupal\Core\State\StateInterface $state * The state key/value store. */ public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptimizerInterface $optimizer, AssetDumperInterface $dumper, StateInterface $state) { diff --git a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php index c5f0a2b..2e47d0f 100644 --- a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php +++ b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php @@ -20,7 +20,7 @@ class JsCollectionRenderer implements AssetCollectionRendererInterface { /** * Constructs a JsCollectionRenderer. * - * @param \Drupal\Core\State\StateInterface + * @param \Drupal\Core\State\StateInterface $state * The state key/value store. */ public function __construct(StateInterface $state) { diff --git a/core/lib/Drupal/Core/Asset/LibraryDiscoveryInterface.php b/core/lib/Drupal/Core/Asset/LibraryDiscoveryInterface.php index c8d18f0..0f4e704 100644 --- a/core/lib/Drupal/Core/Asset/LibraryDiscoveryInterface.php +++ b/core/lib/Drupal/Core/Asset/LibraryDiscoveryInterface.php @@ -39,7 +39,7 @@ public function getLibrariesByExtension($extension); * @param string $name * The name of a registered library to retrieve. * - * @return array|FALSE + * @return array|false * The definition of the requested library, if $name was passed and it * exists, otherwise FALSE. */ diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationCollectorInterface.php b/core/lib/Drupal/Core/Authentication/AuthenticationCollectorInterface.php index 3d869e5..37f245c 100644 --- a/core/lib/Drupal/Core/Authentication/AuthenticationCollectorInterface.php +++ b/core/lib/Drupal/Core/Authentication/AuthenticationCollectorInterface.php @@ -41,7 +41,7 @@ public function isGlobal($provider_id); * @param string $provider_id * The provider ID. * - * @return \Drupal\Core\Authentication\AuthenticationProviderInterface|NULL + * @return \Drupal\Core\Authentication\AuthenticationProviderInterface|null * The authentication provider which matches the ID. */ public function getProvider($provider_id); diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationManager.php b/core/lib/Drupal/Core/Authentication/AuthenticationManager.php index 10b0edc..1049c92 100644 --- a/core/lib/Drupal/Core/Authentication/AuthenticationManager.php +++ b/core/lib/Drupal/Core/Authentication/AuthenticationManager.php @@ -94,7 +94,7 @@ public function challengeException(Request $request, \Exception $previous) { * @param \Symfony\Component\HttpFoundation\Request $request * The incoming request. * - * @return string|NULL + * @return string|null * The id of the first authentication provider which applies to the request. * If no application detects appropriate credentials, then NULL is returned. */ @@ -112,7 +112,7 @@ protected function getProvider(Request $request) { * @param \Symfony\Component\HttpFoundation\Request $request * The incoming request. * - * @return string|NULL + * @return string|null * The ID of the first authentication provider which applies to the request. * If no application detects appropriate credentials, then NULL is returned. */ diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationProviderChallengeInterface.php b/core/lib/Drupal/Core/Authentication/AuthenticationProviderChallengeInterface.php index 4721050..db86cc1 100644 --- a/core/lib/Drupal/Core/Authentication/AuthenticationProviderChallengeInterface.php +++ b/core/lib/Drupal/Core/Authentication/AuthenticationProviderChallengeInterface.php @@ -21,7 +21,7 @@ * @param \Exception $previous * The previous exception. * - * @return \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface|NULL + * @return \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface|null * An exception to be used in order to generate an authentication challenge. */ public function challengeException(Request $request, \Exception $previous); diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationProviderInterface.php b/core/lib/Drupal/Core/Authentication/AuthenticationProviderInterface.php index 7ef18df..02f0497 100644 --- a/core/lib/Drupal/Core/Authentication/AuthenticationProviderInterface.php +++ b/core/lib/Drupal/Core/Authentication/AuthenticationProviderInterface.php @@ -27,7 +27,7 @@ public function applies(Request $request); * @param \Symfony\Component\HttpFoundation\Request|null $request * The request object. * - * @return \Drupal\Core\Session\AccountInterface|NULL + * @return \Drupal\Core\Session\AccountInterface|null * AccountInterface - in case of a successful authentication. * NULL - in case where authentication failed. */ diff --git a/core/lib/Drupal/Core/Composer/Composer.php b/core/lib/Drupal/Core/Composer/Composer.php index 51a4917..263be4c 100644 --- a/core/lib/Drupal/Core/Composer/Composer.php +++ b/core/lib/Drupal/Core/Composer/Composer.php @@ -196,7 +196,7 @@ public static function vendorTestCodeCleanup(PackageEvent $event) { * @param string $package_name * The package name from composer. This is always already lower case. * - * @return NULL|string + * @return string|null * The string key, or NULL if none was found. */ protected static function findPackageKey($package_name) { diff --git a/core/lib/Drupal/Core/Config/ConfigCollectionInfo.php b/core/lib/Drupal/Core/Config/ConfigCollectionInfo.php index ca526cd..483c96d 100644 --- a/core/lib/Drupal/Core/Config/ConfigCollectionInfo.php +++ b/core/lib/Drupal/Core/Config/ConfigCollectionInfo.php @@ -24,7 +24,7 @@ class ConfigCollectionInfo extends Event { * * @param string $collection * Collection name to add. - * @param \Drupal\Core\Config\ConfigFactoryOverrideInterface + * @param \Drupal\Core\Config\ConfigFactoryOverrideInterface $override_service * (optional) The configuration factory override service responsible for the * collection. * @@ -63,7 +63,7 @@ public function getCollectionNames($include_default = TRUE) { * @param string $collection * The configuration collection. * - * @return \Drupal\Core\Config\ConfigFactoryOverrideInterface|NULL + * @return \Drupal\Core\Config\ConfigFactoryOverrideInterface|null * The override service responsible for the collection if one exists. NULL * if not. */ diff --git a/core/lib/Drupal/Core/Config/ConfigCrudEvent.php b/core/lib/Drupal/Core/Config/ConfigCrudEvent.php index af25d36..3172e8e 100644 --- a/core/lib/Drupal/Core/Config/ConfigCrudEvent.php +++ b/core/lib/Drupal/Core/Config/ConfigCrudEvent.php @@ -19,7 +19,7 @@ class ConfigCrudEvent extends Event { /** * Constructs a configuration event object. * - * @param \Drupal\Core\Config\Config + * @param \Drupal\Core\Config\Config $config * Configuration object. */ public function __construct(Config $config) { diff --git a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php index ba71812..3d7077b 100644 --- a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php +++ b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php @@ -40,7 +40,7 @@ public function installDefaultConfig($type, $name); * - it's a configuration entity. * - its dependencies can be met. * - * @param \Drupal\Core\Config\StorageInterface + * @param \Drupal\Core\Config\StorageInterface $storage * (optional) The configuration storage to search for optional * configuration. If not provided, all enabled extension's optional * configuration directories will be searched. diff --git a/core/lib/Drupal/Core/Config/ConfigModuleOverridesEvent.php b/core/lib/Drupal/Core/Config/ConfigModuleOverridesEvent.php index a8a42c5..be5be93 100644 --- a/core/lib/Drupal/Core/Config/ConfigModuleOverridesEvent.php +++ b/core/lib/Drupal/Core/Config/ConfigModuleOverridesEvent.php @@ -37,7 +37,7 @@ class ConfigModuleOverridesEvent extends Event { * * @param array $names * A list of configuration names. - * @param \Drupal\Core\Language\LanguageInterface + * @param \Drupal\Core\Language\LanguageInterface $language * (optional) The language for this configuration. */ public function __construct(array $names, LanguageInterface $language = NULL) { diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityTypeInterface.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityTypeInterface.php index 2cb1434..589a63d 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityTypeInterface.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityTypeInterface.php @@ -65,7 +65,7 @@ public function getConfigPrefix(); /** * Gets the config entity properties to export if declared on the annotation. * - * @return array|NULL + * @return array|null * The properties to export or NULL if they can not be determine from the * config entity type annotation. */ diff --git a/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php index 4396a39..0327e90 100644 --- a/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php +++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php @@ -66,8 +66,8 @@ public function get(EntityTypeInterface $entity_type, $conjunction) { /** * {@inheritdoc} */ - public function getAggregate(EntityTypeInterface $entity_type, $conjunction) { - throw new QueryException('Aggregation over configuration entities is not supported'); + public function getAggregate(EntityTypeInterface $entity_type, $conjunction) { + throw new QueryException('Aggregation over configuration entities is not supported'); } /** @@ -189,7 +189,7 @@ protected function getKeys(Config $config, $key, $get_method, ConfigEntityTypeIn * @param int $start * Which position of $parts we are processing. Defaults to 0. * - * @return array|NULL + * @return array|null * The array of configuration values the match the provided key. NULL if * the configuration object does not have a value that corresponds to the * key. diff --git a/core/lib/Drupal/Core/Cron.php b/core/lib/Drupal/Core/Cron.php index 8662d68..74d2866 100644 --- a/core/lib/Drupal/Core/Cron.php +++ b/core/lib/Drupal/Core/Cron.php @@ -83,7 +83,7 @@ class Cron implements CronInterface { * The account switching service. * @param \Psr\Log\LoggerInterface $logger * A logger instance. - * @param \Drupal\Core\Queue\QueueWorkerManagerInterface + * @param \Drupal\Core\Queue\QueueWorkerManagerInterface $queue_manager * The queue plugin manager. */ public function __construct(ModuleHandlerInterface $module_handler, LockBackendInterface $lock, QueueFactory $queue_factory, StateInterface $state, AccountSwitcherInterface $account_switcher, LoggerInterface $logger, QueueWorkerManagerInterface $queue_manager) { diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php index 95ff527..7e78b14 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php @@ -65,7 +65,7 @@ protected function connect() { $this->pass('Drupal can CONNECT to the database ok.'); } catch (\Exception $e) { - // Attempt to create the database if it is not found. + // Attempt to create the database if it is not found. if ($e->getCode() == Connection::DATABASE_NOT_FOUND) { // Remove the database string from connection info. $connection_info = Database::getConnectionInfo(); diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php index c0775c9..72f35da 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php @@ -23,7 +23,7 @@ class Schema extends DatabaseSchema { * This is collected by DatabaseConnection_pgsql->queryTableInformation(), * by introspecting the database. * - * @see DatabaseConnection_pgsql->queryTableInformation() + * @see \Drupal\Core\Database\Driver\pgsql\Schema::queryTableInformation() * @var array */ protected $tableInformation = array(); diff --git a/core/lib/Drupal/Core/Database/StatementInterface.php b/core/lib/Drupal/Core/Database/StatementInterface.php index 5bc9e48..971d031 100644 --- a/core/lib/Drupal/Core/Database/StatementInterface.php +++ b/core/lib/Drupal/Core/Database/StatementInterface.php @@ -90,7 +90,7 @@ public function rowCount(); * If $mode is PDO::FETCH_CLASS, the optional arguments to pass to the * constructor. */ - public function setFetchMode($mode, $a1 = NULL, $a2 = array()); + public function setFetchMode($mode, $a1 = NULL, $a2 = array()); /** * Fetches the next row from a result set. @@ -109,7 +109,7 @@ public function setFetchMode($mode, $a1 = NULL, $a2 = array()); * @return * A result, formatted according to $mode. */ - public function fetch($mode = NULL, $cursor_orientation = NULL, $cursor_offset = NULL); + public function fetch($mode = NULL, $cursor_orientation = NULL, $cursor_offset = NULL); /** * Returns a single field from the next record of a result set. @@ -128,7 +128,7 @@ public function fetchField($index = 0); * The object will be of the class specified by StatementInterface::setFetchMode() * or stdClass if not specified. */ - public function fetchObject(); + public function fetchObject(); /** * Fetches the next row and returns it as an associative array. @@ -155,7 +155,7 @@ public function fetchAssoc(); * @return * An array of results. */ - function fetchAll($mode = NULL, $column_index = NULL, $constructor_arguments = NULL); + function fetchAll($mode = NULL, $column_index = NULL, $constructor_arguments = NULL); /** * Returns an entire single column of a result set as an indexed array. diff --git a/core/lib/Drupal/Core/DependencyInjection/Container.php b/core/lib/Drupal/Core/DependencyInjection/Container.php index 157f576..4a49122 100644 --- a/core/lib/Drupal/Core/DependencyInjection/Container.php +++ b/core/lib/Drupal/Core/DependencyInjection/Container.php @@ -14,7 +14,7 @@ class Container extends DrupalContainer { * {@inheritdoc} */ public function set($id, $service, $scope = ContainerInterface::SCOPE_CONTAINER) { - parent::set($id, $service, $scope); + parent::set($id, $service, $scope); // Ensure that the _serviceId property is set on synthetic services as well. if (isset($this->services[$id]) && is_object($this->services[$id]) && !isset($this->services[$id]->_serviceId)) { diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php index 4f7e9c4..862fbec 100644 --- a/core/lib/Drupal/Core/DrupalKernelInterface.php +++ b/core/lib/Drupal/Core/DrupalKernelInterface.php @@ -58,7 +58,7 @@ public function getContainer(); * * This also allows inspecting a built container for debugging purposes. * - * @return array|NULL + * @return array|null * The cached container definition or NULL if not found in cache. */ public function getCachedContainerDefinition(); diff --git a/core/lib/Drupal/Core/Entity/Controller/EntityController.php b/core/lib/Drupal/Core/Entity/Controller/EntityController.php index ebee7cc..4ca97e4 100644 --- a/core/lib/Drupal/Core/Entity/Controller/EntityController.php +++ b/core/lib/Drupal/Core/Entity/Controller/EntityController.php @@ -273,7 +273,7 @@ public function deleteTitle(RouteMatchInterface $route_match, EntityInterface $_ * (optional) The entity, set in * \Drupal\Core\Entity\Enhancer\EntityRouteEnhancer. * - * @return \Drupal\Core\Entity\EntityInterface|NULL + * @return \Drupal\Core\Entity\EntityInterface|null * The entity, if it is passed in directly or if the first parameter of the * active route is an entity; otherwise, NULL. */ diff --git a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php index 23fd0b5..51adf91 100644 --- a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php +++ b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php @@ -297,7 +297,7 @@ protected static function matchEntityByTitle(SelectionInterface $handler, $input $multiples[] = $name . ' (' . $id . ')'; } $params['@id'] = $id; - $form_state->setError($element, t('Multiple entities match this reference; "%multiple". Specify the one you want by appending the id in parentheses, like "@value (@id)".', array('%multiple' => implode('", "', $multiples)))); + $form_state->setError($element, t('Multiple entities match this reference; "%multiple". Specify the one you want by appending the id in parentheses, like "@value (@id)".', array('%multiple' => implode('", "', $multiples)) + $params)); } else { // Take the one and only matching entity. diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ReferenceAccessConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ReferenceAccessConstraintValidator.php index ab8d64c..c574781 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ReferenceAccessConstraintValidator.php +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ReferenceAccessConstraintValidator.php @@ -33,10 +33,10 @@ public function validate($value, Constraint $constraint) { $referenced_entities = $existing_entity->{$value->getFieldDefinition()->getName()}->referencedEntities(); // Check permission if we are not already referencing the entity. foreach ($referenced_entities as $ref) { - if (isset($referenced_entities[$ref->id()])) { - $check_permission = FALSE; - break; - } + if (isset($referenced_entities[$ref->id()])) { + $check_permission = FALSE; + break; + } } } // We check that the current user had access to view any newly added diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php index 2d5df2d..78f831a 100644 --- a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php +++ b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php @@ -225,7 +225,7 @@ protected function finish() { if ($this->range) { $this->sqlQuery->range($this->range['start'], $this->range['length']); } - foreach ($this->sqlGroupBy as $field) { + foreach ($this->sqlGroupBy as $field) { $this->sqlQuery->groupBy($field); } foreach ($this->sqlFields as $field) { diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php index e115bd2..2ea450d 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php @@ -526,7 +526,7 @@ protected function loadFromSharedTables(array &$values, array &$translations) { $all_fields = $revisioned_fields; if ($data_fields) { $all_fields = array_merge($revisioned_fields, $data_fields); - $query->leftJoin($this->dataTable, 'data', "(revision.$this->idKey = data.$this->idKey)"); + $query->leftJoin($this->dataTable, 'data', "(revision.$this->idKey = data.$this->idKey and revision.$this->langcodeKey = data.$this->langcodeKey)"); $column_names = []; // Some fields can have more then one columns in the data table so // column names are needed. @@ -1543,6 +1543,7 @@ protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definit $item_query = $this->database->select($table_name, 't', array('fetch' => \PDO::FETCH_ASSOC)) ->fields('t') ->condition('entity_id', $row['entity_id']) + ->condition('deleted', 1) ->orderBy('delta'); foreach ($item_query->execute() as $item_row) { @@ -1581,10 +1582,12 @@ protected function purgeFieldItems(ContentEntityInterface $entity, FieldDefiniti $revision_id = $this->entityType->isRevisionable() ? $entity->getRevisionId() : $entity->id(); $this->database->delete($table_name) ->condition('revision_id', $revision_id) + ->condition('deleted', 1) ->execute(); if ($this->entityType->isRevisionable()) { $this->database->delete($revision_name) ->condition('revision_id', $revision_id) + ->condition('deleted', 1) ->execute(); } } @@ -1684,6 +1687,12 @@ public function countFieldData($storage_definition, $as_bool = FALSE) { * Whether the field has been already deleted. */ protected function storageDefinitionIsDeleted(FieldStorageDefinitionInterface $storage_definition) { + // Configurable fields are marked for deletion. + if ($storage_definition instanceOf FieldStorageConfigInterface) { + return $storage_definition->isDeleted(); + } + // For non configurable fields check whether they are still in the last + // installed schema repository. return !array_key_exists($storage_definition->getName(), $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityTypeId)); } diff --git a/core/lib/Drupal/Core/Entity/entity.api.php b/core/lib/Drupal/Core/Entity/entity.api.php index fe2e008..60ae741 100644 --- a/core/lib/Drupal/Core/Entity/entity.api.php +++ b/core/lib/Drupal/Core/Entity/entity.api.php @@ -893,9 +893,9 @@ function hook_ENTITY_TYPE_storage_load(array $entities) { * @see hook_ENTITY_TYPE_presave() */ function hook_entity_presave(Drupal\Core\Entity\EntityInterface $entity) { - if ($entity instanceof ContentEntityInterface && $entity->isTranslatable()) { - $route_match = \Drupal::routeMatch(); - \Drupal::service('content_translation.synchronizer')->synchronizeFields($entity, $entity->language()->getId(), $route_match->getParameter('source_langcode')); + if ($entity instanceof ContentEntityInterface && $entity->isTranslatable()) { + $route_match = \Drupal::routeMatch(); + \Drupal::service('content_translation.synchronizer')->synchronizeFields($entity, $entity->language()->getId(), $route_match->getParameter('source_langcode')); } } @@ -1845,7 +1845,8 @@ function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\Ent * * @param string $operation * The operation to be performed. See - * \Drupal\Core\Access\AccessibleInterface::access() for possible values. + * \Drupal\Core\Entity\EntityAccessControlHandlerInterface::fieldAccess() + * for possible values. * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition * The field definition. * @param \Drupal\Core\Session\AccountInterface $account @@ -1856,6 +1857,8 @@ function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\Ent * * @return \Drupal\Core\Access\AccessResultInterface * The access result. + * + * @see \Drupal\Core\Entity\EntityAccessControlHandlerInterface::fieldAccess() */ function hook_entity_field_access($operation, \Drupal\Core\Field\FieldDefinitionInterface $field_definition, \Drupal\Core\Session\AccountInterface $account, \Drupal\Core\Field\FieldItemListInterface $items = NULL) { if ($field_definition->getName() == 'field_of_interest' && $operation == 'edit') { diff --git a/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php index 4f44409..62f5486 100644 --- a/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php @@ -30,14 +30,14 @@ class AuthenticationSubscriber implements EventSubscriberInterface { /** * Authentication provider filter. * - * @var \Drupal\Core\Authentication\AuthenticationProviderFilterInterface|NULL + * @var \Drupal\Core\Authentication\AuthenticationProviderFilterInterface|null */ protected $filter; /** * Authentication challenge provider. * - * @var \Drupal\Core\Authentication\AuthenticationProviderChallengeInterface|NULL + * @var \Drupal\Core\Authentication\AuthenticationProviderChallengeInterface|null */ protected $challengeProvider; diff --git a/core/lib/Drupal/Core/EventSubscriber/EntityRouteAlterSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/EntityRouteAlterSubscriber.php index 8337d43..3c37195 100644 --- a/core/lib/Drupal/Core/EventSubscriber/EntityRouteAlterSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/EntityRouteAlterSubscriber.php @@ -30,7 +30,7 @@ class EntityRouteAlterSubscriber implements EventSubscriberInterface { /** * Constructs an EntityRouteAlterSubscriber instance. * - * @param \Drupal\Core\Entity\EntityResolverManager + * @param \Drupal\Core\Entity\EntityResolverManager $entity_resolver_manager * The entity resolver manager. */ public function __construct(EntityResolverManager $entity_resolver_manager) { diff --git a/core/lib/Drupal/Core/Field/AllowedTagsXssTrait.php b/core/lib/Drupal/Core/Field/AllowedTagsXssTrait.php index 4b6d710..427d7f4 100644 --- a/core/lib/Drupal/Core/Field/AllowedTagsXssTrait.php +++ b/core/lib/Drupal/Core/Field/AllowedTagsXssTrait.php @@ -30,7 +30,7 @@ * valid UTF-8. */ public function fieldFilterXss($string) { - return FieldFilteredMarkup::create($string); + return FieldFilteredMarkup::create($string); } /** diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php index ebdb276..97dd493 100644 --- a/core/lib/Drupal/Core/Field/FieldItemList.php +++ b/core/lib/Drupal/Core/Field/FieldItemList.php @@ -257,7 +257,7 @@ public function view($display_options = array()) { /** * {@inheritdoc} */ - public function generateSampleItems($count = 1) { + public function generateSampleItems($count = 1) { $field_definition = $this->getFieldDefinition(); $field_type_class = \Drupal::service('plugin.manager.field.field_type')->getPluginClass($field_definition->getType()); for ($delta = 0; $delta < $count; $delta++) { diff --git a/core/lib/Drupal/Core/Field/FieldItemListInterface.php b/core/lib/Drupal/Core/Field/FieldItemListInterface.php index 2266c9c..f901cb7 100644 --- a/core/lib/Drupal/Core/Field/FieldItemListInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemListInterface.php @@ -245,7 +245,7 @@ public function defaultValuesFormSubmit(array $element, array &$form, FormStateI * in order to be a valid runtime value for the field type; e.g., a date field * could process the defined value of 'NOW' to a valid date. * - * @param array + * @param array $default_value * The unprocessed default value defined for the field, as a numerically * indexed array of items, each item being an array of property/value pairs. * @param \Drupal\Core\Entity\FieldableEntityInterface $entity diff --git a/core/lib/Drupal/Core/Field/FieldTypePluginManager.php b/core/lib/Drupal/Core/Field/FieldTypePluginManager.php index 09c6a12..84bb4c0 100644 --- a/core/lib/Drupal/Core/Field/FieldTypePluginManager.php +++ b/core/lib/Drupal/Core/Field/FieldTypePluginManager.php @@ -34,7 +34,7 @@ class FieldTypePluginManager extends DefaultPluginManager implements FieldTypePl * keyed by the corresponding namespace to look for plugin implementations. * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend * Cache backend instance to use. - * @param \Drupal\Core\Extension\ModuleHandlerInterface + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager * The typed data manager. diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php index e8832f3..398d260 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php @@ -617,7 +617,7 @@ public static function settingsAjaxSubmit($form, FormStateInterface $form_state) $form_state->setRebuild(); } - /** + /** * {@inheritdoc} */ public static function getPreconfiguredOptions() { diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php index add9cd7..ca00bcc 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php @@ -190,7 +190,7 @@ protected function sanitizeLabel(&$label) { /** * Returns the empty option label to add to the list of options, if any. * - * @return string|NULL + * @return string|null * Either a label of the empty option, or NULL. */ protected function getEmptyLabel() { } diff --git a/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php b/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php index 72df780..85d0261 100644 --- a/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php +++ b/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php @@ -31,7 +31,7 @@ class MimeTypeGuesser implements MimeTypeGuesserInterface { */ protected $sortedGuessers = NULL; - /** + /** * The stream wrapper manager. * * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface diff --git a/core/lib/Drupal/Core/Form/EnforcedResponse.php b/core/lib/Drupal/Core/Form/EnforcedResponse.php index c28033f..2b1466d 100644 --- a/core/lib/Drupal/Core/Form/EnforcedResponse.php +++ b/core/lib/Drupal/Core/Form/EnforcedResponse.php @@ -37,7 +37,7 @@ class EnforcedResponse extends Response { * @param \Exception $e * The exception where the enforced response is to be extracted from. * - * @return \Drupal\Core\Form\EnforcedResponse|NULL + * @return \Drupal\Core\Form\EnforcedResponse|null * The enforced response or NULL if the exception chain does not contain a * \Drupal\Core\Form\EnforcedResponseException exception. */ diff --git a/core/lib/Drupal/Core/Form/FormState.php b/core/lib/Drupal/Core/Form/FormState.php index ca7355d..33ca1fa 100644 --- a/core/lib/Drupal/Core/Form/FormState.php +++ b/core/lib/Drupal/Core/Form/FormState.php @@ -648,7 +648,7 @@ public function disableRedirect($no_redirect = TRUE) { * {@inheritdoc} */ public function isRedirectDisabled() { - return $this->no_redirect; + return $this->no_redirect; } /** diff --git a/core/lib/Drupal/Core/Form/FormStateInterface.php b/core/lib/Drupal/Core/Form/FormStateInterface.php index 26485a0..b1e3f3b 100644 --- a/core/lib/Drupal/Core/Form/FormStateInterface.php +++ b/core/lib/Drupal/Core/Form/FormStateInterface.php @@ -780,7 +780,7 @@ public function setRequestMethod($method); /** * Returns the HTTP form method. * - * @param string + * @param string $method_type * The HTTP form method. * * @return bool diff --git a/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationBase.php b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationBase.php index af1b2c9..2a1e61e 100644 --- a/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationBase.php +++ b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationBase.php @@ -123,7 +123,7 @@ protected function prepareArguments(array $arguments) { // Use the default value if the argument is not passed in. if (!array_key_exists($id, $arguments)) { - $arguments[$id] = $argument['default']; + $arguments[$id] = $argument['default']; } } } diff --git a/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php b/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php index c6a3840..f5d1c7f 100644 --- a/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php +++ b/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php @@ -37,7 +37,7 @@ class SiteSettingsForm extends FormBase { public function __construct($site_path, RendererInterface $renderer) { $this->sitePath = $site_path; $this->renderer = $renderer; -} + } /** * {@inheritdoc} diff --git a/core/lib/Drupal/Core/Language/ContextProvider/CurrentLanguageContext.php b/core/lib/Drupal/Core/Language/ContextProvider/CurrentLanguageContext.php index b8fe273..3825033 100644 --- a/core/lib/Drupal/Core/Language/ContextProvider/CurrentLanguageContext.php +++ b/core/lib/Drupal/Core/Language/ContextProvider/CurrentLanguageContext.php @@ -26,7 +26,7 @@ class CurrentLanguageContext implements ContextProviderInterface { /** * Constructs a new CurrentLanguageContext. * - * @param \Drupal\Core\Language\LanguageManagerInterface + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. */ public function __construct(LanguageManagerInterface $language_manager) { diff --git a/core/lib/Drupal/Core/Mail/MailFormatHelper.php b/core/lib/Drupal/Core/Mail/MailFormatHelper.php index 080b326..3dffe83 100644 --- a/core/lib/Drupal/Core/Mail/MailFormatHelper.php +++ b/core/lib/Drupal/Core/Mail/MailFormatHelper.php @@ -202,8 +202,7 @@ public static function htmlToText($string, $allowed_tags = NULL) { // Ensure blank new-line. $chunk = ''; } - - // Fall-through. + // Intentional fall-through to the processing for '/li' and '/dd'. case '/li': case '/dd': array_pop($indent); @@ -212,6 +211,7 @@ public static function htmlToText($string, $allowed_tags = NULL) { case '/h3': case '/h4': array_pop($indent); + // Intentional fall-through to the processing for '/h5' and '/h6'. case '/h5': case '/h6': // Ensure blank new-line. diff --git a/core/lib/Drupal/Core/Mail/MailInterface.php b/core/lib/Drupal/Core/Mail/MailInterface.php index 1435ff9..fbde1b8 100644 --- a/core/lib/Drupal/Core/Mail/MailInterface.php +++ b/core/lib/Drupal/Core/Mail/MailInterface.php @@ -30,7 +30,7 @@ * * @see \Drupal\Core\Mail\MailManagerInterface */ - public function format(array $message); + public function format(array $message); /** * Sends a message composed by \Drupal\Core\Mail\MailManagerInterface->mail(). @@ -60,6 +60,6 @@ public function format(array $message); * @return bool * TRUE if the mail was successfully accepted for delivery, otherwise FALSE. */ - public function mail(array $message); + public function mail(array $message); } diff --git a/core/lib/Drupal/Core/Menu/ContextualLinkManager.php b/core/lib/Drupal/Core/Menu/ContextualLinkManager.php index 29803ad..e264036 100644 --- a/core/lib/Drupal/Core/Menu/ContextualLinkManager.php +++ b/core/lib/Drupal/Core/Menu/ContextualLinkManager.php @@ -126,11 +126,11 @@ protected function getDiscovery() { public function processDefinition(&$definition, $plugin_id) { parent::processDefinition($definition, $plugin_id); - // If there is no route name, this is a broken definition. + // If there is no route name, this is a broken definition. if (empty($definition['route_name'])) { throw new PluginException(sprintf('Contextual link plugin (%s) definition must include "route_name".', $plugin_id)); } - // If there is no group name, this is a broken definition. + // If there is no group name, this is a broken definition. if (empty($definition['group'])) { throw new PluginException(sprintf('Contextual link plugin (%s) definition must include "group".', $plugin_id)); } diff --git a/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php b/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php index 43c4468..7159083 100644 --- a/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php +++ b/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php @@ -37,7 +37,7 @@ class DefaultMenuLinkTreeManipulators { * * @var \Drupal\Core\Entity\Query\QueryFactory */ - protected $queryFactory; + protected $queryFactory; /** * Constructs a \Drupal\Core\Menu\DefaultMenuLinkTreeManipulators object. diff --git a/core/lib/Drupal/Core/Menu/LocalActionDefault.php b/core/lib/Drupal/Core/Menu/LocalActionDefault.php index faa20a8..bfa088e 100644 --- a/core/lib/Drupal/Core/Menu/LocalActionDefault.php +++ b/core/lib/Drupal/Core/Menu/LocalActionDefault.php @@ -24,7 +24,7 @@ class LocalActionDefault extends PluginBase implements LocalActionInterface, Con * * @var \Drupal\Core\Routing\RouteProviderInterface */ - protected $routeProvider; + protected $routeProvider; /** * Constructs a LocalActionDefault object. diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManager.php b/core/lib/Drupal/Core/Menu/LocalTaskManager.php index ec1df5c..26b2ed5 100644 --- a/core/lib/Drupal/Core/Menu/LocalTaskManager.php +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php @@ -158,7 +158,7 @@ protected function getDiscovery() { */ public function processDefinition(&$definition, $plugin_id) { parent::processDefinition($definition, $plugin_id); - // If there is no route name, this is a broken definition. + // If there is no route name, this is a broken definition. if (empty($definition['route_name'])) { throw new PluginException(sprintf('Plugin (%s) definition must include "route_name"', $plugin_id)); } diff --git a/core/lib/Drupal/Core/Menu/MenuActiveTrailInterface.php b/core/lib/Drupal/Core/Menu/MenuActiveTrailInterface.php index ddcd39d..8410e5a 100644 --- a/core/lib/Drupal/Core/Menu/MenuActiveTrailInterface.php +++ b/core/lib/Drupal/Core/Menu/MenuActiveTrailInterface.php @@ -29,7 +29,7 @@ public function getActiveTrailIds($menu_name); * (optional) The menu within which to find the active link. If omitted, all * menus will be searched. * - * @return \Drupal\Core\Menu\MenuLinkInterface|NULL + * @return \Drupal\Core\Menu\MenuLinkInterface|null * The menu link for the given route name, parameters and menu, or NULL if * there is no matching menu link or the current user cannot access the * current page (i.e. we have a 403 response). diff --git a/core/lib/Drupal/Core/Menu/MenuLinkTreeElement.php b/core/lib/Drupal/Core/Menu/MenuLinkTreeElement.php index 1327c78..e7e12c4 100644 --- a/core/lib/Drupal/Core/Menu/MenuLinkTreeElement.php +++ b/core/lib/Drupal/Core/Menu/MenuLinkTreeElement.php @@ -68,7 +68,7 @@ class MenuLinkTreeElement { * If the value is NULL the access was not determined yet, if an access result * object, it was determined already. * - * @var \Drupal\Core\Access\AccessResultInterface|NULL + * @var \Drupal\Core\Access\AccessResultInterface|null */ public $access; diff --git a/core/lib/Drupal/Core/Menu/MenuTreeStorageInterface.php b/core/lib/Drupal/Core/Menu/MenuTreeStorageInterface.php index 4ee308b..0a580d6 100644 --- a/core/lib/Drupal/Core/Menu/MenuTreeStorageInterface.php +++ b/core/lib/Drupal/Core/Menu/MenuTreeStorageInterface.php @@ -41,7 +41,7 @@ public function rebuild(array $definitions); * @param string $id * The menu link plugin ID. * - * @return array|FALSE + * @return array|false * The plugin definition, or FALSE if no definition was found for the ID. */ public function load($id); diff --git a/core/lib/Drupal/Core/Menu/StaticMenuLinkOverridesInterface.php b/core/lib/Drupal/Core/Menu/StaticMenuLinkOverridesInterface.php index a833a79..1a5c41d 100644 --- a/core/lib/Drupal/Core/Menu/StaticMenuLinkOverridesInterface.php +++ b/core/lib/Drupal/Core/Menu/StaticMenuLinkOverridesInterface.php @@ -21,7 +21,7 @@ public function reload(); * @param string $id * A menu link plugin ID. * - * @return array|NULL + * @return array|null * An override with following supported keys: * - parent * - weight diff --git a/core/lib/Drupal/Core/PageCache/RequestPolicyInterface.php b/core/lib/Drupal/Core/PageCache/RequestPolicyInterface.php index 9bd24a8..02b20cb 100644 --- a/core/lib/Drupal/Core/PageCache/RequestPolicyInterface.php +++ b/core/lib/Drupal/Core/PageCache/RequestPolicyInterface.php @@ -39,7 +39,7 @@ * @param \Symfony\Component\HttpFoundation\Request $request * The incoming request object. * - * @return string|NULL + * @return string|null * One of static::ALLOW, static::DENY or NULL. Calling code may attempt to * deliver a cached page if static::ALLOW is returned. Returns NULL if the * policy is not specified for the given request. diff --git a/core/lib/Drupal/Core/PageCache/ResponsePolicyInterface.php b/core/lib/Drupal/Core/PageCache/ResponsePolicyInterface.php index a82427b..0d3f965 100644 --- a/core/lib/Drupal/Core/PageCache/ResponsePolicyInterface.php +++ b/core/lib/Drupal/Core/PageCache/ResponsePolicyInterface.php @@ -27,7 +27,7 @@ * @param \Symfony\Component\HttpFoundation\Request $request * The request object. * - * @return string|NULL + * @return string|null * Either static::DENY or NULL. Calling code may attempt to store a page in * the cache unless static::DENY is returned. Returns NULL if the policy * policy is not specified for the given response. diff --git a/core/lib/Drupal/Core/Path/AliasManager.php b/core/lib/Drupal/Core/Path/AliasManager.php index 0368d43..3a31917 100644 --- a/core/lib/Drupal/Core/Path/AliasManager.php +++ b/core/lib/Drupal/Core/Path/AliasManager.php @@ -283,7 +283,7 @@ protected function pathAliasWhitelistRebuild($path = NULL) { if (!empty($path)) { if ($this->whitelist->get(strtok($path, '/'))) { return; - } + } } $this->whitelist->clear(); } diff --git a/core/lib/Drupal/Core/Plugin/Context/Context.php b/core/lib/Drupal/Core/Plugin/Context/Context.php index 87fab3a..35eaacd 100644 --- a/core/lib/Drupal/Core/Plugin/Context/Context.php +++ b/core/lib/Drupal/Core/Plugin/Context/Context.php @@ -42,7 +42,7 @@ class Context extends ComponentContext implements ContextInterface { * * @param \Drupal\Core\Plugin\Context\ContextDefinitionInterface $context_definition * The context definition. - * @param mixed $context_value|NULL + * @param mixed $context_value|null * The context value object. */ public function __construct(ContextDefinitionInterface $context_definition, $context_value = NULL) { diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextDefinition.php b/core/lib/Drupal/Core/Plugin/Context/ContextDefinition.php index 37f7bbc..71cfaa1 100644 --- a/core/lib/Drupal/Core/Plugin/Context/ContextDefinition.php +++ b/core/lib/Drupal/Core/Plugin/Context/ContextDefinition.php @@ -85,7 +85,7 @@ public static function create($data_type = 'any') { * * @param string $data_type * The required data type. - * @param mixed string|null $label + * @param string|null $label * The label of this context definition for the UI. * @param bool $required * Whether the context definition is required. @@ -245,7 +245,7 @@ public function getDataDefinition() { ->setDescription($this->getDescription()) ->setRequired($this->isRequired()); $constraints = $definition->getConstraints() + $this->getConstraints(); - $definition->setConstraints($constraints); + $definition->setConstraints($constraints); return $definition; } diff --git a/core/lib/Drupal/Core/Render/PageDisplayVariantSelectionEvent.php b/core/lib/Drupal/Core/Render/PageDisplayVariantSelectionEvent.php index 852a47e..37ff0f7 100644 --- a/core/lib/Drupal/Core/Render/PageDisplayVariantSelectionEvent.php +++ b/core/lib/Drupal/Core/Render/PageDisplayVariantSelectionEvent.php @@ -54,7 +54,7 @@ class PageDisplayVariantSelectionEvent extends Event implements RefinableCacheab /** * Constructs the page display variant plugin selection event. * - * @param string + * @param string $plugin_id * The ID of the page display variant plugin to use by default. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The current route match, for context. diff --git a/core/lib/Drupal/Core/Render/theme.api.php b/core/lib/Drupal/Core/Render/theme.api.php index 61cafc3..322761e 100644 --- a/core/lib/Drupal/Core/Render/theme.api.php +++ b/core/lib/Drupal/Core/Render/theme.api.php @@ -555,7 +555,7 @@ function hook_form_system_theme_settings_alter(&$form, \Drupal\Core\Form\FormSta * The name of the theme hook. */ function hook_preprocess(&$variables, $hook) { - static $hooks; + static $hooks; // Add contextual links to the variables, if the user has permission. diff --git a/core/lib/Drupal/Core/Routing/StackedRouteMatchInterface.php b/core/lib/Drupal/Core/Routing/StackedRouteMatchInterface.php index 8cc4fb5..45b777c 100644 --- a/core/lib/Drupal/Core/Routing/StackedRouteMatchInterface.php +++ b/core/lib/Drupal/Core/Routing/StackedRouteMatchInterface.php @@ -28,7 +28,7 @@ public function getMasterRouteMatch(); /** * Returns the parent route match of the current. * - * @return \Drupal\Core\Routing\RouteMatchInterface|NULL + * @return \Drupal\Core\Routing\RouteMatchInterface|null * The parent route match or NULL, if it the master route match. */ public function getParentRouteMatch(); @@ -36,10 +36,10 @@ public function getParentRouteMatch(); /** * Returns a route match from a given request, if possible. * - * @param \Symfony\Component\HttpFoundation\Request + * @param \Symfony\Component\HttpFoundation\Request $request * The request. * - * @return \Drupal\Core\Routing\RouteMatchInterface|NULL + * @return \Drupal\Core\Routing\RouteMatchInterface|null * THe matching route match, or NULL if there is no matching one. */ public function getRouteMatchFromRequest(Request $request); diff --git a/core/lib/Drupal/Core/Session/AccountProxy.php b/core/lib/Drupal/Core/Session/AccountProxy.php index a50a289..7aa95ae 100644 --- a/core/lib/Drupal/Core/Session/AccountProxy.php +++ b/core/lib/Drupal/Core/Session/AccountProxy.php @@ -179,7 +179,7 @@ public function setInitialAccountId($account_id) { * @param int $account_id * The id of an account to load. * - * @return \Drupal\Core\Session\AccountInterface|NULL + * @return \Drupal\Core\Session\AccountInterface|null * An account or NULL if none is found. */ protected function loadUserEntity($account_id) { diff --git a/core/lib/Drupal/Core/Session/PermissionsHashGenerator.php b/core/lib/Drupal/Core/Session/PermissionsHashGenerator.php index 767d2b4..e172537 100644 --- a/core/lib/Drupal/Core/Session/PermissionsHashGenerator.php +++ b/core/lib/Drupal/Core/Session/PermissionsHashGenerator.php @@ -40,7 +40,7 @@ class PermissionsHashGenerator implements PermissionsHashGeneratorInterface { * The private key service. * @param \Drupal\Core\Cache\CacheBackendInterface $cache * The cache backend interface to use for the persistent cache. - * @param \Drupal\Core\Cache\CacheBackendInterface + * @param \Drupal\Core\Cache\CacheBackendInterface $static * The cache backend interface to use for the static cache. */ public function __construct(PrivateKey $private_key, CacheBackendInterface $cache, CacheBackendInterface $static) { diff --git a/core/lib/Drupal/Core/Session/SessionConfiguration.php b/core/lib/Drupal/Core/Session/SessionConfiguration.php index 351e57d..49dc63e 100644 --- a/core/lib/Drupal/Core/Session/SessionConfiguration.php +++ b/core/lib/Drupal/Core/Session/SessionConfiguration.php @@ -141,7 +141,7 @@ protected function getCookieDomain(Request $request) { /** * Wraps drupal_valid_test_ua(). * - * @return string|FALSE + * @return string|false * Either the simpletest prefix (the string "simpletest" followed by any * number of digits) or FALSE if the user agent does not contain a valid * HMAC and timestamp. diff --git a/core/lib/Drupal/Core/StreamWrapper/PublicStream.php b/core/lib/Drupal/Core/StreamWrapper/PublicStream.php index 52f05e4..f278353 100644 --- a/core/lib/Drupal/Core/StreamWrapper/PublicStream.php +++ b/core/lib/Drupal/Core/StreamWrapper/PublicStream.php @@ -66,7 +66,7 @@ public function getExternalUrl() { public static function baseUrl() { $settings_base_url = Settings::get('file_public_base_url', ''); if ($settings_base_url) { - return (string) $settings_base_url; + return (string) $settings_base_url; } else { return $GLOBALS['base_url'] . '/' . static::basePath(); diff --git a/core/lib/Drupal/Core/StringTranslation/Translator/TranslatorInterface.php b/core/lib/Drupal/Core/StringTranslation/Translator/TranslatorInterface.php index 367581b..e0fee16 100644 --- a/core/lib/Drupal/Core/StringTranslation/Translator/TranslatorInterface.php +++ b/core/lib/Drupal/Core/StringTranslation/Translator/TranslatorInterface.php @@ -17,7 +17,7 @@ * @param string $context * The string context. * - * @return string|FALSE + * @return string|false * Translated string if there is a translation, FALSE if not. */ public function getStringTranslation($langcode, $string, $context); diff --git a/core/lib/Drupal/Core/Theme/ThemeNegotiator.php b/core/lib/Drupal/Core/Theme/ThemeNegotiator.php index 1d6b589..3064eb0 100644 --- a/core/lib/Drupal/Core/Theme/ThemeNegotiator.php +++ b/core/lib/Drupal/Core/Theme/ThemeNegotiator.php @@ -24,7 +24,7 @@ class ThemeNegotiator implements ThemeNegotiatorInterface { * * Set to NULL if the array needs to be re-calculated. * - * @var array|NULL + * @var array|null */ protected $sortedNegotiators; diff --git a/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php b/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php index 4ae187b..64d779c 100644 --- a/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php +++ b/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php @@ -44,7 +44,7 @@ * If an unsupported data type gets passed to the class; e.g., 'string' to a * definition class handling 'entity:* data types. */ - public static function createFromDataType($data_type); + public static function createFromDataType($data_type); /** * Returns the data type of the data. diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManagerInterface.php b/core/lib/Drupal/Core/TypedData/TypedDataManagerInterface.php index 16a910c..20a4a76 100644 --- a/core/lib/Drupal/Core/TypedData/TypedDataManagerInterface.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataManagerInterface.php @@ -192,7 +192,7 @@ public function getValidationConstraintManager(); * The validation constraint manager is used to instantiate validation * constraint plugins. * - * @param \Drupal\Core\Validation\ConstraintManager + * @param \Drupal\Core\Validation\ConstraintManager $constraintManager * The constraint manager to set. */ public function setValidationConstraintManager(ConstraintManager $constraintManager); diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php index 6a58319..e7624d8 100644 --- a/core/lib/Drupal/Core/Url.php +++ b/core/lib/Drupal/Core/Url.php @@ -845,7 +845,7 @@ protected function unroutedUrlAssembler() { /** * Sets the URL generator. * - * @param \Drupal\Core\Routing\UrlGeneratorInterface + * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator * (optional) The URL generator, specify NULL to reset it. * * @return $this @@ -859,7 +859,7 @@ public function setUrlGenerator(UrlGeneratorInterface $url_generator = NULL) { /** * Sets the unrouted URL assembler. * - * @param \Drupal\Core\Utility\UnroutedUrlAssemblerInterface + * @param \Drupal\Core\Utility\UnroutedUrlAssemblerInterface $url_assembler * The unrouted URL assembler. * * @return $this diff --git a/core/lib/Drupal/Core/Utility/ProjectInfo.php b/core/lib/Drupal/Core/Utility/ProjectInfo.php index 1eed1fe..24043d3 100644 --- a/core/lib/Drupal/Core/Utility/ProjectInfo.php +++ b/core/lib/Drupal/Core/Utility/ProjectInfo.php @@ -172,7 +172,7 @@ function getProjectName(Extension $file) { * @return * Array of .info.yml file data we need for the update manager. * - * @see \Drupal\Core\Utility\ProjectInfo->processInfoList() + * @see \Drupal\Core\Utility\ProjectInfo::processInfoList() */ function filterProjectInfo($info, $additional_whitelist = array()) { $whitelist = array( diff --git a/core/lib/Drupal/Core/Utility/ThemeRegistry.php b/core/lib/Drupal/Core/Utility/ThemeRegistry.php index 919571c..8dbf42e 100644 --- a/core/lib/Drupal/Core/Utility/ThemeRegistry.php +++ b/core/lib/Drupal/Core/Utility/ThemeRegistry.php @@ -53,7 +53,7 @@ function __construct($cid, CacheBackendInterface $cache, LockBackendInterface $l $this->tags = $tags; $this->persistable = $modules_loaded && \Drupal::hasRequest() && \Drupal::request()->isMethod('GET'); - // @todo: Implement lazyload. + // @todo: Implement lazyload. $this->cacheLoaded = TRUE; if ($this->persistable && $cached = $this->cache->get($this->cid)) { diff --git a/core/modules/action/src/ActionListBuilder.php b/core/modules/action/src/ActionListBuilder.php index 0902403..0a54cd6 100644 --- a/core/modules/action/src/ActionListBuilder.php +++ b/core/modules/action/src/ActionListBuilder.php @@ -108,10 +108,10 @@ public function getDefaultOperations(EntityInterface $entity) { * {@inheritdoc} */ public function render() { - $build['action_header']['#markup'] = '

' . t('Available actions:') . '

'; + $build['action_header']['#markup'] = '

' . $this->t('Available actions:') . '

'; $build['action_table'] = parent::render(); if (!$this->hasConfigurableActions) { - unset($build['action_table']['#header']['operations']); + unset($build['action_table']['table']['#header']['operations']); } $build['action_admin_manage_form'] = \Drupal::formBuilder()->getForm('Drupal\action\Form\ActionAdminManageForm'); return $build; diff --git a/core/modules/action/src/Plugin/Action/EmailAction.php b/core/modules/action/src/Plugin/Action/EmailAction.php index 1ee6184..c4ea5ac 100644 --- a/core/modules/action/src/Plugin/Action/EmailAction.php +++ b/core/modules/action/src/Plugin/Action/EmailAction.php @@ -84,9 +84,9 @@ class EmailAction extends ConfigurableActionBase implements ContainerFactoryPlug * The entity manager. * @param \Psr\Log\LoggerInterface $logger * A logger instance. - * @param \Drupal\Core\Mail\MailManagerInterface + * @param \Drupal\Core\Mail\MailManagerInterface $mail_manager * The mail manager. - * @param \Drupal\Core\Language\LanguageManagerInterface + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. * @param \Egulias\EmailValidator\EmailValidator $email_validator * The email validator. diff --git a/core/modules/action/src/Tests/ActionListTest.php b/core/modules/action/src/Tests/ActionListTest.php new file mode 100644 index 0000000..f533cdf --- /dev/null +++ b/core/modules/action/src/Tests/ActionListTest.php @@ -0,0 +1,37 @@ +drupalLogin($this->drupalCreateUser(['administer actions'])); + + // Ensure the empty text appears on the action list page. + /** @var $storage \Drupal\Core\Entity\EntityStorageInterface */ + $storage = $this->container->get('entity.manager')->getStorage('action'); + $actions = $storage->loadMultiple(); + $storage->delete($actions); + $this->drupalGet('/admin/config/system/actions'); + $this->assertRaw('There is no Action yet.'); + } + +} diff --git a/core/modules/aggregator/aggregator.links.menu.yml b/core/modules/aggregator/aggregator.links.menu.yml index dd2735b..6c966d5 100644 --- a/core/modules/aggregator/aggregator.links.menu.yml +++ b/core/modules/aggregator/aggregator.links.menu.yml @@ -1,11 +1,11 @@ aggregator.admin_overview: - title: 'Feed aggregator' + title: 'Aggregator' description: 'Add feeds or import OPMLs to gather external content and configure how often they are updated.' route_name: aggregator.admin_overview parent: system.admin_config_services weight: 10 aggregator.page_last: - title: 'Feed aggregator' + title: 'Aggregator' weight: 5 route_name: aggregator.page_last aggregator.feed_add: diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module index 7405699..15b7353 100644 --- a/core/modules/aggregator/aggregator.module +++ b/core/modules/aggregator/aggregator.module @@ -31,11 +31,11 @@ function aggregator_help($route_name, RouteMatchInterface $route_match) { $output .= '
' . t('Users view feed content in the main aggregator display, or by their source (usually via an RSS feed reader). The most recent content in a feed can be displayed as a block through the Blocks administration page.', array(':aggregator' => \Drupal::url('aggregator.page_last'), ':aggregator-sources' => $url->toString(), ':admin-block' => (\Drupal::moduleHandler()->moduleExists('block')) ? \Drupal::url('block.admin_display') : '#')) . '
'; } $output .= '
' . t('Adding, editing, and deleting feeds') . '
'; - $output .= '
' . t('Administrators can add, edit, and delete feeds, and choose how often to check each feed for newly updated items on the Feed aggregator page.', array(':feededit' => \Drupal::url('aggregator.admin_overview'))) . '
'; + $output .= '
' . t('Administrators can add, edit, and delete feeds, and choose how often to check each feed for newly updated items on the Aggregator administration page.', array(':feededit' => \Drupal::url('aggregator.admin_overview'))) . '
'; $output .= '
' . t('Configuring the display of feed items') . '
'; - $output .= '
' . t('Administrators can choose how many items are displayed in the listing pages, which HTML tags are allowed in the content of feed items, and whether they should be trimmed to a maximum number of characters on the Feed aggregator settings page.', array(':settings' => \Drupal::url('aggregator.admin_settings'))) . '
'; + $output .= '
' . t('Administrators can choose how many items are displayed in the listing pages, which HTML tags are allowed in the content of feed items, and whether they should be trimmed to a maximum number of characters on the Aggregator settings page.', array(':settings' => \Drupal::url('aggregator.admin_settings'))) . '
'; $output .= '
' . t('Discarding old feed items') . '
'; - $output .= '
' . t('Administrators can choose whether to discard feed items that are older than a specified period of time on the Feed aggregator settings page. This requires a correctly configured cron maintenance task (see below).', array(':settings' => \Drupal::url('aggregator.admin_settings'))) . '
'; + $output .= '
' . t('Administrators can choose whether to discard feed items that are older than a specified period of time on the Aggregator settings page. This requires a correctly configured cron maintenance task (see below).', array(':settings' => \Drupal::url('aggregator.admin_settings'))) . '
'; $output .= '
' . t('OPML integration') . '
'; // Check if the aggregator opml View is enabled. diff --git a/core/modules/aggregator/aggregator.routing.yml b/core/modules/aggregator/aggregator.routing.yml index 941f693..105ab6d 100644 --- a/core/modules/aggregator/aggregator.routing.yml +++ b/core/modules/aggregator/aggregator.routing.yml @@ -2,7 +2,7 @@ aggregator.admin_overview: path: '/admin/config/services/aggregator' defaults: _controller: '\Drupal\aggregator\Controller\AggregatorController::adminOverview' - _title: 'Feed aggregator' + _title: 'Aggregator' requirements: _permission: 'administer news feeds' @@ -10,7 +10,7 @@ aggregator.admin_settings: path: '/admin/config/services/aggregator/settings' defaults: _form: '\Drupal\aggregator\Form\SettingsForm' - _title: 'Feed aggregator settings' + _title: 'Aggregator settings' requirements: _permission: 'administer news feeds' @@ -53,6 +53,6 @@ aggregator.page_last: path: '/aggregator' defaults: _controller: '\Drupal\aggregator\Controller\AggregatorController::pageLast' - _title: 'Feed aggregator' + _title: 'Aggregator' requirements: _permission: 'access news feeds' diff --git a/core/modules/aggregator/src/Entity/Item.php b/core/modules/aggregator/src/Entity/Item.php index 330aacb..2a2c195 100644 --- a/core/modules/aggregator/src/Entity/Item.php +++ b/core/modules/aggregator/src/Entity/Item.php @@ -54,7 +54,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setDescription(t('The ID of the feed item.')); $fields['langcode']->setLabel(t('Language code')) - ->setDescription(t('The feed item language code.')); + ->setDescription(t('The feed item language code.')); $fields['fid'] = BaseFieldDefinition::create('entity_reference') ->setLabel(t('Source feed')) diff --git a/core/modules/aggregator/src/FeedAccessControlHandler.php b/core/modules/aggregator/src/FeedAccessControlHandler.php index 3f11da9..334265a 100644 --- a/core/modules/aggregator/src/FeedAccessControlHandler.php +++ b/core/modules/aggregator/src/FeedAccessControlHandler.php @@ -21,11 +21,9 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter switch ($operation) { case 'view': return AccessResult::allowedIfHasPermission($account, 'access news feeds'); - break; default: return AccessResult::allowedIfHasPermission($account, 'administer news feeds'); - break; } } diff --git a/core/modules/aggregator/src/Plugin/Field/FieldFormatter/AggregatorTitleFormatter.php b/core/modules/aggregator/src/Plugin/Field/FieldFormatter/AggregatorTitleFormatter.php index 346ea26..25e33e9 100644 --- a/core/modules/aggregator/src/Plugin/Field/FieldFormatter/AggregatorTitleFormatter.php +++ b/core/modules/aggregator/src/Plugin/Field/FieldFormatter/AggregatorTitleFormatter.php @@ -60,17 +60,17 @@ public function viewElements(FieldItemListInterface $items, $langcode) { } foreach ($items as $delta => $item) { - if ($this->getSetting('display_as_link') && $url_string) { - $elements[$delta] = [ + if ($this->getSetting('display_as_link') && $url_string) { + $elements[$delta] = [ '#type' => 'link', '#title' => $item->value, '#url' => Url::fromUri($url_string), - ]; - } - else { - $elements[$delta] = ['#markup' => $item->value]; - } + ]; } + else { + $elements[$delta] = ['#markup' => $item->value]; + } + } return $elements; } diff --git a/core/modules/aggregator/src/Tests/AggregatorAdminTest.php b/core/modules/aggregator/src/Tests/AggregatorAdminTest.php index 249817f..487e0ef 100644 --- a/core/modules/aggregator/src/Tests/AggregatorAdminTest.php +++ b/core/modules/aggregator/src/Tests/AggregatorAdminTest.php @@ -14,7 +14,7 @@ class AggregatorAdminTest extends AggregatorTestBase { */ public function testSettingsPage() { $this->drupalGet('admin/config'); - $this->clickLink('Feed aggregator'); + $this->clickLink('Aggregator'); $this->clickLink('Settings'); // Make sure that test plugins are present. $this->assertText('Test fetcher'); diff --git a/core/modules/aggregator/src/Tests/FeedAdminDisplayTest.php b/core/modules/aggregator/src/Tests/FeedAdminDisplayTest.php index b6e36e9..0324e3b 100644 --- a/core/modules/aggregator/src/Tests/FeedAdminDisplayTest.php +++ b/core/modules/aggregator/src/Tests/FeedAdminDisplayTest.php @@ -3,7 +3,7 @@ namespace Drupal\aggregator\Tests; /** - * Tests the display of a feed on the feed aggregator list page. + * Tests the display of a feed on the Aggregator list page. * * @group aggregator */ diff --git a/core/modules/aggregator/tests/src/Unit/Plugin/AggregatorPluginSettingsBaseTest.php b/core/modules/aggregator/tests/src/Unit/Plugin/AggregatorPluginSettingsBaseTest.php index 022b793..87a5d93 100644 --- a/core/modules/aggregator/tests/src/Unit/Plugin/AggregatorPluginSettingsBaseTest.php +++ b/core/modules/aggregator/tests/src/Unit/Plugin/AggregatorPluginSettingsBaseTest.php @@ -1,6 +1,6 @@ normalUserAlt->save(); // Enable our test block. - $this->block = $this->drupalPlaceBlock('test_cache'); + $this->block = $this->drupalPlaceBlock('test_cache'); } /** diff --git a/core/modules/block/src/Tests/BlockFormInBlockTest.php b/core/modules/block/src/Tests/BlockFormInBlockTest.php index 73c7591..20d24b2 100644 --- a/core/modules/block/src/Tests/BlockFormInBlockTest.php +++ b/core/modules/block/src/Tests/BlockFormInBlockTest.php @@ -25,7 +25,7 @@ protected function setUp() { parent::setUp(); // Enable our test block. - $this->drupalPlaceBlock('test_form_in_block'); + $this->drupalPlaceBlock('test_form_in_block'); } /** diff --git a/core/modules/block/tests/src/Kernel/Migrate/d7/MigrateBlockTest.php b/core/modules/block/tests/src/Kernel/Migrate/d7/MigrateBlockTest.php index 571c210..a0597b7 100644 --- a/core/modules/block/tests/src/Kernel/Migrate/d7/MigrateBlockTest.php +++ b/core/modules/block/tests/src/Kernel/Migrate/d7/MigrateBlockTest.php @@ -13,7 +13,7 @@ */ class MigrateBlockTest extends MigrateDrupal7TestBase { - /** + /** * {@inheritdoc} */ public static $modules = [ diff --git a/core/modules/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php index 9e0531e..13af342 100644 --- a/core/modules/block_content/src/Entity/BlockContent.php +++ b/core/modules/block_content/src/Entity/BlockContent.php @@ -105,9 +105,15 @@ public function getTheme() { */ public function postSave(EntityStorageInterface $storage, $update = TRUE) { parent::postSave($storage, $update); + static::invalidateBlockPluginCache(); + } - // Invalidate the block cache to update custom block-based derivatives. - \Drupal::service('plugin.manager.block')->clearCachedDefinitions(); + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageInterface $storage, array $entities) { + parent::postDelete($storage, $entities); + static::invalidateBlockPluginCache(); } /** @@ -284,4 +290,12 @@ public function setRevisionLogMessage($revision_log_message) { return $this; } + /** + * Invalidates the block plugin cache after changes and deletions. + */ + protected static function invalidateBlockPluginCache() { + // Invalidate the block cache to update custom block-based derivatives. + \Drupal::service('plugin.manager.block')->clearCachedDefinitions(); + } + } diff --git a/core/modules/block_content/src/Plugin/Derivative/BlockContent.php b/core/modules/block_content/src/Plugin/Derivative/BlockContent.php index 63b68c0..ab56445 100644 --- a/core/modules/block_content/src/Plugin/Derivative/BlockContent.php +++ b/core/modules/block_content/src/Plugin/Derivative/BlockContent.php @@ -44,6 +44,8 @@ public static function create(ContainerInterface $container, $base_plugin_id) { */ public function getDerivativeDefinitions($base_plugin_definition) { $block_contents = $this->blockContentStorage->loadMultiple(); + // Reset the discovered definitions. + $this->derivatives = []; /** @var $block_content \Drupal\block_content\Entity\BlockContent */ foreach ($block_contents as $block_content) { $this->derivatives[$block_content->uuid()] = $base_plugin_definition; diff --git a/core/modules/block_content/tests/src/Kernel/BlockContentDeletionTest.php b/core/modules/block_content/tests/src/Kernel/BlockContentDeletionTest.php new file mode 100644 index 0000000..e54b2d3 --- /dev/null +++ b/core/modules/block_content/tests/src/Kernel/BlockContentDeletionTest.php @@ -0,0 +1,63 @@ +installSchema('system', ['sequence']); + $this->installEntitySchema('user'); + $this->installEntitySchema('block_content'); + } + + /** + * Tests deleting a block_content updates the discovered block plugin. + */ + public function testDeletingBlockContentShouldClearPluginCache() { + // Create a block content type. + $block_content_type = BlockContentType::create([ + 'id' => 'spiffy', + 'label' => 'Mucho spiffy', + 'description' => "Provides a block type that increases your site's spiffiness by upto 11%", + ]); + $block_content_type->save(); + // And a block content entity. + $block_content = BlockContent::create([ + 'info' => 'Spiffy prototype', + 'type' => 'spiffy', + ]); + $block_content->save(); + + // Make sure the block content provides a derivative block plugin in the + // block repository. + /** @var \Drupal\Core\Block\BlockManagerInterface $block_manager */ + $block_manager = $this->container->get('plugin.manager.block'); + $plugin_id = 'block_content' . PluginBase::DERIVATIVE_SEPARATOR . $block_content->uuid(); + $this->assertTrue($block_manager->hasDefinition($plugin_id)); + + // Now delete the block content entity. + $block_content->delete(); + // The plugin should no longer exist. + $this->assertFalse($block_manager->hasDefinition($plugin_id)); + } + +} diff --git a/core/modules/block_place/src/Plugin/DisplayVariant/PlaceBlockPageVariant.php b/core/modules/block_place/src/Plugin/DisplayVariant/PlaceBlockPageVariant.php index eaef409..b2d3c33 100644 --- a/core/modules/block_place/src/Plugin/DisplayVariant/PlaceBlockPageVariant.php +++ b/core/modules/block_place/src/Plugin/DisplayVariant/PlaceBlockPageVariant.php @@ -6,10 +6,10 @@ use Drupal\block\Plugin\DisplayVariant\BlockPageVariant; use Drupal\Component\Serialization\Json; use Drupal\Core\Entity\EntityViewBuilderInterface; +use Drupal\Core\Routing\RedirectDestinationInterface; use Drupal\Core\Theme\ThemeManagerInterface; use Drupal\Core\Link; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpFoundation\RequestStack; /** * Allows blocks to be placed directly within a region. @@ -29,11 +29,11 @@ class PlaceBlockPageVariant extends BlockPageVariant { protected $themeManager; /** - * The request stack. + * The redirect destination. * - * @var \Symfony\Component\HttpFoundation\RequestStack + * @var \Drupal\Core\Routing\RedirectDestinationInterface */ - protected $requestStack; + protected $redirectDestination; /** * Constructs a new PlaceBlockPageVariant. @@ -52,14 +52,14 @@ class PlaceBlockPageVariant extends BlockPageVariant { * The Block entity type list cache tags. * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager * The theme manager. - * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack - * The current request stack. + * @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination + * The redirect destination. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, array $block_list_cache_tags, ThemeManagerInterface $theme_manager, RequestStack $request_stack) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, array $block_list_cache_tags, ThemeManagerInterface $theme_manager, RedirectDestinationInterface $redirect_destination) { parent::__construct($configuration, $plugin_id, $plugin_definition, $block_repository, $block_view_builder, $block_list_cache_tags); $this->themeManager = $theme_manager; - $this->requestStack = $request_stack; + $this->redirectDestination = $redirect_destination; } /** @@ -74,7 +74,7 @@ public static function create(ContainerInterface $container, array $configuratio $container->get('entity_type.manager')->getViewBuilder('block'), $container->get('entity_type.manager')->getDefinition('block')->getListCacheTags(), $container->get('theme.manager'), - $container->get('request_stack') + $container->get('redirect.destination') ); } @@ -86,13 +86,13 @@ public function build() { $active_theme = $this->themeManager->getActiveTheme(); $theme_name = $active_theme->getName(); + $destination = $this->redirectDestination->get(); $visible_regions = $this->getVisibleRegionNames($theme_name); // Build an array of the region names in the right order. $build += array_fill_keys(array_keys($visible_regions), []); foreach ($visible_regions as $region => $region_name) { - $destination = $this->requestStack->getCurrentRequest()->query->get('destination'); $query = [ 'region' => $region, ]; diff --git a/core/modules/book/src/BookManager.php b/core/modules/book/src/BookManager.php index 2942462..60754be 100644 --- a/core/modules/book/src/BookManager.php +++ b/core/modules/book/src/BookManager.php @@ -138,7 +138,7 @@ public function getParentDepthLimit(array $book_link) { /** * Determine the relative depth of the children of a given book link. * - * @param array + * @param array $book_link * The book link. * * @return int diff --git a/core/modules/book/src/Tests/BookTest.php b/core/modules/book/src/Tests/BookTest.php index 7ebec63..0158992 100644 --- a/core/modules/book/src/Tests/BookTest.php +++ b/core/modules/book/src/Tests/BookTest.php @@ -526,49 +526,49 @@ function testNavigationBlockOnAccessModuleInstalled() { /** * Tests the access for deleting top-level book nodes. */ - function testBookDelete() { - $node_storage = $this->container->get('entity.manager')->getStorage('node'); - $nodes = $this->createBook(); - $this->drupalLogin($this->adminUser); - $edit = array(); - - // Test access to delete top-level and child book nodes. - $this->drupalGet('node/' . $this->book->id() . '/outline/remove'); - $this->assertResponse('403', 'Deleting top-level book node properly forbidden.'); - $this->drupalPostForm('node/' . $nodes[4]->id() . '/outline/remove', $edit, t('Remove')); - $node_storage->resetCache(array($nodes[4]->id())); - $node4 = $node_storage->load($nodes[4]->id()); - $this->assertTrue(empty($node4->book), 'Deleting child book node properly allowed.'); - - // Delete all child book nodes and retest top-level node deletion. - foreach ($nodes as $node) { - $nids[] = $node->id(); - } - entity_delete_multiple('node', $nids); - $this->drupalPostForm('node/' . $this->book->id() . '/outline/remove', $edit, t('Remove')); - $node_storage->resetCache(array($this->book->id())); - $node = $node_storage->load($this->book->id()); - $this->assertTrue(empty($node->book), 'Deleting childless top-level book node properly allowed.'); - - // Tests directly deleting a book parent. - $nodes = $this->createBook(); - $this->drupalLogin($this->adminUser); - $this->drupalGet($this->book->urlInfo('delete-form')); - $this->assertRaw(t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', ['%title' => $this->book->label()])); - // Delete parent, and visit a child page. - $this->drupalPostForm($this->book->urlInfo('delete-form'), [], t('Delete')); - $this->drupalGet($nodes[0]->urlInfo()); - $this->assertResponse(200); - $this->assertText($nodes[0]->label()); - // The book parents should be updated. - $node_storage = \Drupal::entityTypeManager()->getStorage('node'); - $node_storage->resetCache(); - $child = $node_storage->load($nodes[0]->id()); - $this->assertEqual($child->id(), $child->book['bid'], 'Child node book ID updated when parent is deleted.'); - // 3rd-level children should now be 2nd-level. - $second = $node_storage->load($nodes[1]->id()); - $this->assertEqual($child->id(), $second->book['bid'], '3rd-level child node is now second level when top-level node is deleted.'); - } + function testBookDelete() { + $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $nodes = $this->createBook(); + $this->drupalLogin($this->adminUser); + $edit = array(); + + // Test access to delete top-level and child book nodes. + $this->drupalGet('node/' . $this->book->id() . '/outline/remove'); + $this->assertResponse('403', 'Deleting top-level book node properly forbidden.'); + $this->drupalPostForm('node/' . $nodes[4]->id() . '/outline/remove', $edit, t('Remove')); + $node_storage->resetCache(array($nodes[4]->id())); + $node4 = $node_storage->load($nodes[4]->id()); + $this->assertTrue(empty($node4->book), 'Deleting child book node properly allowed.'); + + // Delete all child book nodes and retest top-level node deletion. + foreach ($nodes as $node) { + $nids[] = $node->id(); + } + entity_delete_multiple('node', $nids); + $this->drupalPostForm('node/' . $this->book->id() . '/outline/remove', $edit, t('Remove')); + $node_storage->resetCache(array($this->book->id())); + $node = $node_storage->load($this->book->id()); + $this->assertTrue(empty($node->book), 'Deleting childless top-level book node properly allowed.'); + + // Tests directly deleting a book parent. + $nodes = $this->createBook(); + $this->drupalLogin($this->adminUser); + $this->drupalGet($this->book->urlInfo('delete-form')); + $this->assertRaw(t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', ['%title' => $this->book->label()])); + // Delete parent, and visit a child page. + $this->drupalPostForm($this->book->urlInfo('delete-form'), [], t('Delete')); + $this->drupalGet($nodes[0]->urlInfo()); + $this->assertResponse(200); + $this->assertText($nodes[0]->label()); + // The book parents should be updated. + $node_storage = \Drupal::entityTypeManager()->getStorage('node'); + $node_storage->resetCache(); + $child = $node_storage->load($nodes[0]->id()); + $this->assertEqual($child->id(), $child->book['bid'], 'Child node book ID updated when parent is deleted.'); + // 3rd-level children should now be 2nd-level. + $second = $node_storage->load($nodes[1]->id()); + $this->assertEqual($child->id(), $second->book['bid'], '3rd-level child node is now second level when top-level node is deleted.'); + } /** * Tests re-ordering of books. diff --git a/core/modules/ckeditor/src/CKEditorPluginInterface.php b/core/modules/ckeditor/src/CKEditorPluginInterface.php index 65475a0..543a112 100644 --- a/core/modules/ckeditor/src/CKEditorPluginInterface.php +++ b/core/modules/ckeditor/src/CKEditorPluginInterface.php @@ -71,7 +71,7 @@ public function getLibraries(Editor $editor); * Note: this does not use a Drupal library because this uses CKEditor's API, * see http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.resourceManager.html#addExternal. * - * @return string|FALSE + * @return string|false * The Drupal root-relative path to the file, FALSE if an internal plugin. */ public function getFile(); diff --git a/core/modules/ckeditor/src/Plugin/CKEditorPlugin/StylesCombo.php b/core/modules/ckeditor/src/Plugin/CKEditorPlugin/StylesCombo.php index 888fec0..75b78a5 100644 --- a/core/modules/ckeditor/src/Plugin/CKEditorPlugin/StylesCombo.php +++ b/core/modules/ckeditor/src/Plugin/CKEditorPlugin/StylesCombo.php @@ -115,7 +115,7 @@ public function validateStylesValue(array $element, FormStateInterface $form_sta * * @param string $styles * The "styles" setting. - * @return array|FALSE + * @return array|false * An array containing the "stylesSet" configuration, or FALSE when the * syntax is invalid. */ diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index 8a1febc..b0f1550 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -657,7 +657,7 @@ function template_preprocess_comment(&$variables) { $variables['permalink'] = \Drupal::l(t('Permalink'), new Url('')); } else { - $uri = $comment->urlInfo(); + $uri = $comment->permalink(); $attributes = $uri->getOption('attributes') ?: array(); $attributes += array('class' => array('permalink'), 'rel' => 'bookmark'); $uri->setOption('attributes', $attributes); diff --git a/core/modules/comment/src/CommentInterface.php b/core/modules/comment/src/CommentInterface.php index 72a6652..28318ee 100644 --- a/core/modules/comment/src/CommentInterface.php +++ b/core/modules/comment/src/CommentInterface.php @@ -32,7 +32,7 @@ public function hasParentComment(); /** * Returns the parent comment entity if this is a reply to a comment. * - * @return \Drupal\comment\CommentInterface|NULL + * @return \Drupal\comment\CommentInterface|null * A comment entity of the parent comment or NULL if there is no parent. */ public function getParentComment(); diff --git a/core/modules/comment/src/Form/CommentAdminOverview.php b/core/modules/comment/src/Form/CommentAdminOverview.php index dac62bd..3966fca 100644 --- a/core/modules/comment/src/Form/CommentAdminOverview.php +++ b/core/modules/comment/src/Form/CommentAdminOverview.php @@ -151,10 +151,10 @@ public function buildForm(array $form, FormStateInterface $form_state, $type = ' 'operations' => $this->t('Operations'), ); $cids = $this->commentStorage->getQuery() - ->condition('status', $status) - ->tableSort($header) - ->pager(50) - ->execute(); + ->condition('status', $status) + ->tableSort($header) + ->pager(50) + ->execute(); /** @var $comments \Drupal\comment\CommentInterface[] */ $comments = $this->commentStorage->loadMultiple($cids); diff --git a/core/modules/comment/src/Tests/CommentLinksTest.php b/core/modules/comment/src/Tests/CommentLinksTest.php index e961691..1115f57 100644 --- a/core/modules/comment/src/Tests/CommentLinksTest.php +++ b/core/modules/comment/src/Tests/CommentLinksTest.php @@ -66,7 +66,7 @@ public function testCommentLinks() { 'subject' => $this->randomMachineName(), 'hostname' => '127.0.0.1', 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, - 'comment_body' => array(LanguageInterface::LANGCODE_NOT_SPECIFIED => array($this->randomMachineName())), + 'comment_body' => array(array('value' => $this->randomMachineName())), )); $comment->save(); $this->comment = $comment; @@ -101,6 +101,26 @@ public function testCommentLinks() { $this->assertLink('Add new comment'); } + // Change weight to make links go before comment body. + entity_get_display('comment', 'comment', 'default') + ->setComponent('links', array('weight' => -100)) + ->save(); + $this->drupalGet($this->node->urlInfo()); + $element = $this->cssSelect('article.js-comment > div'); + // Get last child element. + $element = end($element[0]); + $this->assertIdentical($element[0]->getName(), 'div', 'Last element is comment body.'); + + // Change weight to make links go after comment body. + entity_get_display('comment', 'comment', 'default') + ->setComponent('links', array('weight' => 100)) + ->save(); + $this->drupalGet($this->node->urlInfo()); + $element = $this->cssSelect('article.js-comment > div'); + // Get last child element. + $element = end($element[0]); + $this->assertIdentical($element[0]->getName(), 'ul', 'Last element is comment links.'); + // Make sure we can hide node links. entity_get_display('node', $this->node->bundle(), 'default') ->removeComponent('links') diff --git a/core/modules/comment/src/Tests/CommentTitleTest.php b/core/modules/comment/src/Tests/CommentTitleTest.php index 0546cb6..aab1cf5 100644 --- a/core/modules/comment/src/Tests/CommentTitleTest.php +++ b/core/modules/comment/src/Tests/CommentTitleTest.php @@ -59,6 +59,12 @@ public function testCommentPopulatedTitles() { $this->assertTrue($this->commentExists($comment1), 'Comment #1. Comment found.'); // Tests that markup is created for comment with heading. $this->assertPattern('|]*>]*>' . $subject_text . '|', 'Comment title is rendered in h3 when title populated.'); + // Tests that the comment's title link is the permalink of the comment. + $comment_permalink = $this->cssSelect('.permalink'); + $comment_permalink = (string) $comment_permalink[0]['href']; + // Tests that the comment's title link contains the url fragment. + $this->assertTrue(strpos($comment_permalink, '#comment-' . $comment1->id()), "The comment's title link contains the url fragment."); + $this->assertEqual($comment1->permalink()->toString(), $comment_permalink, "The comment's title has the correct link."); } } diff --git a/core/modules/comment/tests/src/Kernel/Migrate/d7/MigrateCommentTest.php b/core/modules/comment/tests/src/Kernel/Migrate/d7/MigrateCommentTest.php index ed19178..4d4df03 100644 --- a/core/modules/comment/tests/src/Kernel/Migrate/d7/MigrateCommentTest.php +++ b/core/modules/comment/tests/src/Kernel/Migrate/d7/MigrateCommentTest.php @@ -40,7 +40,7 @@ protected function setUp() { ), )); $this->executeMigrations([ - 'd7_node:test_content_type', + 'd7_node', 'd7_comment_type', 'd7_comment', ]); diff --git a/core/modules/comment/tests/src/Unit/CommentLinkBuilderTest.php b/core/modules/comment/tests/src/Unit/CommentLinkBuilderTest.php index 898e0ab..61e93a1 100644 --- a/core/modules/comment/tests/src/Unit/CommentLinkBuilderTest.php +++ b/core/modules/comment/tests/src/Unit/CommentLinkBuilderTest.php @@ -1,6 +1,6 @@ calls_to_fetch) { case 1: return 'something'; - break; + case 2: return 'something-else'; - break; + default: return FALSE; - break; } } diff --git a/core/modules/config/src/Form/ConfigSync.php b/core/modules/config/src/Form/ConfigSync.php index 8dd9ca0..51cded2 100644 --- a/core/modules/config/src/Form/ConfigSync.php +++ b/core/modules/config/src/Form/ConfigSync.php @@ -124,7 +124,7 @@ class ConfigSync extends FormBase { * The module installer. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler * The theme handler. - * @param \Drupal\Core\Render\RendererInterface + * @param \Drupal\Core\Render\RendererInterface $renderer * The renderer. */ public function __construct(StorageInterface $sync_storage, StorageInterface $active_storage, StorageInterface $snapshot_storage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler, RendererInterface $renderer) { diff --git a/core/modules/config/src/StorageReplaceDataWrapper.php b/core/modules/config/src/StorageReplaceDataWrapper.php index 58b3b49..91c3a7c 100644 --- a/core/modules/config/src/StorageReplaceDataWrapper.php +++ b/core/modules/config/src/StorageReplaceDataWrapper.php @@ -44,6 +44,7 @@ class StorageReplaceDataWrapper implements StorageInterface { public function __construct(StorageInterface $storage, $collection = StorageInterface::DEFAULT_COLLECTION) { $this->storage = $storage; $this->collection = $collection; + $this->replacementData[$collection] = []; } /** @@ -104,7 +105,7 @@ public function rename($name, $new_name) { $this->replacementData[$this->collection][$new_name] = $this->replacementData[$this->collection][$name]; unset($this->replacementData[$this->collection][$name]); } - return $this->rename($name, $new_name); + return $this->storage->rename($name, $new_name); } /** @@ -164,8 +165,10 @@ public function deleteAll($prefix = '') { * {@inheritdoc} */ public function createCollection($collection) { - $this->collection = $collection; - return $this->storage->createCollection($collection); + return new static( + $this->storage->createCollection($collection), + $collection + ); } /** diff --git a/core/modules/config/src/Tests/ConfigSingleImportExportTest.php b/core/modules/config/src/Tests/ConfigSingleImportExportTest.php index a5b8386..53f6164 100644 --- a/core/modules/config/src/Tests/ConfigSingleImportExportTest.php +++ b/core/modules/config/src/Tests/ConfigSingleImportExportTest.php @@ -20,7 +20,10 @@ class ConfigSingleImportExportTest extends WebTestBase { public static $modules = [ 'block', 'config', - 'config_test' + 'config_test', + // Adding language module makes it possible to involve non-default + // (language.xx) collections in import/export operations. + 'language', ]; protected function setUp() { diff --git a/core/modules/config_translation/src/ConfigEntityMapper.php b/core/modules/config_translation/src/ConfigEntityMapper.php index 5ba4456..faf8991 100644 --- a/core/modules/config_translation/src/ConfigEntityMapper.php +++ b/core/modules/config_translation/src/ConfigEntityMapper.php @@ -120,7 +120,7 @@ public function getEntity() { return $this->entity; } - /** + /** * Sets the entity instance for this mapper. * * This method can only be invoked when the concrete entity is known, that is diff --git a/core/modules/config_translation/src/ConfigNamesMapper.php b/core/modules/config_translation/src/ConfigNamesMapper.php index bb82fe5..0784090 100644 --- a/core/modules/config_translation/src/ConfigNamesMapper.php +++ b/core/modules/config_translation/src/ConfigNamesMapper.php @@ -109,7 +109,7 @@ class ConfigNamesMapper extends PluginBase implements ConfigMapperInterface, Con * The locale configuration manager. * @param \Drupal\config_translation\ConfigMapperManagerInterface $config_mapper_manager * The mapper plugin discovery service. - * @param \Drupal\Core\Routing\RouteProviderInterface + * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider * The route provider. * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation * The string translation manager. diff --git a/core/modules/contact/src/ContactFormEditForm.php b/core/modules/contact/src/ContactFormEditForm.php index 473d317..7747dad 100644 --- a/core/modules/contact/src/ContactFormEditForm.php +++ b/core/modules/contact/src/ContactFormEditForm.php @@ -40,8 +40,8 @@ class ContactFormEditForm extends EntityForm implements ContainerInjectionInterf * The email validator. */ public function __construct(EmailValidator $email_validator, PathValidatorInterface $path_validator) { - $this->emailValidator = $email_validator; - $this->pathValidator = $path_validator; + $this->emailValidator = $email_validator; + $this->pathValidator = $path_validator; } /** diff --git a/core/modules/contact/tests/drupal-7.contact.database.php b/core/modules/contact/tests/drupal-7.contact.database.php index 494e430..115f83c 100644 --- a/core/modules/contact/tests/drupal-7.contact.database.php +++ b/core/modules/contact/tests/drupal-7.contact.database.php @@ -23,11 +23,11 @@ 'weight', 'selected' )) -->values(array( + ->values(array( 'category' => 'Upgrade test', 'recipients' => 'test1@example.com,test2@example.com', 'reply' => 'Test reply', 'weight' => 1, 'selected' => 1, )) -->execute(); + ->execute(); diff --git a/core/modules/content_moderation/src/Plugin/Derivative/DynamicLocalTasks.php b/core/modules/content_moderation/src/Plugin/Derivative/DynamicLocalTasks.php index 84ad4aa..7ff1fa5 100644 --- a/core/modules/content_moderation/src/Plugin/Derivative/DynamicLocalTasks.php +++ b/core/modules/content_moderation/src/Plugin/Derivative/DynamicLocalTasks.php @@ -78,11 +78,12 @@ public function getDerivativeDefinitions($base_plugin_definition) { foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { if ($this->moderationInfo->canModerateEntitiesOfEntityType($entity_type)) { - $this->derivatives["$entity_type_id.moderation_tab"] = [ - 'route_name' => "entity.$entity_type_id.moderation", + $bundle_id = $entity_type->getBundleEntityType(); + $this->derivatives["$bundle_id.moderation_tab"] = [ + 'route_name' => "entity.$bundle_id.moderation", 'title' => $this->t('Manage moderation'), // @todo - are we sure they all have an edit_form? - 'base_route' => "entity.$entity_type_id.edit_form", + 'base_route' => "entity.$bundle_id.edit_form", 'weight' => 30, ] + $base_plugin_definition; } diff --git a/core/modules/content_moderation/src/Tests/ModerationStateBlockTest.php b/core/modules/content_moderation/src/Tests/ModerationStateBlockTest.php index 001d6b5..03a4b0e 100644 --- a/core/modules/content_moderation/src/Tests/ModerationStateBlockTest.php +++ b/core/modules/content_moderation/src/Tests/ModerationStateBlockTest.php @@ -51,6 +51,12 @@ protected function setUp() { public function testCustomBlockModeration() { $this->drupalLogin($this->rootUser); + $this->drupalGet('admin/structure/block/block-content/types'); + $this->assertLinkByHref('admin/structure/block/block-content/manage/basic/moderation'); + $this->drupalGet('admin/structure/block/block-content/manage/basic'); + $this->assertLinkByHref('admin/structure/block/block-content/manage/basic/moderation'); + $this->drupalGet('admin/structure/block/block-content/manage/basic/moderation'); + // Enable moderation for custom blocks at // admin/structure/block/block-content/manage/basic/moderation. $edit = [ @@ -59,7 +65,7 @@ public function testCustomBlockModeration() { 'allowed_moderation_states_published[published]' => TRUE, 'default_moderation_state' => 'draft', ]; - $this->drupalPostForm('admin/structure/block/block-content/manage/basic/moderation', $edit, t('Save')); + $this->drupalPostForm(NULL, $edit, t('Save')); $this->assertText(t('Your settings have been saved.')); // Create a custom block at block/add and save it as draft. diff --git a/core/modules/content_moderation/src/Tests/ModerationStateTestBase.php b/core/modules/content_moderation/src/Tests/ModerationStateTestBase.php index 0dbdc3d..ddd275e 100644 --- a/core/modules/content_moderation/src/Tests/ModerationStateTestBase.php +++ b/core/modules/content_moderation/src/Tests/ModerationStateTestBase.php @@ -108,6 +108,8 @@ protected function createContentTypeFromUi($content_type_name, $content_type_id, protected function enableModerationThroughUi($content_type_id, array $allowed_states, $default_state) { $this->drupalGet('admin/structure/types'); $this->assertLinkByHref('admin/structure/types/manage/' . $content_type_id . '/moderation'); + $this->drupalGet('admin/structure/types/manage/' . $content_type_id); + $this->assertLinkByHref('admin/structure/types/manage/' . $content_type_id . '/moderation'); $this->drupalGet('admin/structure/types/manage/' . $content_type_id . '/moderation'); $this->assertFieldByName('enable_moderation_state'); $this->assertNoFieldChecked('edit-enable-moderation-state'); diff --git a/core/modules/content_translation/src/Controller/ContentTranslationController.php b/core/modules/content_translation/src/Controller/ContentTranslationController.php index efd5953..43b59aa 100644 --- a/core/modules/content_translation/src/Controller/ContentTranslationController.php +++ b/core/modules/content_translation/src/Controller/ContentTranslationController.php @@ -26,7 +26,7 @@ class ContentTranslationController extends ControllerBase { /** * Initializes a content translation controller. * - * @param \Drupal\content_translation\ContentTranslationManagerInterface + * @param \Drupal\content_translation\ContentTranslationManagerInterface $manager * A content translation manager instance. */ public function __construct(ContentTranslationManagerInterface $manager) { @@ -319,7 +319,7 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL * @param \Drupal\Core\Language\LanguageInterface $target * The language of the translated values. Defaults to the current content * language. - * @param \Drupal\Core\Routing\RouteMatchInterface + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The route match object from which to extract the entity type. * @param string $entity_type_id * (optional) The entity type ID. @@ -354,7 +354,7 @@ public function add(LanguageInterface $source, LanguageInterface $target, RouteM * @param \Drupal\Core\Language\LanguageInterface $language * The language of the translated values. Defaults to the current content * language. - * @param \Drupal\Core\Routing\RouteMatchInterface + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The route match object from which to extract the entity type. * @param string $entity_type_id * (optional) The entity type ID. diff --git a/core/modules/datetime/datetime.module b/core/modules/datetime/datetime.module index f25140f..1078541 100644 --- a/core/modules/datetime/datetime.module +++ b/core/modules/datetime/datetime.module @@ -39,7 +39,7 @@ function datetime_help($route_name, RouteMatchInterface $route_match) { $output .= '
' . t('Dates can be displayed using the Plain or the Default formatter. The Plain formatter displays the date in the ISO 8601 format. If you choose the Default formatter, you can choose a format from a predefined list that can be managed on the Date and time formats page.', array(':date_format_list' => \Drupal::url('entity.date_format.collection'))) . '
'; $output .= ''; return $output; - } + } } /** diff --git a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDatelistWidget.php b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDatelistWidget.php index c88c5c8..8cdabf1 100644 --- a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDatelistWidget.php +++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDatelistWidget.php @@ -61,16 +61,16 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen break; } switch ($time_type) { - case '24': - $date_part_order = array_merge($date_part_order, array('hour', 'minute')); - break; + case '24': + $date_part_order = array_merge($date_part_order, array('hour', 'minute')); + break; - case '12': - $date_part_order = array_merge($date_part_order, array('hour', 'minute', 'ampm')); - break; + case '12': + $date_part_order = array_merge($date_part_order, array('hour', 'minute', 'ampm')); + break; - case 'none': - break; + case 'none': + break; } $element['value'] = array( diff --git a/core/modules/datetime/src/Tests/DateTimeFieldTest.php b/core/modules/datetime/src/Tests/DateTimeFieldTest.php index ce598bf..64576f3 100644 --- a/core/modules/datetime/src/Tests/DateTimeFieldTest.php +++ b/core/modules/datetime/src/Tests/DateTimeFieldTest.php @@ -631,7 +631,7 @@ function testDatelistWidget() { $this->drupalPostForm(NULL, $edit, t('Save')); $this->assertResponse(200); $this->assertOptionSelected("edit-$field_name-0-value-minute", '0', 'Correct minute selected.'); - } + } /** * The data provider for testing the validation of the datelist widget. diff --git a/core/modules/datetime_range/config/schema/datetime_range.schema.yml b/core/modules/datetime_range/config/schema/datetime_range.schema.yml new file mode 100644 index 0000000..90bbd38 --- /dev/null +++ b/core/modules/datetime_range/config/schema/datetime_range.schema.yml @@ -0,0 +1,70 @@ +# Schema for the configuration files of the Datetime Range module. + +# Daterange field type. + +field.storage_settings.daterange: + type: field.storage_settings.datetime + label: 'Date range settings' + +field.field_settings.daterange: + type: field.field_settings.datetime + label: 'Date range settings' + +field.value.daterange: + type: mapping + label: 'Default value' + mapping: + default_date_type: + type: string + label: 'Default start date type' + default_date: + type: string + label: 'Default start date value' + default_end_date_type: + type: string + label: 'Default end date type' + default_end_date: + type: string + label: 'Default end date value' + +field.formatter.settings.daterange_default: + type: field.formatter.settings.datetime_default + label: 'Date range default display format settings' + mapping: + separator: + type: string + label: 'Separator' + +field.formatter.settings.daterange_plain: + type: field.formatter.settings.datetime_plain + label: 'Date range plain display format settings' + mapping: + separator: + type: string + label: 'Separator' + +field.formatter.settings.daterange_custom: + type: field.formatter.settings.datetime_custom + label: 'Date range custom display format settings' + mapping: + separator: + type: string + label: 'Separator' + +field.widget.settings.daterange_datelist: + type: mapping + label: 'Date range select list display format settings' + mapping: + increment: + type: integer + label: 'Time increments' + date_order: + type: string + label: 'Date part order' + time_type: + type: string + label: 'Time type' + +field.widget.settings.daterange_default: + type: mapping + label: 'Date range default display format settings' diff --git a/core/modules/datetime_range/datetime_range.info.yml b/core/modules/datetime_range/datetime_range.info.yml new file mode 100644 index 0000000..dd22d9a --- /dev/null +++ b/core/modules/datetime_range/datetime_range.info.yml @@ -0,0 +1,8 @@ +name: 'Datetime Range' +type: module +description: 'Provides the ability to store end dates.' +package: Core (Experimental) +version: VERSION +core: 8.x +dependencies: + - datetime diff --git a/core/modules/datetime_range/datetime_range.module b/core/modules/datetime_range/datetime_range.module new file mode 100644 index 0000000..ceeb99d --- /dev/null +++ b/core/modules/datetime_range/datetime_range.module @@ -0,0 +1,28 @@ +' . t('About') . ''; + $output .= '

' . t('The Datetime Range module provides a Date field that stores start dates and times, as well as end dates and times. See the Field module help and the Field UI module help pages for general information on fields and how to create and manage them. For more information, see the online documentation for the Datetime Range module.', array(':field' => \Drupal::url('help.page', array('name' => 'field')), ':field_ui' => (\Drupal::moduleHandler()->moduleExists('field_ui')) ? \Drupal::url('help.page', array('name' => 'field_ui')) : '#', ':datetime_do' => 'https://www.drupal.org/documentation/modules/datetime_range')) . '

'; + $output .= '

' . t('Uses') . '

'; + $output .= '
'; + $output .= '
' . t('Managing and displaying date fields') . '
'; + $output .= '
' . t('The settings and the display of the Date field can be configured separately. See the Field UI help for more information on how to manage fields and their display.', array(':field_ui' => (\Drupal::moduleHandler()->moduleExists('field_ui')) ? \Drupal::url('help.page', array('name' => 'field_ui')) : '#')) . '
'; + $output .= '
' . t('Displaying dates') . '
'; + $output .= '
' . t('Dates can be displayed using the Plain or the Default formatter. The Plain formatter displays the date in the ISO 8601 format. If you choose the Default formatter, you can choose a format from a predefined list that can be managed on the Date and time formats page.', array(':date_format_list' => \Drupal::url('entity.date_format.collection'))) . '
'; + $output .= '
'; + return $output; + } +} diff --git a/core/modules/datetime_range/src/DateTimeRangeTrait.php b/core/modules/datetime_range/src/DateTimeRangeTrait.php new file mode 100644 index 0000000..5a34f2c --- /dev/null +++ b/core/modules/datetime_range/src/DateTimeRangeTrait.php @@ -0,0 +1,78 @@ +getFieldSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE) { + // A date without time will pick up the current time, use the default. + datetime_date_default_time($date); + } + $this->setTimeZone($date); + + $build = [ + '#plain_text' => $this->formatDate($date), + '#cache' => [ + 'contexts' => [ + 'timezone', + ], + ], + ]; + + return $build; + } + + /** + * Creates a render array from a date object with ISO date attribute. + * + * @param \Drupal\Core\Datetime\DrupalDateTime $date + * A date object. + * + * @return array + * A render array. + */ + protected function buildDateWithIsoAttribute(DrupalDateTime $date) { + if ($this->getFieldSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE) { + // A date without time will pick up the current time, use the default. + datetime_date_default_time($date); + } + + // Create the ISO date in Universal Time. + $iso_date = $date->format("Y-m-d\TH:i:s") . 'Z'; + + $this->setTimeZone($date); + + $build = [ + '#theme' => 'time', + '#text' => $this->formatDate($date), + '#html' => FALSE, + '#attributes' => [ + 'datetime' => $iso_date, + ], + '#cache' => [ + 'contexts' => [ + 'timezone', + ], + ], + ]; + + return $build; + } + +} diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangeCustomFormatter.php b/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangeCustomFormatter.php new file mode 100644 index 0000000..a23de51 --- /dev/null +++ b/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangeCustomFormatter.php @@ -0,0 +1,96 @@ + '-', + ] + parent::defaultSettings(); + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $elements = []; + $separator = $this->getSetting('separator'); + + foreach ($items as $delta => $item) { + if (!empty($item->start_date) && !empty($item->end_date)) { + /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */ + $start_date = $item->start_date; + /** @var \Drupal\Core\Datetime\DrupalDateTime $end_date */ + $end_date = $item->end_date; + + if ($start_date->format('U') !== $end_date->format('U')) { + $elements[$delta] = [ + 'start_date' => $this->buildDate($start_date), + 'separator' => ['#plain_text' => ' ' . $separator . ' '], + 'end_date' => $this->buildDate($end_date), + ]; + } + else { + $elements[$delta] = $this->buildDate($start_date); + } + } + } + + return $elements; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $form = parent::settingsForm($form, $form_state); + + $form['separator'] = [ + '#type' => 'textfield', + '#title' => $this->t('Date separator'), + '#description' => $this->t('The string to separate the start and end dates'), + '#default_value' => $this->getSetting('separator'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = parent::settingsSummary(); + + if ($separator = $this->getSetting('separator')) { + $summary[] = $this->t('Separator: %separator', ['%separator' => $separator]); + } + + return $summary; + } + +} diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangeDefaultFormatter.php b/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangeDefaultFormatter.php new file mode 100644 index 0000000..f5ebb8c --- /dev/null +++ b/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangeDefaultFormatter.php @@ -0,0 +1,104 @@ + elements, with + * configurable date formats (from the list of configured formats) and a + * separator. + * + * @FieldFormatter( + * id = "daterange_default", + * label = @Translation("Default"), + * field_types = { + * "daterange" + * } + * ) + */ +class DateRangeDefaultFormatter extends DateTimeDefaultFormatter { + + use DateTimeRangeTrait; + + /** + * {@inheritdoc} + */ + public static function defaultSettings() { + return [ + 'separator' => '-', + ] + parent::defaultSettings(); + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $elements = []; + $separator = $this->getSetting('separator'); + + foreach ($items as $delta => $item) { + if (!empty($item->start_date) && !empty($item->end_date)) { + /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */ + $start_date = $item->start_date; + /** @var \Drupal\Core\Datetime\DrupalDateTime $end_date */ + $end_date = $item->end_date; + + if ($start_date->format('U') !== $end_date->format('U')) { + $elements[$delta] = [ + 'start_date' => $this->buildDateWithIsoAttribute($start_date), + 'separator' => ['#plain_text' => ' ' . $separator . ' '], + 'end_date' => $this->buildDateWithIsoAttribute($end_date), + ]; + } + else { + $elements[$delta] = $this->buildDateWithIsoAttribute($start_date); + } + + if (!empty($item->_attributes)) { + $elements[$delta]['#attributes'] += $item->_attributes; + // Unset field item attributes since they have been included in the + // formatter output and should not be rendered in the field template. + unset($item->_attributes); + } + } + } + + return $elements; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $form = parent::settingsForm($form, $form_state); + + $form['separator'] = [ + '#type' => 'textfield', + '#title' => $this->t('Date separator'), + '#description' => $this->t('The string to separate the start and end dates'), + '#default_value' => $this->getSetting('separator'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = parent::settingsSummary(); + + if ($separator = $this->getSetting('separator')) { + $summary[] = $this->t('Separator: %separator', ['%separator' => $separator]); + } + + return $summary; + } + +} diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangePlainFormatter.php b/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangePlainFormatter.php new file mode 100644 index 0000000..4842e1f --- /dev/null +++ b/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangePlainFormatter.php @@ -0,0 +1,96 @@ + '-', + ] + parent::defaultSettings(); + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $elements = []; + $separator = $this->getSetting('separator'); + + foreach ($items as $delta => $item) { + if (!empty($item->start_date) && !empty($item->end_date)) { + /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */ + $start_date = $item->start_date; + /** @var \Drupal\Core\Datetime\DrupalDateTime $end_date */ + $end_date = $item->end_date; + + if ($start_date->format('U') !== $end_date->format('U')) { + $elements[$delta] = [ + 'start_date' => $this->buildDate($start_date), + 'separator' => ['#plain_text' => ' ' . $separator . ' '], + 'end_date' => $this->buildDate($end_date), + ]; + } + else { + $elements[$delta] = $this->buildDate($start_date); + } + } + } + + return $elements; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $form = parent::settingsForm($form, $form_state); + + $form['separator'] = [ + '#type' => 'textfield', + '#title' => $this->t('Date separator'), + '#description' => $this->t('The string to separate the start and end dates'), + '#default_value' => $this->getSetting('separator'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = parent::settingsSummary(); + + if ($separator = $this->getSetting('separator')) { + $summary[] = $this->t('Separator: %separator', ['%separator' => $separator]); + } + + return $summary; + } + +} diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldType/DateRangeFieldItemList.php b/core/modules/datetime_range/src/Plugin/Field/FieldType/DateRangeFieldItemList.php new file mode 100644 index 0000000..e145032 --- /dev/null +++ b/core/modules/datetime_range/src/Plugin/Field/FieldType/DateRangeFieldItemList.php @@ -0,0 +1,131 @@ +getFieldDefinition()->getDefaultValueCallback())) { + $default_value = $this->getFieldDefinition()->getDefaultValueLiteral(); + + $element = parent::defaultValuesForm($form, $form_state); + + $element['default_date_type']['#title'] = $this->t('Default start date'); + $element['default_date_type']['#description'] = $this->t('Set a default value for the start date.'); + + $element['default_end_date_type'] = [ + '#type' => 'select', + '#title' => $this->t('Default end date'), + '#description' => $this->t('Set a default value for the end date.'), + '#default_value' => isset($default_value[0]['default_end_date_type']) ? $default_value[0]['default_end_date_type'] : '', + '#options' => [ + static::DEFAULT_VALUE_NOW => $this->t('Current date'), + static::DEFAULT_VALUE_CUSTOM => $this->t('Relative date'), + ], + '#empty_value' => '', + ]; + + $element['default_end_date'] = [ + '#type' => 'textfield', + '#title' => $this->t('Relative default value'), + '#description' => $this->t("Describe a time by reference to the current day, like '+90 days' (90 days from the day the field is created) or '+1 Saturday' (the next Saturday). See strtotime for more details."), + '#default_value' => (isset($default_value[0]['default_end_date_type']) && $default_value[0]['default_end_date_type'] == static::DEFAULT_VALUE_CUSTOM) ? $default_value[0]['default_end_date'] : '', + '#states' => [ + 'visible' => [ + ':input[id="edit-default-value-input-default-end-date-type"]' => ['value' => static::DEFAULT_VALUE_CUSTOM], + ], + ], + ]; + + return $element; + } + } + + /** + * {@inheritdoc} + */ + public function defaultValuesFormValidate(array $element, array &$form, FormStateInterface $form_state) { + if ($form_state->getValue(['default_value_input', 'default_date_type']) == static::DEFAULT_VALUE_CUSTOM) { + $is_strtotime = @strtotime($form_state->getValue(['default_value_input', 'default_date'])); + if (!$is_strtotime) { + $form_state->setErrorByName('default_value_input][default_date', $this->t('The relative start date value entered is invalid.')); + } + } + + if ($form_state->getValue(['default_value_input', 'default_end_date_type']) == static::DEFAULT_VALUE_CUSTOM) { + $is_strtotime = @strtotime($form_state->getValue(['default_value_input', 'default_end_date'])); + if (!$is_strtotime) { + $form_state->setErrorByName('default_value_input][default_end_date', $this->t('The relative end date value entered is invalid.')); + } + } + } + + /** + * {@inheritdoc} + */ + public function defaultValuesFormSubmit(array $element, array &$form, FormStateInterface $form_state) { + if ($form_state->getValue(['default_value_input', 'default_date_type']) || $form_state->getValue(['default_value_input', 'default_end_date_type'])) { + if ($form_state->getValue(['default_value_input', 'default_date_type']) == static::DEFAULT_VALUE_NOW) { + $form_state->setValueForElement($element['default_date'], static::DEFAULT_VALUE_NOW); + } + if ($form_state->getValue(['default_value_input', 'default_end_date_type']) == static::DEFAULT_VALUE_NOW) { + $form_state->setValueForElement($element['default_end_date'], static::DEFAULT_VALUE_NOW); + } + return [$form_state->getValue('default_value_input')]; + } + return []; + } + + /** + * {@inheritdoc} + */ + public static function processDefaultValue($default_value, FieldableEntityInterface $entity, FieldDefinitionInterface $definition) { + // Explicitly call the base class so that we can get the default value + // types. + $default_value = FieldItemList::processDefaultValue($default_value, $entity, $definition); + + // Allow either the start or end date to have a default, but not require + // defaults for both. + if (!empty($default_value[0]['default_date_type']) || !empty($default_value[0]['default_end_date_type'])) { + // A default value should be in the format and timezone used for date + // storage. All-day ranges are stored the same as date+time ranges. We + // only provide a default value for the first item, as do all fields. + // Otherwise, there is no way to clear out unwanted values on multiple + // value fields. + $storage_format = $definition->getSetting('datetime_type') == DateRangeItem::DATETIME_TYPE_DATE ? DATETIME_DATE_STORAGE_FORMAT : DATETIME_DATETIME_STORAGE_FORMAT; + $default_values = [[]]; + + if (!empty($default_value[0]['default_date_type'])) { + $start_date = new DrupalDateTime($default_value[0]['default_date'], DATETIME_STORAGE_TIMEZONE); + $start_value = $start_date->format($storage_format); + $default_values[0]['value'] = $start_value; + $default_values[0]['start_date'] = $start_date; + } + + if (!empty($default_value[0]['default_end_date_type'])) { + $end_date = new DrupalDateTime($default_value[0]['default_end_date'], DATETIME_STORAGE_TIMEZONE); + $end_value = $end_date->format($storage_format); + $default_values[0]['end_value'] = $end_value; + $default_values[0]['end_date'] = $end_date; + } + + $default_value = $default_values; + } + + return $default_value; + } + +} diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldType/DateRangeItem.php b/core/modules/datetime_range/src/Plugin/Field/FieldType/DateRangeItem.php new file mode 100644 index 0000000..49c4dce --- /dev/null +++ b/core/modules/datetime_range/src/Plugin/Field/FieldType/DateRangeItem.php @@ -0,0 +1,132 @@ +setLabel(t('Start date value')) + ->setRequired(TRUE); + + $properties['start_date'] = DataDefinition::create('any') + ->setLabel(t('Computed start date')) + ->setDescription(t('The computed start DateTime object.')) + ->setComputed(TRUE) + ->setClass(DateTimeComputed::class) + ->setSetting('date source', 'value'); + + $properties['end_value'] = DataDefinition::create('datetime_iso8601') + ->setLabel(t('End date value')) + ->setRequired(TRUE); + + $properties['end_date'] = DataDefinition::create('any') + ->setLabel(t('Computed end date')) + ->setDescription(t('The computed end DateTime object.')) + ->setComputed(TRUE) + ->setClass(DateTimeComputed::class) + ->setSetting('date source', 'end_value'); + + return $properties; + } + + /** + * {@inheritdoc} + */ + public static function schema(FieldStorageDefinitionInterface $field_definition) { + $schema = parent::schema($field_definition); + + $schema['columns']['value']['description'] = 'The start date value.'; + + $schema['columns']['end_value'] = [ + 'description' => 'The end date value.', + ] + $schema['columns']['value']; + + $schema['indexes']['end_value'] = ['end_value']; + + return $schema; + } + + /** + * {@inheritdoc} + */ + public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) { + $element = parent::storageSettingsForm($form, $form_state, $has_data); + + $element['datetime_type']['#options'][static::DATETIME_TYPE_ALLDAY] = $this->t('All Day'); + + return $element; + } + + /** + * {@inheritdoc} + */ + public static function generateSampleValue(FieldDefinitionInterface $field_definition) { + $type = $field_definition->getSetting('datetime_type'); + + // Just pick a date in the past year. No guidance is provided by this Field + // type. + $start = REQUEST_TIME - mt_rand(0, 86400 * 365) - 86400; + $end = $start + 86400; + if ($type == static::DATETIME_TYPE_DATETIME) { + $values['value'] = gmdate(DATETIME_DATETIME_STORAGE_FORMAT, $start); + $values['end_value'] = gmdate(DATETIME_DATETIME_STORAGE_FORMAT, $end); + } + else { + $values['value'] = gmdate(DATETIME_DATE_STORAGE_FORMAT, $start); + $values['end_value'] = gmdate(DATETIME_DATE_STORAGE_FORMAT, $end); + } + return $values; + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + $start_value = $this->get('value')->getValue(); + $end_value = $this->get('end_value')->getValue(); + return ($start_value === NULL || $start_value === '') && ($end_value === NULL || $end_value === ''); + } + + /** + * {@inheritdoc} + */ + public function onChange($property_name, $notify = TRUE) { + // Enforce that the computed date is recalculated. + if ($property_name == 'value') { + $this->start_date = NULL; + } + elseif ($property_name == 'end_value') { + $this->end_date = NULL; + } + parent::onChange($property_name, $notify); + } + +} diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDatelistWidget.php b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDatelistWidget.php new file mode 100644 index 0000000..3e0e6e4 --- /dev/null +++ b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDatelistWidget.php @@ -0,0 +1,157 @@ + '15', + 'date_order' => 'YMD', + 'time_type' => '24', + ] + parent::defaultSettings(); + } + + /** + * {@inheritdoc} + */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + $element = parent::formElement($items, $delta, $element, $form, $form_state); + + $date_order = $this->getSetting('date_order'); + + if ($this->getFieldSetting('datetime_type') == DateRangeItem::DATETIME_TYPE_DATETIME) { + $time_type = $this->getSetting('time_type'); + $increment = $this->getSetting('increment'); + } + else { + $time_type = ''; + $increment = ''; + } + + // Set up the date part order array. + switch ($date_order) { + default: + case 'YMD': + $date_part_order = ['year', 'month', 'day']; + break; + + case 'MDY': + $date_part_order = ['month', 'day', 'year']; + break; + + case 'DMY': + $date_part_order = ['day', 'month', 'year']; + break; + } + switch ($time_type) { + case '24': + $date_part_order = array_merge($date_part_order, ['hour', 'minute']); + break; + + case '12': + $date_part_order = array_merge($date_part_order, ['hour', 'minute', 'ampm']); + break; + + case 'none': + break; + } + + $element['value'] = [ + '#type' => 'datelist', + '#date_increment' => $increment, + '#date_part_order' => $date_part_order, + ] + $element['value']; + + $element['end_value'] = [ + '#type' => 'datelist', + '#date_increment' => $increment, + '#date_part_order' => $date_part_order, + ] + $element['end_value']; + + return $element; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $element = parent::settingsForm($form, $form_state); + + $element['date_order'] = [ + '#type' => 'select', + '#title' => $this->t('Date part order'), + '#default_value' => $this->getSetting('date_order'), + '#options' => ['MDY' => $this->t('Month/Day/Year'), 'DMY' => $this->t('Day/Month/Year'), 'YMD' => $this->t('Year/Month/Day')], + ]; + + if ($this->getFieldSetting('datetime_type') == DateRangeItem::DATETIME_TYPE_DATETIME) { + $element['time_type'] = [ + '#type' => 'select', + '#title' => $this->t('Time type'), + '#default_value' => $this->getSetting('time_type'), + '#options' => ['24' => $this->t('24 hour time'), '12' => $this->t('12 hour time')], + ]; + + $element['increment'] = [ + '#type' => 'select', + '#title' => $this->t('Time increments'), + '#default_value' => $this->getSetting('increment'), + '#options' => [ + 1 => $this->t('1 minute'), + 5 => $this->t('5 minute'), + 10 => $this->t('10 minute'), + 15 => $this->t('15 minute'), + 30 => $this->t('30 minute'), + ], + ]; + } + else { + $element['time_type'] = [ + '#type' => 'hidden', + '#value' => 'none', + ]; + + $element['increment'] = [ + '#type' => 'hidden', + '#value' => $this->getSetting('increment'), + ]; + } + + return $element; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = []; + + $summary[] = $this->t('Date part order: @order', ['@order' => $this->getSetting('date_order')]); + if ($this->getFieldSetting('datetime_type') == DateRangeItem::DATETIME_TYPE_DATETIME) { + $summary[] = $this->t('Time type: @time_type', ['@time_type' => $this->getSetting('time_type')]); + $summary[] = $this->t('Time increments: @increment', ['@increment' => $this->getSetting('increment')]); + } + + return $summary; + } + +} diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php new file mode 100644 index 0000000..79f7994 --- /dev/null +++ b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php @@ -0,0 +1,101 @@ +dateStorage = $date_storage; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $plugin_id, + $plugin_definition, + $configuration['field_definition'], + $configuration['settings'], + $configuration['third_party_settings'], + $container->get('entity_type.manager')->getStorage('date_format') + ); + } + + /** + * {@inheritdoc} + */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + $element = parent::formElement($items, $delta, $element, $form, $form_state); + + // Identify the type of date and time elements to use. + switch ($this->getFieldSetting('datetime_type')) { + case DateRangeItem::DATETIME_TYPE_DATE: + case DateRangeItem::DATETIME_TYPE_ALLDAY: + $date_type = 'date'; + $time_type = 'none'; + $date_format = $this->dateStorage->load('html_date')->getPattern(); + $time_format = ''; + break; + + default: + $date_type = 'date'; + $time_type = 'time'; + $date_format = $this->dateStorage->load('html_date')->getPattern(); + $time_format = $this->dateStorage->load('html_time')->getPattern(); + break; + } + + $element['value'] += [ + '#date_date_format' => $date_format, + '#date_date_element' => $date_type, + '#date_date_callbacks' => [], + '#date_time_format' => $time_format, + '#date_time_element' => $time_type, + '#date_time_callbacks' => [], + ]; + + $element['end_value'] += [ + '#date_date_format' => $date_format, + '#date_date_element' => $date_type, + '#date_date_callbacks' => [], + '#date_time_format' => $time_format, + '#date_time_element' => $time_type, + '#date_time_callbacks' => [], + ]; + + return $element; + } + +} diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php new file mode 100644 index 0000000..ae9f24a --- /dev/null +++ b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php @@ -0,0 +1,167 @@ +t('Start'); + + $element['end_value'] = [ + '#title' => $this->t('End'), + ] + $element['value']; + + if ($items[$delta]->start_date) { + /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */ + $start_date = $items[$delta]->start_date; + $element['value']['#default_value'] = $this->createDefaultValue($start_date, $element['value']['#date_timezone']); + } + + if ($items[$delta]->end_date) { + /** @var \Drupal\Core\Datetime\DrupalDateTime $end_date */ + $end_date = $items[$delta]->end_date; + $element['end_value']['#default_value'] = $this->createDefaultValue($end_date, $element['end_value']['#date_timezone']); + } + + return $element; + } + + /** + * {@inheritdoc} + */ + public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { + // The widget form element type has transformed the value to a + // DrupalDateTime object at this point. We need to convert it back to the + // storage timezone and format. + foreach ($values as &$item) { + if (!empty($item['value']) && $item['value'] instanceof DrupalDateTime) { + /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */ + $start_date = $item['value']; + switch ($this->getFieldSetting('datetime_type')) { + case DateRangeItem::DATETIME_TYPE_DATE: + // If this is a date-only field, set it to the default time so the + // timezone conversion can be reversed. + datetime_date_default_time($start_date); + $format = DATETIME_DATE_STORAGE_FORMAT; + break; + + case DateRangeItem::DATETIME_TYPE_ALLDAY: + // All day fields start at midnight on the starting date, but are + // stored like datetime fields, so we need to adjust the time. + // This function is called twice, so to prevent a double conversion + // we need to explicitly set the timezone. + $start_date->setTimeZone(timezone_open(drupal_get_user_timezone())); + $start_date->setTime(0, 0, 0); + $format = DATETIME_DATETIME_STORAGE_FORMAT; + break; + + default: + $format = DATETIME_DATETIME_STORAGE_FORMAT; + break; + } + // Adjust the date for storage. + $start_date->setTimezone(new \DateTimezone(DATETIME_STORAGE_TIMEZONE)); + $item['value'] = $start_date->format($format); + } + + if (!empty($item['end_value']) && $item['end_value'] instanceof DrupalDateTime) { + /** @var \Drupal\Core\Datetime\DrupalDateTime $end_date */ + $end_date = $item['end_value']; + switch ($this->getFieldSetting('datetime_type')) { + case DateRangeItem::DATETIME_TYPE_DATE: + // If this is a date-only field, set it to the default time so the + // timezone conversion can be reversed. + datetime_date_default_time($end_date); + $format = DATETIME_DATE_STORAGE_FORMAT; + break; + + case DateRangeItem::DATETIME_TYPE_ALLDAY: + // All day fields end at midnight on the end date, but are + // stored like datetime fields, so we need to adjust the time. + // This function is called twice, so to prevent a double conversion + // we need to explicitly set the timezone. + $end_date->setTimeZone(timezone_open(drupal_get_user_timezone())); + $end_date->setTime(23, 59, 59); + $format = DATETIME_DATETIME_STORAGE_FORMAT; + break; + + default: + $format = DATETIME_DATETIME_STORAGE_FORMAT; + break; + } + // Adjust the date for storage. + $end_date->setTimezone(new \DateTimezone(DATETIME_STORAGE_TIMEZONE)); + $item['end_value'] = $end_date->format($format); + } + } + + return $values; + } + + /** + * #element_validate callback to ensure that the start date <= the end date. + * + * @param array $element + * An associative array containing the properties and children of the + * generic form element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param array $complete_form + * The complete form structure. + */ + public function validateStartEnd(array &$element, FormStateInterface $form_state, array &$complete_form) { + $start_date = $element['value']['#value']['object']; + $end_date = $element['end_value']['#value']['object']; + + if ($start_date instanceof DrupalDateTime && $end_date instanceof DrupalDateTime) { + if ($start_date->format('U') !== $end_date->format('U')) { + $interval = $start_date->diff($end_date); + if ($interval->invert === 1) { + $form_state->setError($element, $this->t('The @title end date cannot be before the start date', ['@title' => $element['#title']])); + } + } + } + } + + /** + * Creates a date object for use as a default value. + * + * This will take a default value, apply the proper timezone for display in + * a widget, and set the default time for date-only fields. + * + * @param \Drupal\Core\Datetime\DrupalDateTime $date + * The UTC default date. + * @param string $timezone + * The timezone to apply. + * + * @return \Drupal\Core\Datetime\DrupalDateTime + * A date object for use as a default value in a field widget. + */ + protected function createDefaultValue($date, $timezone) { + // The date was created and verified during field_load(), so it is safe to + // use without further inspection. + if ($this->getFieldSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE) { + // A date without time will pick up the current time, use the default + // time. + datetime_date_default_time($date); + } + $date->setTimezone(new \DateTimeZone($timezone)); + return $date; + } + +} diff --git a/core/modules/datetime_range/src/Tests/DateRangeFieldTest.php b/core/modules/datetime_range/src/Tests/DateRangeFieldTest.php new file mode 100644 index 0000000..078a67e --- /dev/null +++ b/core/modules/datetime_range/src/Tests/DateRangeFieldTest.php @@ -0,0 +1,1432 @@ +drupalCreateUser([ + 'access content', + 'view test entity', + 'administer entity_test content', + 'administer entity_test form display', + 'administer content types', + 'administer node fields', + ]); + $this->drupalLogin($web_user); + + // Create a field with settings to validate. + $field_name = Unicode::strtolower($this->randomMachineName()); + $this->fieldStorage = FieldStorageConfig::create([ + 'field_name' => $field_name, + 'entity_type' => 'entity_test', + 'type' => 'daterange', + 'settings' => ['datetime_type' => DateRangeItem::DATETIME_TYPE_DATE], + ]); + $this->fieldStorage->save(); + $this->field = FieldConfig::create([ + 'field_storage' => $this->fieldStorage, + 'bundle' => 'entity_test', + 'required' => TRUE, + ]); + $this->field->save(); + + entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default') + ->setComponent($field_name, [ + 'type' => 'daterange_default', + ]) + ->save(); + + $this->defaultSettings = [ + 'separator' => '-', + 'timezone_override' => '', + ]; + + $this->displayOptions = [ + 'type' => 'daterange_default', + 'label' => 'hidden', + 'settings' => ['format_type' => 'medium'] + $this->defaultSettings, + ]; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + + $this->dateFormatter = \Drupal::service('date.formatter'); + } + + /** + * Tests date field functionality. + */ + public function testDateRangeField() { + $field_name = $this->fieldStorage->getName(); + + // Loop through defined timezones to test that date-only fields work at the + // extremes. + foreach (static::$timezones as $timezone) { + + $this->setSiteTimezone($timezone); + + // Ensure field is set to a date-only field. + $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE); + $this->fieldStorage->save(); + + // Display creation form. + $this->drupalGet('entity_test/add'); + $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.'); + $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.'); + $this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]/h4[contains(@class, "js-form-required")]', TRUE, 'Required markup found'); + $this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Start time element not found.'); + $this->assertNoFieldByName("{$field_name}[0][end_value][time]", '', 'End time element not found.'); + + // Build up dates in the UTC timezone. + $value = '2012-12-31 00:00:00'; + $start_date = new DrupalDateTime($value, 'UTC'); + $end_value = '2013-06-06 00:00:00'; + $end_date = new DrupalDateTime($end_value, 'UTC'); + + // Submit a valid date and ensure it is accepted. + $date_format = DateFormat::load('html_date')->getPattern(); + $time_format = DateFormat::load('html_time')->getPattern(); + + $edit = array( + "{$field_name}[0][value][date]" => $start_date->format($date_format), + "{$field_name}[0][end_value][date]" => $end_date->format($date_format), + ); + $this->drupalPostForm(NULL, $edit, t('Save')); + preg_match('|entity_test/manage/(\d+)|', $this->url, $match); + $id = $match[1]; + $this->assertText(t('entity_test @id has been created.', array('@id' => $id))); + $this->assertRaw($start_date->format($date_format)); + $this->assertNoRaw($start_date->format($time_format)); + $this->assertRaw($end_date->format($date_format)); + $this->assertNoRaw($end_date->format($time_format)); + + // Verify the date doesn't change when entity is edited through the form. + $entity = EntityTest::load($id); + $this->assertEqual('2012-12-31', $entity->{$field_name}->value); + $this->assertEqual('2013-06-06', $entity->{$field_name}->end_value); + $this->drupalGet('entity_test/manage/' . $id . '/edit'); + $this->drupalPostForm(NULL, [], t('Save')); + $this->drupalGet('entity_test/manage/' . $id . '/edit'); + $this->drupalPostForm(NULL, [], t('Save')); + $this->drupalGet('entity_test/manage/' . $id . '/edit'); + $this->drupalPostForm(NULL, [], t('Save')); + $entity = EntityTest::load($id); + $this->assertEqual('2012-12-31', $entity->{$field_name}->value); + $this->assertEqual('2013-06-06', $entity->{$field_name}->end_value); + + // Formats that display a time component for date-only fields will display + // the default time, so that is applied before calculating the expected + // value. + datetime_date_default_time($start_date); + datetime_date_default_time($end_date); + + // Reset display options since these get changed below. + $this->displayOptions = [ + 'type' => 'daterange_default', + 'label' => 'hidden', + 'settings' => [ + 'format_type' => 'long', + 'separator' => 'THESEPARATOR', + ] + $this->defaultSettings, + ]; + + // Verify that the default formatter works. + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + + $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long', '', DATETIME_STORAGE_TIMEZONE); + $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DATETIME_STORAGE_TIMEZONE); + $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long', '', DATETIME_STORAGE_TIMEZONE); + $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DATETIME_STORAGE_TIMEZONE); + $this->renderTestEntity($id); + $this->assertFieldByXPath('//time[@datetime="' . $start_expected_iso . '"]', $start_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', [ + '%value' => 'long', + '%expected' => $start_expected, + '%expected_iso' => $start_expected_iso, + ])); + $this->assertFieldByXPath('//time[@datetime="' . $end_expected_iso . '"]', $end_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', [ + '%value' => 'long', + '%expected' => $end_expected, + '%expected_iso' => $end_expected_iso, + ])); + $this->assertText(' THESEPARATOR ', 'Found proper separator'); + + // Verify that the plain formatter works. + $this->displayOptions['type'] = 'daterange_plain'; + $this->displayOptions['settings'] = $this->defaultSettings; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + $expected = $start_date->format(DATETIME_DATE_STORAGE_FORMAT) . ' - ' . $end_date->format(DATETIME_DATE_STORAGE_FORMAT); + $this->renderTestEntity($id); + $this->assertText($expected, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', array('%expected' => $expected))); + + // Verify that the custom formatter works. + $this->displayOptions['type'] = 'daterange_custom'; + $this->displayOptions['settings'] = array('date_format' => 'm/d/Y') + $this->defaultSettings; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' - ' . $end_date->format($this->displayOptions['settings']['date_format']); + $this->renderTestEntity($id); + $this->assertText($expected, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', array('%expected' => $expected))); + + // Test formatters when start date and end date are the same + $this->drupalGet('entity_test/add'); + $value = '2012-12-31 00:00:00'; + $start_date = new DrupalDateTime($value, 'UTC'); + + $date_format = DateFormat::load('html_date')->getPattern(); + $time_format = DateFormat::load('html_time')->getPattern(); + + $edit = array( + "{$field_name}[0][value][date]" => $start_date->format($date_format), + "{$field_name}[0][end_value][date]" => $start_date->format($date_format), + ); + + $this->drupalPostForm(NULL, $edit, t('Save')); + preg_match('|entity_test/manage/(\d+)|', $this->url, $match); + $id = $match[1]; + $this->assertText(t('entity_test @id has been created.', array('@id' => $id))); + + datetime_date_default_time($start_date); + + $this->displayOptions = [ + 'type' => 'daterange_default', + 'label' => 'hidden', + 'settings' => [ + 'format_type' => 'long', + 'separator' => 'THESEPARATOR', + ] + $this->defaultSettings, + ]; + + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + + $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long', '', DATETIME_STORAGE_TIMEZONE); + $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DATETIME_STORAGE_TIMEZONE); + $this->renderTestEntity($id); + $this->assertFieldByXPath('//time[@datetime="' . $start_expected_iso . '"]', $start_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', [ + '%value' => 'long', + '%expected' => $start_expected, + '%expected_iso' => $start_expected_iso, + ])); + $this->assertNoText(' THESEPARATOR ', 'Separator not found on page'); + + $this->displayOptions['type'] = 'daterange_plain'; + $this->displayOptions['settings'] = $this->defaultSettings; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + $expected = $start_date->format(DATETIME_DATE_STORAGE_FORMAT); + $this->renderTestEntity($id); + $this->assertText($expected, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', array('%expected' => $expected))); + $this->assertNoText(' THESEPARATOR ', 'Separator not found on page'); + + $this->displayOptions['type'] = 'daterange_custom'; + $this->displayOptions['settings'] = array('date_format' => 'm/d/Y') + $this->defaultSettings; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + $expected = $start_date->format($this->displayOptions['settings']['date_format']); + $this->renderTestEntity($id); + $this->assertText($expected, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', array('%expected' => $expected))); + $this->assertNoText(' THESEPARATOR ', 'Separator not found on page'); + } + } + + /** + * Tests date and time field. + */ + public function testDatetimeRangeField() { + $field_name = $this->fieldStorage->getName(); + + // Ensure the field to a datetime field. + $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME); + $this->fieldStorage->save(); + + // Display creation form. + $this->drupalGet('entity_test/add'); + $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.'); + $this->assertFieldByName("{$field_name}[0][value][time]", '', 'Start time element found.'); + $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.'); + $this->assertFieldByName("{$field_name}[0][end_value][time]", '', 'End time element found.'); + + // Build up dates in the UTC timezone. + $value = '2012-12-31 00:00:00'; + $start_date = new DrupalDateTime($value, 'UTC'); + $end_value = '2013-06-06 00:00:00'; + $end_date = new DrupalDateTime($end_value, 'UTC'); + + // Update the timezone to the system default. + $start_date->setTimezone(timezone_open(drupal_get_user_timezone())); + $end_date->setTimezone(timezone_open(drupal_get_user_timezone())); + + // Submit a valid date and ensure it is accepted. + $date_format = DateFormat::load('html_date')->getPattern(); + $time_format = DateFormat::load('html_time')->getPattern(); + + $edit = array( + "{$field_name}[0][value][date]" => $start_date->format($date_format), + "{$field_name}[0][value][time]" => $start_date->format($time_format), + "{$field_name}[0][end_value][date]" => $end_date->format($date_format), + "{$field_name}[0][end_value][time]" => $end_date->format($time_format), + ); + $this->drupalPostForm(NULL, $edit, t('Save')); + preg_match('|entity_test/manage/(\d+)|', $this->url, $match); + $id = $match[1]; + $this->assertText(t('entity_test @id has been created.', array('@id' => $id))); + $this->assertRaw($start_date->format($date_format)); + $this->assertRaw($start_date->format($time_format)); + $this->assertRaw($end_date->format($date_format)); + $this->assertRaw($end_date->format($time_format)); + + // Verify that the default formatter works. + $this->displayOptions['settings'] = [ + 'format_type' => 'long', + 'separator' => 'THESEPARATOR', + ] + $this->defaultSettings; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + + $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long'); + $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC'); + $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long'); + $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC'); + $this->renderTestEntity($id); + $this->assertFieldByXPath('//time[@datetime="' . $start_expected_iso . '"]', $start_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso])); + $this->assertFieldByXPath('//time[@datetime="' . $end_expected_iso . '"]', $end_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $end_expected, '%expected_iso' => $end_expected_iso])); + $this->assertText(' THESEPARATOR ', 'Found proper separator'); + + // Verify that the plain formatter works. + $this->displayOptions['type'] = 'daterange_plain'; + $this->displayOptions['settings'] = $this->defaultSettings; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + $expected = $start_date->format(DATETIME_DATETIME_STORAGE_FORMAT) . ' - ' . $end_date->format(DATETIME_DATETIME_STORAGE_FORMAT); + $this->renderTestEntity($id); + $this->assertText($expected, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', array('%expected' => $expected))); + + // Verify that the 'datetime_custom' formatter works. + $this->displayOptions['type'] = 'daterange_custom'; + $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A'] + $this->defaultSettings; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' - ' . $end_date->format($this->displayOptions['settings']['date_format']); + $this->renderTestEntity($id); + $this->assertText($expected, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', array('%expected' => $expected))); + + // Verify that the 'timezone_override' setting works. + $this->displayOptions['type'] = 'daterange_custom'; + $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York'] + $this->defaultSettings; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + $expected = $start_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']); + $expected .= ' - ' . $end_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']); + $this->renderTestEntity($id); + $this->assertText($expected, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', array('%expected' => $expected))); + + // Test formatters when start date and end date are the same + $this->drupalGet('entity_test/add'); + $value = '2012-12-31 00:00:00'; + $start_date = new DrupalDateTime($value, 'UTC'); + $start_date->setTimezone(timezone_open(drupal_get_user_timezone())); + + $date_format = DateFormat::load('html_date')->getPattern(); + $time_format = DateFormat::load('html_time')->getPattern(); + + $edit = array( + "{$field_name}[0][value][date]" => $start_date->format($date_format), + "{$field_name}[0][value][time]" => $start_date->format($time_format), + "{$field_name}[0][end_value][date]" => $start_date->format($date_format), + "{$field_name}[0][end_value][time]" => $start_date->format($time_format), + ); + + $this->drupalPostForm(NULL, $edit, t('Save')); + preg_match('|entity_test/manage/(\d+)|', $this->url, $match); + $id = $match[1]; + $this->assertText(t('entity_test @id has been created.', array('@id' => $id))); + + $this->displayOptions = [ + 'type' => 'daterange_default', + 'label' => 'hidden', + 'settings' => [ + 'format_type' => 'long', + 'separator' => 'THESEPARATOR', + ] + $this->defaultSettings, + ]; + + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + + $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long'); + $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC'); + $this->renderTestEntity($id); + $this->assertFieldByXPath('//time[@datetime="' . $start_expected_iso . '"]', $start_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso])); + $this->assertNoText(' THESEPARATOR ', 'Separator not found on page'); + + $this->displayOptions['type'] = 'daterange_plain'; + $this->displayOptions['settings'] = $this->defaultSettings; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + $expected = $start_date->format(DATETIME_DATETIME_STORAGE_FORMAT); + $this->renderTestEntity($id); + $this->assertText($expected, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', array('%expected' => $expected))); + $this->assertNoText(' THESEPARATOR ', 'Separator not found on page'); + + $this->displayOptions['type'] = 'daterange_custom'; + $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A'] + $this->defaultSettings; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + $expected = $start_date->format($this->displayOptions['settings']['date_format']); + $this->renderTestEntity($id); + $this->assertText($expected, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', array('%expected' => $expected))); + $this->assertNoText(' THESEPARATOR ', 'Separator not found on page'); + } + + /** + * Tests all-day field. + */ + public function testAlldayRangeField() { + $field_name = $this->fieldStorage->getName(); + + // Ensure field is set to a all-day field. + $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_ALLDAY); + $this->fieldStorage->save(); + + // Display creation form. + $this->drupalGet('entity_test/add'); + $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.'); + $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.'); + $this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]/h4[contains(@class, "js-form-required")]', TRUE, 'Required markup found'); + $this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Start time element not found.'); + $this->assertNoFieldByName("{$field_name}[0][end_value][time]", '', 'End time element not found.'); + + // Build up dates in the proper timezone. + $value = '2012-12-31 00:00:00'; + $start_date = new DrupalDateTime($value, timezone_open(drupal_get_user_timezone())); + $end_value = '2013-06-06 23:59:59'; + $end_date = new DrupalDateTime($end_value, timezone_open(drupal_get_user_timezone())); + + // Submit a valid date and ensure it is accepted. + $date_format = DateFormat::load('html_date')->getPattern(); + $time_format = DateFormat::load('html_time')->getPattern(); + + $edit = array( + "{$field_name}[0][value][date]" => $start_date->format($date_format), + "{$field_name}[0][end_value][date]" => $end_date->format($date_format), + ); + $this->drupalPostForm(NULL, $edit, t('Save')); + preg_match('|entity_test/manage/(\d+)|', $this->url, $match); + $id = $match[1]; + $this->assertText(t('entity_test @id has been created.', array('@id' => $id))); + $this->assertRaw($start_date->format($date_format)); + $this->assertNoRaw($start_date->format($time_format)); + $this->assertRaw($end_date->format($date_format)); + $this->assertNoRaw($end_date->format($time_format)); + + // Verify that the default formatter works. + $this->displayOptions['settings'] = [ + 'format_type' => 'long', + 'separator' => 'THESEPARATOR', + ] + $this->defaultSettings; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + + $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long'); + $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC'); + $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long'); + $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC'); + $this->renderTestEntity($id); + $this->assertFieldByXPath('//time[@datetime="' . $start_expected_iso . '"]', $start_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso])); + $this->assertFieldByXPath('//time[@datetime="' . $end_expected_iso . '"]', $end_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $end_expected, '%expected_iso' => $end_expected_iso])); + $this->assertText(' THESEPARATOR ', 'Found proper separator'); + + // Verify that the plain formatter works. + $this->displayOptions['type'] = 'daterange_plain'; + $this->displayOptions['settings'] = $this->defaultSettings; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + $expected = $start_date->format(DATETIME_DATETIME_STORAGE_FORMAT) . ' - ' . $end_date->format(DATETIME_DATETIME_STORAGE_FORMAT); + $this->renderTestEntity($id); + $this->assertText($expected, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', array('%expected' => $expected))); + + // Verify that the custom formatter works. + $this->displayOptions['type'] = 'daterange_custom'; + $this->displayOptions['settings'] = array('date_format' => 'm/d/Y') + $this->defaultSettings; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' - ' . $end_date->format($this->displayOptions['settings']['date_format']); + $this->renderTestEntity($id); + $this->assertText($expected, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', array('%expected' => $expected))); + + // Verify that the 'timezone_override' setting works. + $this->displayOptions['type'] = 'daterange_custom'; + $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York'] + $this->defaultSettings; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + $expected = $start_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']); + $expected .= ' - ' . $end_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']); + $this->renderTestEntity($id); + $this->assertText($expected, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', array('%expected' => $expected))); + + // Test formatters when start date and end date are the same + $this->drupalGet('entity_test/add'); + + $value = '2012-12-31 00:00:00'; + $start_date = new DrupalDateTime($value, timezone_open(drupal_get_user_timezone())); + $end_value = '2012-12-31 23:59:59'; + $end_date = new DrupalDateTime($end_value, timezone_open(drupal_get_user_timezone())); + + $date_format = DateFormat::load('html_date')->getPattern(); + $time_format = DateFormat::load('html_time')->getPattern(); + + $edit = array( + "{$field_name}[0][value][date]" => $start_date->format($date_format), + "{$field_name}[0][end_value][date]" => $start_date->format($date_format), + ); + $this->drupalPostForm(NULL, $edit, t('Save')); + preg_match('|entity_test/manage/(\d+)|', $this->url, $match); + $id = $match[1]; + $this->assertText(t('entity_test @id has been created.', array('@id' => $id))); + + $this->displayOptions = [ + 'type' => 'daterange_default', + 'label' => 'hidden', + 'settings' => [ + 'format_type' => 'long', + 'separator' => 'THESEPARATOR', + ] + $this->defaultSettings, + ]; + + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + + $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long'); + $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC'); + $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long'); + $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC'); + $this->renderTestEntity($id); + $this->assertFieldByXPath('//time[@datetime="' . $start_expected_iso . '"]', $start_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso])); + $this->assertFieldByXPath('//time[@datetime="' . $end_expected_iso . '"]', $end_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $end_expected, '%expected_iso' => $end_expected_iso])); + $this->assertText(' THESEPARATOR ', 'Found proper separator'); + + $this->displayOptions['type'] = 'daterange_plain'; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + $expected = $start_date->format(DATETIME_DATETIME_STORAGE_FORMAT) . ' THESEPARATOR ' . $end_date->format(DATETIME_DATETIME_STORAGE_FORMAT); + $this->renderTestEntity($id); + $this->assertText($expected, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', array('%expected' => $expected))); + $this->assertText(' THESEPARATOR ', 'Found proper separator'); + + $this->displayOptions['type'] = 'daterange_custom'; + $this->displayOptions['settings']['date_format'] = 'm/d/Y'; + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' THESEPARATOR ' . $end_date->format($this->displayOptions['settings']['date_format']); + $this->renderTestEntity($id); + $this->assertText($expected, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', array('%expected' => $expected))); + $this->assertText(' THESEPARATOR ', 'Found proper separator'); + + } + + /** + * Tests Date Range List Widget functionality. + */ + public function testDatelistWidget() { + $field_name = $this->fieldStorage->getName(); + + // Ensure field is set to a date only field. + $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE); + $this->fieldStorage->save(); + + // Change the widget to a datelist widget. + entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default') + ->setComponent($field_name, [ + 'type' => 'daterange_datelist', + 'settings' => [ + 'date_order' => 'YMD', + ], + ]) + ->save(); + \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); + + // Display creation form. + $this->drupalGet('entity_test/add'); + + // Assert that Hour and Minute Elements do not appear on Date Only. + $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element not found on Date Only.'); + $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-minute\"]", NULL, 'Minute element not found on Date Only.'); + $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-hour\"]", NULL, 'Hour element not found on Date Only.'); + $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-minute\"]", NULL, 'Minute element not found on Date Only.'); + + // Go to the form display page to assert that increment option does not + // appear on Date Only. + $fieldEditUrl = 'entity_test/structure/entity_test/form-display'; + $this->drupalGet($fieldEditUrl); + + // Click on the widget settings button to open the widget settings form. + $this->drupalPostAjaxForm(NULL, [], $field_name . "_settings_edit"); + $xpathIncr = "//select[starts-with(@id, \"edit-fields-$field_name-settings-edit-form-settings-increment\")]"; + $this->assertNoFieldByXPath($xpathIncr, NULL, 'Increment element not found for Date Only.'); + + // Change the field is set to an all day field. + $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_ALLDAY); + $this->fieldStorage->save(); + + // Change the widget to a datelist widget. + entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default') + ->setComponent($field_name, [ + 'type' => 'daterange_datelist', + 'settings' => [ + 'date_order' => 'YMD', + ], + ]) + ->save(); + \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); + + // Display creation form. + $this->drupalGet('entity_test/add'); + + // Assert that Hour and Minute Elements do not appear on Date Only. + $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element not found on Date Only.'); + $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-minute\"]", NULL, 'Minute element not found on Date Only.'); + $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-hour\"]", NULL, 'Hour element not found on Date Only.'); + $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-minute\"]", NULL, 'Minute element not found on Date Only.'); + + // Go to the form display page to assert that increment option does not + // appear on Date Only. + $fieldEditUrl = 'entity_test/structure/entity_test/form-display'; + $this->drupalGet($fieldEditUrl); + + // Click on the widget settings button to open the widget settings form. + $this->drupalPostAjaxForm(NULL, [], $field_name . "_settings_edit"); + $xpathIncr = "//select[starts-with(@id, \"edit-fields-$field_name-settings-edit-form-settings-increment\")]"; + $this->assertNoFieldByXPath($xpathIncr, NULL, 'Increment element not found for Date Only.'); + + // Change the field to a datetime field. + $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME); + $this->fieldStorage->save(); + + // Change the widget to a datelist widget. + entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default') + ->setComponent($field_name, [ + 'type' => 'daterange_datelist', + 'settings' => [ + 'increment' => 1, + 'date_order' => 'YMD', + 'time_type' => '12', + ], + ]) + ->save(); + \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); + + // Go to the form display page to assert that increment option does appear + // on Date Time. + $fieldEditUrl = 'entity_test/structure/entity_test/form-display'; + $this->drupalGet($fieldEditUrl); + + // Click on the widget settings button to open the widget settings form. + $this->drupalPostAjaxForm(NULL, [], $field_name . "_settings_edit"); + $this->assertFieldByXPath($xpathIncr, NULL, 'Increment element found for Date and time.'); + + // Display creation form. + $this->drupalGet('entity_test/add'); + + foreach (['value', 'end-value'] as $column) { + foreach (['year', 'month', 'day', 'hour', 'minute', 'ampm'] as $element) { + $this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-$column-$element\"]", NULL, $element . ' element found.'); + $this->assertOptionSelected("edit-$field_name-0-$column-$element", '', 'No ' . $element . ' selected.'); + } + } + + // Submit a valid date and ensure it is accepted. + $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 5, 'minute' => 15]; + $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 30]; + + $edit = []; + // Add the ampm indicator since we are testing 12 hour time. + $start_date_value['ampm'] = 'am'; + $end_date_value['ampm'] = 'pm'; + foreach ($start_date_value as $part => $value) { + $edit["{$field_name}[0][value][$part]"] = $value; + } + foreach ($end_date_value as $part => $value) { + $edit["{$field_name}[0][end_value][$part]"] = $value; + } + + $this->drupalPostForm(NULL, $edit, t('Save')); + preg_match('|entity_test/manage/(\d+)|', $this->url, $match); + $id = $match[1]; + $this->assertText(t('entity_test @id has been created.', ['@id' => $id])); + + $this->assertOptionSelected("edit-$field_name-0-value-year", '2012', 'Correct year selected.'); + $this->assertOptionSelected("edit-$field_name-0-value-month", '12', 'Correct month selected.'); + $this->assertOptionSelected("edit-$field_name-0-value-day", '31', 'Correct day selected.'); + $this->assertOptionSelected("edit-$field_name-0-value-hour", '5', 'Correct hour selected.'); + $this->assertOptionSelected("edit-$field_name-0-value-minute", '15', 'Correct minute selected.'); + $this->assertOptionSelected("edit-$field_name-0-value-ampm", 'am', 'Correct ampm selected.'); + + $this->assertOptionSelected("edit-$field_name-0-end-value-year", '2013', 'Correct year selected.'); + $this->assertOptionSelected("edit-$field_name-0-end-value-month", '1', 'Correct month selected.'); + $this->assertOptionSelected("edit-$field_name-0-end-value-day", '15', 'Correct day selected.'); + $this->assertOptionSelected("edit-$field_name-0-end-value-hour", '3', 'Correct hour selected.'); + $this->assertOptionSelected("edit-$field_name-0-end-value-minute", '30', 'Correct minute selected.'); + $this->assertOptionSelected("edit-$field_name-0-end-value-ampm", 'pm', 'Correct ampm selected.'); + + // Test the widget using increment other than 1 and 24 hour mode. + entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default') + ->setComponent($field_name, [ + 'type' => 'daterange_datelist', + 'settings' => [ + 'increment' => 15, + 'date_order' => 'YMD', + 'time_type' => '24', + ], + ]) + ->save(); + \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); + + // Display creation form. + $this->drupalGet('entity_test/add'); + + // Other elements are unaffected by the changed settings. + $this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element found.'); + $this->assertOptionSelected("edit-$field_name-0-value-hour", '', 'No hour selected.'); + $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-ampm\"]", NULL, 'AMPM element not found.'); + $this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-hour\"]", NULL, 'Hour element found.'); + $this->assertOptionSelected("edit-$field_name-0-end-value-hour", '', 'No hour selected.'); + $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-ampm\"]", NULL, 'AMPM element not found.'); + + // Submit a valid date and ensure it is accepted. + $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 17, 'minute' => 15]; + $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 30]; + + $edit = []; + foreach ($start_date_value as $part => $value) { + $edit["{$field_name}[0][value][$part]"] = $value; + } + foreach ($end_date_value as $part => $value) { + $edit["{$field_name}[0][end_value][$part]"] = $value; + } + + $this->drupalPostForm(NULL, $edit, t('Save')); + preg_match('|entity_test/manage/(\d+)|', $this->url, $match); + $id = $match[1]; + $this->assertText(t('entity_test @id has been created.', ['@id' => $id])); + + $this->assertOptionSelected("edit-$field_name-0-value-year", '2012', 'Correct year selected.'); + $this->assertOptionSelected("edit-$field_name-0-value-month", '12', 'Correct month selected.'); + $this->assertOptionSelected("edit-$field_name-0-value-day", '31', 'Correct day selected.'); + $this->assertOptionSelected("edit-$field_name-0-value-hour", '17', 'Correct hour selected.'); + $this->assertOptionSelected("edit-$field_name-0-value-minute", '15', 'Correct minute selected.'); + + $this->assertOptionSelected("edit-$field_name-0-end-value-year", '2013', 'Correct year selected.'); + $this->assertOptionSelected("edit-$field_name-0-end-value-month", '1', 'Correct month selected.'); + $this->assertOptionSelected("edit-$field_name-0-end-value-day", '15', 'Correct day selected.'); + $this->assertOptionSelected("edit-$field_name-0-end-value-hour", '3', 'Correct hour selected.'); + $this->assertOptionSelected("edit-$field_name-0-end-value-minute", '30', 'Correct minute selected.'); + + // Test the widget for partial completion of fields. + entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default') + ->setComponent($field_name, [ + 'type' => 'daterange_datelist', + 'settings' => [ + 'increment' => 1, + 'date_order' => 'YMD', + 'time_type' => '24', + ], + ]) + ->save(); + \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); + + // Test the widget for validation notifications. + foreach ($this->datelistDataProvider() as $data) { + list($start_date_value, $end_date_value, $expected) = $data; + + // Display creation form. + $this->drupalGet('entity_test/add'); + + // Submit a partial date and ensure and error message is provided. + $edit = []; + foreach ($start_date_value as $part => $value) { + $edit["{$field_name}[0][value][$part]"] = $value; + } + foreach ($end_date_value as $part => $value) { + $edit["{$field_name}[0][end_value][$part]"] = $value; + } + + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertResponse(200); + foreach ($expected as $expected_text) { + $this->assertText(t($expected_text)); + } + } + + // Test the widget for complete input with zeros as part of selections. + $this->drupalGet('entity_test/add'); + + $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 0, 'minute' => 0]; + $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 30]; + $edit = []; + foreach ($start_date_value as $part => $value) { + $edit["{$field_name}[0][value][$part]"] = $value; + } + foreach ($end_date_value as $part => $value) { + $edit["{$field_name}[0][end_value][$part]"] = $value; + } + + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertResponse(200); + preg_match('|entity_test/manage/(\d+)|', $this->url, $match); + $id = $match[1]; + $this->assertText(t('entity_test @id has been created.', ['@id' => $id])); + + // Test the widget to ensure zeros are not deselected on validation. + $this->drupalGet('entity_test/add'); + + $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 0, 'minute' => 0]; + $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 0]; + $edit = []; + foreach ($start_date_value as $part => $value) { + $edit["{$field_name}[0][value][$part]"] = $value; + } + foreach ($end_date_value as $part => $value) { + $edit["{$field_name}[0][end_value][$part]"] = $value; + } + + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertResponse(200); + $this->assertOptionSelected("edit-$field_name-0-value-minute", '0', 'Correct minute selected.'); + $this->assertOptionSelected("edit-$field_name-0-end-value-minute", '0', 'Correct minute selected.'); + } + + /** + * The data provider for testing the validation of the datelist widget. + * + * @return array + * An array of datelist input permutations to test. + */ + protected function datelistDataProvider() { + return [ + // Year only selected, validation error on Month, Day, Hour, Minute. + [ + ['year' => 2012, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''], + ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [ + 'A value must be selected for month.', + 'A value must be selected for day.', + 'A value must be selected for hour.', + 'A value must be selected for minute.', + ], + ], + // Year and Month selected, validation error on Day, Hour, Minute. + [ + ['year' => 2012, 'month' => '12', 'day' => '', 'hour' => '', 'minute' => ''], + ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [ + 'A value must be selected for day.', + 'A value must be selected for hour.', + 'A value must be selected for minute.', + ], + ], + // Year, Month and Day selected, validation error on Hour, Minute. + [ + ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '', 'minute' => ''], + ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [ + 'A value must be selected for hour.', + 'A value must be selected for minute.', + ], + ], + // Year, Month, Day and Hour selected, validation error on Minute only. + [ + ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => ''], + ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [ + 'A value must be selected for minute.', + ], + ], + // Year selected, validation error on Month, Day, Hour, Minute. + [ + ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'], + ['year' => 2013, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''], [ + 'A value must be selected for month.', + 'A value must be selected for day.', + 'A value must be selected for hour.', + 'A value must be selected for minute.', + ], + ], + // Year and Month selected, validation error on Day, Hour, Minute. + [ + ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'], + ['year' => 2013, 'month' => '1', 'day' => '', 'hour' => '', 'minute' => ''], [ + 'A value must be selected for day.', + 'A value must be selected for hour.', + 'A value must be selected for minute.', + ], + ], + // Year, Month and Day selected, validation error on Hour, Minute. + [ + ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'], + ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '', 'minute' => ''], [ + 'A value must be selected for hour.', + 'A value must be selected for minute.', + ], + ], + // Year, Month, Day and Hour selected, validation error on Minute only. + [ + ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'], + ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => ''], [ + 'A value must be selected for minute.', + ], + ], + ]; + } + + /** + * Test default value functionality. + */ + public function testDefaultValue() { + // Create a test content type. + $this->drupalCreateContentType(['type' => 'date_content']); + + // Create a field storage with settings to validate. + $field_name = Unicode::strtolower($this->randomMachineName()); + $field_storage = FieldStorageConfig::create([ + 'field_name' => $field_name, + 'entity_type' => 'node', + 'type' => 'daterange', + 'settings' => ['datetime_type' => DateRangeItem::DATETIME_TYPE_DATE], + ]); + $field_storage->save(); + + $field = FieldConfig::create([ + 'field_storage' => $field_storage, + 'bundle' => 'date_content', + ]); + $field->save(); + + // Set now as default_value. + $field_edit = [ + 'default_value_input[default_date_type]' => 'now', + 'default_value_input[default_end_date_type]' => 'now', + ]; + $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings')); + + // Check that default value is selected in default value form. + $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name); + $this->assertOptionSelected('edit-default-value-input-default-date-type', 'now', 'The default start value is selected in instance settings page'); + $this->assertFieldByName('default_value_input[default_date]', '', 'The relative start default value is empty in instance settings page'); + $this->assertOptionSelected('edit-default-value-input-default-end-date-type', 'now', 'The default end value is selected in instance settings page'); + $this->assertFieldByName('default_value_input[default_end_date]', '', 'The relative end default value is empty in instance settings page'); + + // Check if default_date has been stored successfully. + $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get(); + $this->assertEqual($config_entity['default_value'][0], [ + 'default_date_type' => 'now', + 'default_date' => 'now', + 'default_end_date_type' => 'now', + 'default_end_date' => 'now', + ], 'Default value has been stored successfully'); + + // Clear field cache in order to avoid stale cache values. + \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); + + // Create a new node to check that datetime field default value is today. + $new_node = Node::create(['type' => 'date_content']); + $expected_date = new DrupalDateTime('now', DATETIME_STORAGE_TIMEZONE); + $this->assertEqual($new_node->get($field_name)->offsetGet(0)->value, $expected_date->format(DATETIME_DATE_STORAGE_FORMAT)); + $this->assertEqual($new_node->get($field_name)->offsetGet(0)->end_value, $expected_date->format(DATETIME_DATE_STORAGE_FORMAT)); + + // Set an invalid relative default_value to test validation. + $field_edit = [ + 'default_value_input[default_date_type]' => 'relative', + 'default_value_input[default_date]' => 'invalid date', + 'default_value_input[default_end_date_type]' => 'relative', + 'default_value_input[default_end_date]' => '+1 day', + ]; + $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings')); + $this->assertText('The relative start date value entered is invalid.'); + + $field_edit = [ + 'default_value_input[default_date_type]' => 'relative', + 'default_value_input[default_date]' => '+1 day', + 'default_value_input[default_end_date_type]' => 'relative', + 'default_value_input[default_end_date]' => 'invalid date', + ]; + $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings')); + $this->assertText('The relative end date value entered is invalid.'); + + // Set a relative default_value. + $field_edit = [ + 'default_value_input[default_date_type]' => 'relative', + 'default_value_input[default_date]' => '+45 days', + 'default_value_input[default_end_date_type]' => 'relative', + 'default_value_input[default_end_date]' => '+90 days', + ]; + $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings')); + + // Check that default value is selected in default value form. + $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name); + $this->assertOptionSelected('edit-default-value-input-default-date-type', 'relative', 'The default start value is selected in instance settings page'); + $this->assertFieldByName('default_value_input[default_date]', '+45 days', 'The relative default start value is displayed in instance settings page'); + $this->assertOptionSelected('edit-default-value-input-default-end-date-type', 'relative', 'The default end value is selected in instance settings page'); + $this->assertFieldByName('default_value_input[default_end_date]', '+90 days', 'The relative default end value is displayed in instance settings page'); + + // Check if default_date has been stored successfully. + $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get(); + $this->assertEqual($config_entity['default_value'][0], [ + 'default_date_type' => 'relative', + 'default_date' => '+45 days', + 'default_end_date_type' => 'relative', + 'default_end_date' => '+90 days', + ], 'Default value has been stored successfully'); + + // Clear field cache in order to avoid stale cache values. + \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); + + // Create a new node to check that datetime field default value is +90 days. + $new_node = Node::create(['type' => 'date_content']); + $expected_start_date = new DrupalDateTime('+45 days', DATETIME_STORAGE_TIMEZONE); + $expected_end_date = new DrupalDateTime('+90 days', DATETIME_STORAGE_TIMEZONE); + $this->assertEqual($new_node->get($field_name)->offsetGet(0)->value, $expected_start_date->format(DATETIME_DATE_STORAGE_FORMAT)); + $this->assertEqual($new_node->get($field_name)->offsetGet(0)->end_value, $expected_end_date->format(DATETIME_DATE_STORAGE_FORMAT)); + + // Remove default value. + $field_edit = [ + 'default_value_input[default_date_type]' => '', + 'default_value_input[default_end_date_type]' => '', + ]; + $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings')); + + // Check that default value is selected in default value form. + $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name); + $this->assertOptionSelected('edit-default-value-input-default-date-type', '', 'The default start value is selected in instance settings page'); + $this->assertFieldByName('default_value_input[default_date]', '', 'The relative default start value is empty in instance settings page'); + $this->assertOptionSelected('edit-default-value-input-default-end-date-type', '', 'The default end value is selected in instance settings page'); + $this->assertFieldByName('default_value_input[default_end_date]', '', 'The relative default end value is empty in instance settings page'); + + // Check if default_date has been stored successfully. + $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get(); + $this->assertTrue(empty($config_entity['default_value']), 'Empty default value has been stored successfully'); + + // Clear field cache in order to avoid stale cache values. + \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); + + // Create a new node to check that datetime field default value is not set. + $new_node = Node::create(['type' => 'date_content']); + $this->assertNull($new_node->get($field_name)->value, 'Default value is not set'); + + // Set now as default_value for start date only. + entity_get_form_display('node', 'date_content', 'default') + ->setComponent($field_name, [ + 'type' => 'datetime_default', + ]) + ->save(); + + $expected_date = new DrupalDateTime('now', DATETIME_STORAGE_TIMEZONE); + + $field_edit = [ + 'default_value_input[default_date_type]' => 'now', + 'default_value_input[default_end_date_type]' => '', + ]; + $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings')); + + // Make sure only the start value is populated on node add page. + $this->drupalGet('node/add/date_content'); + $this->assertFieldByName("{$field_name}[0][value][date]", $expected_date->format(DATETIME_DATE_STORAGE_FORMAT), 'Start date element populated.'); + $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element empty.'); + + // Set now as default_value for end date only. + $field_edit = [ + 'default_value_input[default_date_type]' => '', + 'default_value_input[default_end_date_type]' => 'now', + ]; + $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings')); + + // Make sure only the start value is populated on node add page. + $this->drupalGet('node/add/date_content'); + $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element empty.'); + $this->assertFieldByName("{$field_name}[0][end_value][date]", $expected_date->format(DATETIME_DATE_STORAGE_FORMAT), 'End date element populated.'); + } + + /** + * Test that invalid values are caught and marked as invalid. + */ + public function testInvalidField() { + // Change the field to a datetime field. + $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME); + $this->fieldStorage->save(); + $field_name = $this->fieldStorage->getName(); + + $this->drupalGet('entity_test/add'); + $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.'); + $this->assertFieldByName("{$field_name}[0][value][time]", '', 'Start time element found.'); + $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.'); + $this->assertFieldByName("{$field_name}[0][end_value][time]", '', 'End time element found.'); + + // Submit invalid start dates and ensure they is not accepted. + $date_value = ''; + $edit = [ + "{$field_name}[0][value][date]" => $date_value, + "{$field_name}[0][value][time]" => '12:00:00', + "{$field_name}[0][end_value][date]" => '2012-12-01', + "{$field_name}[0][end_value][time]" => '12:00:00', + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', 'Empty start date value has been caught.'); + + $date_value = 'aaaa-12-01'; + $edit = [ + "{$field_name}[0][value][date]" => $date_value, + "{$field_name}[0][value][time]" => '00:00:00', + "{$field_name}[0][end_value][date]" => '2012-12-01', + "{$field_name}[0][end_value][time]" => '12:00:00', + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', new FormattableMarkup('Invalid start year value %date has been caught.', ['%date' => $date_value])); + + $date_value = '2012-75-01'; + $edit = [ + "{$field_name}[0][value][date]" => $date_value, + "{$field_name}[0][value][time]" => '00:00:00', + "{$field_name}[0][end_value][date]" => '2012-12-01', + "{$field_name}[0][end_value][time]" => '12:00:00', + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', new FormattableMarkup('Invalid start month value %date has been caught.', ['%date' => $date_value])); + + $date_value = '2012-12-99'; + $edit = [ + "{$field_name}[0][value][date]" => $date_value, + "{$field_name}[0][value][time]" => '00:00:00', + "{$field_name}[0][end_value][date]" => '2012-12-01', + "{$field_name}[0][end_value][time]" => '12:00:00', + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', new FormattableMarkup('Invalid start day value %date has been caught.', ['%date' => $date_value])); + + // Submit invalid start times and ensure they is not accepted. + $time_value = ''; + $edit = [ + "{$field_name}[0][value][date]" => '2012-12-01', + "{$field_name}[0][value][time]" => $time_value, + "{$field_name}[0][end_value][date]" => '2012-12-01', + "{$field_name}[0][end_value][time]" => '12:00:00', + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', 'Empty start time value has been caught.'); + + $time_value = '49:00:00'; + $edit = [ + "{$field_name}[0][value][date]" => '2012-12-01', + "{$field_name}[0][value][time]" => $time_value, + "{$field_name}[0][end_value][date]" => '2012-12-01', + "{$field_name}[0][end_value][time]" => '12:00:00', + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', new FormattableMarkup('Invalid start hour value %time has been caught.', ['%time' => $time_value])); + + $time_value = '12:99:00'; + $edit = [ + "{$field_name}[0][value][date]" => '2012-12-01', + "{$field_name}[0][value][time]" => $time_value, + "{$field_name}[0][end_value][date]" => '2012-12-01', + "{$field_name}[0][end_value][time]" => '12:00:00', + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', new FormattableMarkup('Invalid start minute value %time has been caught.', ['%time' => $time_value])); + + $time_value = '12:15:99'; + $edit = [ + "{$field_name}[0][value][date]" => '2012-12-01', + "{$field_name}[0][value][time]" => $time_value, + "{$field_name}[0][end_value][date]" => '2012-12-01', + "{$field_name}[0][end_value][time]" => '12:00:00', + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', new FormattableMarkup('Invalid start second value %time has been caught.', ['%time' => $time_value])); + + // Submit invalid end dates and ensure they is not accepted. + $date_value = ''; + $edit = [ + "{$field_name}[0][value][date]" => '2012-12-01', + "{$field_name}[0][value][time]" => '12:00:00', + "{$field_name}[0][end_value][date]" => $date_value, + "{$field_name}[0][end_value][time]" => '12:00:00', + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', 'Empty end date value has been caught.'); + + $date_value = 'aaaa-12-01'; + $edit = [ + "{$field_name}[0][value][date]" => '2012-12-01', + "{$field_name}[0][value][time]" => '12:00:00', + "{$field_name}[0][end_value][date]" => $date_value, + "{$field_name}[0][end_value][time]" => '00:00:00', + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', new FormattableMarkup('Invalid end year value %date has been caught.', ['%date' => $date_value])); + + $date_value = '2012-75-01'; + $edit = [ + "{$field_name}[0][value][date]" => '2012-12-01', + "{$field_name}[0][value][time]" => '12:00:00', + "{$field_name}[0][end_value][date]" => $date_value, + "{$field_name}[0][end_value][time]" => '00:00:00', + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', new FormattableMarkup('Invalid end month value %date has been caught.', ['%date' => $date_value])); + + $date_value = '2012-12-99'; + $edit = [ + "{$field_name}[0][value][date]" => '2012-12-01', + "{$field_name}[0][value][time]" => '12:00:00', + "{$field_name}[0][end_value][date]" => $date_value, + "{$field_name}[0][end_value][time]" => '00:00:00', + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', new FormattableMarkup('Invalid end day value %date has been caught.', ['%date' => $date_value])); + + // Submit invalid start times and ensure they is not accepted. + $time_value = ''; + $edit = [ + "{$field_name}[0][value][date]" => '2012-12-01', + "{$field_name}[0][value][time]" => '12:00:00', + "{$field_name}[0][end_value][date]" => '2012-12-01', + "{$field_name}[0][end_value][time]" => $time_value, + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', 'Empty end time value has been caught.'); + + $time_value = '49:00:00'; + $edit = [ + "{$field_name}[0][value][date]" => '2012-12-01', + "{$field_name}[0][value][time]" => '12:00:00', + "{$field_name}[0][end_value][date]" => '2012-12-01', + "{$field_name}[0][end_value][time]" => $time_value, + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', new FormattableMarkup('Invalid end hour value %time has been caught.', ['%time' => $time_value])); + + $time_value = '12:99:00'; + $edit = [ + "{$field_name}[0][value][date]" => '2012-12-01', + "{$field_name}[0][value][time]" => '12:00:00', + "{$field_name}[0][end_value][date]" => '2012-12-01', + "{$field_name}[0][end_value][time]" => $time_value, + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', new FormattableMarkup('Invalid end minute value %time has been caught.', ['%time' => $time_value])); + + $time_value = '12:15:99'; + $edit = [ + "{$field_name}[0][value][date]" => '2012-12-01', + "{$field_name}[0][value][time]" => '12:00:00', + "{$field_name}[0][end_value][date]" => '2012-12-01', + "{$field_name}[0][end_value][time]" => $time_value, + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('date is invalid', new FormattableMarkup('Invalid end second value %time has been caught.', ['%time' => $time_value])); + + $edit = [ + "{$field_name}[0][value][date]" => '2012-12-01', + "{$field_name}[0][value][time]" => '12:00:00', + "{$field_name}[0][end_value][date]" => '2010-12-01', + "{$field_name}[0][end_value][time]" => '12:00:00', + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText(new FormattableMarkup('The @title end date cannot be before the start date', ['@title' => $field_name]), 'End date before start date has been caught.'); + + $edit = [ + "{$field_name}[0][value][date]" => '2012-12-01', + "{$field_name}[0][value][time]" => '12:00:00', + "{$field_name}[0][end_value][date]" => '2012-12-01', + "{$field_name}[0][end_value][time]" => '11:00:00', + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText(new FormattableMarkup('The @title end date cannot be before the start date', ['@title' => $field_name]), 'End time before start time has been caught.'); + } + + /** + * Tests that 'Date' field storage setting form is disabled if field has data. + */ + public function testDateStorageSettings() { + // Create a test content type. + $this->drupalCreateContentType(['type' => 'date_content']); + + // Create a field storage with settings to validate. + $field_name = Unicode::strtolower($this->randomMachineName()); + $field_storage = FieldStorageConfig::create([ + 'field_name' => $field_name, + 'entity_type' => 'node', + 'type' => 'daterange', + 'settings' => [ + 'datetime_type' => DateRangeItem::DATETIME_TYPE_DATE, + ], + ]); + $field_storage->save(); + $field = FieldConfig::create([ + 'field_storage' => $field_storage, + 'field_name' => $field_name, + 'bundle' => 'date_content', + ]); + $field->save(); + + entity_get_form_display('node', 'date_content', 'default') + ->setComponent($field_name, [ + 'type' => 'datetime_default', + ]) + ->save(); + $edit = [ + 'title[0][value]' => $this->randomString(), + 'body[0][value]' => $this->randomString(), + $field_name . '[0][value][date]' => '2016-04-01', + $field_name . '[0][end_value][date]' => '2016-04-02', + ]; + $this->drupalPostForm('node/add/date_content', $edit, t('Save')); + $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name . '/storage'); + $result = $this->xpath("//*[@id='edit-settings-datetime-type' and contains(@disabled, 'disabled')]"); + $this->assertEqual(count($result), 1, "Changing datetime setting is disabled."); + $this->assertText('There is data for this field in the database. The field settings can no longer be changed.'); + } + + /** + * Renders a entity_test and sets the output in the internal browser. + * + * @param int $id + * The entity_test ID to render. + * @param string $view_mode + * (optional) The view mode to use for rendering. Defaults to 'full'. + * @param bool $reset + * (optional) Whether to reset the entity_test controller cache. Defaults to + * TRUE to simplify testing. + */ + protected function renderTestEntity($id, $view_mode = 'full', $reset = TRUE) { + if ($reset) { + \Drupal::service('entity_type.manager')->getStorage('entity_test')->resetCache([$id]); + } + $entity = EntityTest::load($id); + $display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode); + $build = $display->build($entity); + $output = \Drupal::service('renderer')->renderRoot($build); + $this->setRawContent($output); + $this->verbose($output); + } + + /** + * Sets the site timezone to a given timezone. + * + * @param string $timezone + * The timezone identifier to set. + */ + protected function setSiteTimezone($timezone) { + // Set an explicit site timezone, and disallow per-user timezones. + $this->config('system.date') + ->set('timezone.user.configurable', 0) + // A timezone with an offset greater than UTC+12 is used. + ->set('timezone.default', $timezone) + ->save(); + } + +} diff --git a/core/modules/dblog/src/Controller/DbLogController.php b/core/modules/dblog/src/Controller/DbLogController.php index 572b9ae..36ee204 100644 --- a/core/modules/dblog/src/Controller/DbLogController.php +++ b/core/modules/dblog/src/Controller/DbLogController.php @@ -343,14 +343,18 @@ protected function buildFilterQuery() { */ public function formatMessage($row) { // Check for required properties. - if (isset($row->message) && isset($row->variables)) { + if (isset($row->message, $row->variables)) { + $variables = @unserialize($row->variables); // Messages without variables or user specified text. - if ($row->variables === 'N;') { + if ($variables === NULL) { $message = Xss::filterAdmin($row->message); } + elseif (!is_array($variables)) { + $message = $this->t('Log data is corrupted and cannot be unserialized: @message', ['@message' => Xss::filterAdmin($row->message)]); + } // Message to translate with injected variables. else { - $message = $this->t(Xss::filterAdmin($row->message), unserialize($row->variables)); + $message = $this->t(Xss::filterAdmin($row->message), $variables); } } else { diff --git a/core/modules/dblog/src/Tests/DbLogTest.php b/core/modules/dblog/src/Tests/DbLogTest.php index 90cb6c2..886f768 100644 --- a/core/modules/dblog/src/Tests/DbLogTest.php +++ b/core/modules/dblog/src/Tests/DbLogTest.php @@ -236,14 +236,14 @@ protected function clearLogsEntries() { * (optional) The log entry severity. */ protected function filterLogsEntries($type = NULL, $severity = NULL) { - $edit = array(); - if (!is_null($type)) { - $edit['type[]'] = $type; - } - if (!is_null($severity)) { - $edit['severity[]'] = $severity; - } - $this->drupalPostForm(NULL, $edit, t('Filter')); + $edit = array(); + if (!is_null($type)) { + $edit['type[]'] = $type; + } + if (!is_null($severity)) { + $edit['severity[]'] = $severity; + } + $this->drupalPostForm(NULL, $edit, t('Filter')); } /** diff --git a/core/modules/dblog/tests/src/Kernel/DbLogControllerTest.php b/core/modules/dblog/tests/src/Kernel/DbLogControllerTest.php new file mode 100644 index 0000000..d6d88fa --- /dev/null +++ b/core/modules/dblog/tests/src/Kernel/DbLogControllerTest.php @@ -0,0 +1,41 @@ +installEntitySchema('user'); + $dblog_controller = DbLogController::create($this->container); + + // Check message with properly serialized data. + $message = (object) [ + 'message' => 'Sample message with placeholder: @placeholder', + 'variables' => serialize(['@placeholder' => 'test placeholder']), + ]; + + $this->assertEquals('Sample message with placeholder: test placeholder', $dblog_controller->formatMessage($message)); + + // Check that controller work with corrupted data. + $message->variables = 'BAD SERIALIZED DATA'; + $formatted = $dblog_controller->formatMessage($message); + $this->assertEquals('Log data is corrupted and cannot be unserialized: Sample message with placeholder: @placeholder', $formatted); + } + +} diff --git a/core/modules/field/src/FieldStorageConfigStorage.php b/core/modules/field/src/FieldStorageConfigStorage.php index 072b6fa..ebb0371 100644 --- a/core/modules/field/src/FieldStorageConfigStorage.php +++ b/core/modules/field/src/FieldStorageConfigStorage.php @@ -64,7 +64,7 @@ class FieldStorageConfigStorage extends ConfigEntityStorage { * The module handler. * @param \Drupal\Core\State\StateInterface $state * The state key value store. - * @param \Drupal\Component\Plugin\PluginManagerInterface\FieldTypePluginManagerInterface + * @param \Drupal\Component\Plugin\PluginManagerInterface\FieldTypePluginManagerInterface $field_type_manager * The field type plugin manager. */ public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, StateInterface $state, FieldTypePluginManagerInterface $field_type_manager) { diff --git a/core/modules/field/src/Plugin/migrate/process/FieldType.php b/core/modules/field/src/Plugin/migrate/process/FieldType.php index 1296940..ad070d0 100644 --- a/core/modules/field/src/Plugin/migrate/process/FieldType.php +++ b/core/modules/field/src/Plugin/migrate/process/FieldType.php @@ -72,7 +72,8 @@ public function transform($value, MigrateExecutableInterface $migrate_executable $field_type = is_array($value) ? $value[0] : $value; try { - return $this->cckPluginManager->createInstance($field_type, [], $this->migration)->getFieldType($row); + $plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, [], $this->migration); + return $this->cckPluginManager->createInstance($plugin_id, [], $this->migration)->getFieldType($row); } catch (PluginNotFoundException $e) { return parent::transform($value, $migrate_executable, $row, $destination_property); diff --git a/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerViewMode.php b/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerViewMode.php index 93ba4ef..2e1832e 100644 --- a/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerViewMode.php +++ b/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerViewMode.php @@ -59,11 +59,11 @@ public function query() { 'label', 'display_settings', 'widget_settings', - )) - ->fields('cnf', array( + )) + ->fields('cnf', array( 'type', 'module', - )); + )); $query->join('content_node_field', 'cnf', 'cnfi.field_name = cnf.field_name'); $query->orderBy('cnfi.weight'); diff --git a/core/modules/field/src/Tests/EntityReference/EntityReferenceAdminTest.php b/core/modules/field/src/Tests/EntityReference/EntityReferenceAdminTest.php index d30c3cc..0324c4a 100644 --- a/core/modules/field/src/Tests/EntityReference/EntityReferenceAdminTest.php +++ b/core/modules/field/src/Tests/EntityReference/EntityReferenceAdminTest.php @@ -208,6 +208,7 @@ public function testFieldAdminHandler() { 'id' => 'node_test_view', 'label' => 'Node Test View', 'show[wizard_key]' => 'node', + 'show[sort]' => 'none', 'page[create]' => 1, 'page[title]' => 'Test Node View', 'page[path]' => 'test/node/view', @@ -221,6 +222,14 @@ public function testFieldAdminHandler() { 'style_options[search_fields][title]' => 'title', ); $this->drupalPostForm(NULL, $edit, t('Apply')); + + // Set sort to NID ascending. + $edit = [ + 'name[node_field_data.nid]' => 1, + ]; + $this->drupalPostForm('admin/structure/views/nojs/add-handler/node_test_view/entity_reference_1/sort', $edit, t('Add and configure sort criteria')); + $this->drupalPostForm(NULL, NULL, t('Apply')); + $this->drupalPostForm('admin/structure/views/view/node_test_view/edit/entity_reference_1', array(), t('Save')); $this->clickLink(t('Settings')); @@ -301,6 +310,7 @@ public function testFieldAdminHandler() { $this->assertText(t('Multiple entities match this reference;')); $this->assertText(t("@node1", ['@node1' => $node1->getTitle() . ' (' . $node1->id() . ')'])); $this->assertText(t("@node2", ['@node2' => $node2->getTitle() . ' (' . $node2->id() . ')'])); + $this->assertText(t('Specify the one you want by appending the id in parentheses, like "@example".', ['@example' => $node2->getTitle() . ' (' . $node2->id() . ')'])); $edit = array( 'title[0][value]' => 'Test', diff --git a/core/modules/field/src/Tests/EntityReference/EntityReferenceFieldTranslatedReferenceViewTest.php b/core/modules/field/src/Tests/EntityReference/EntityReferenceFieldTranslatedReferenceViewTest.php index d93b2ac..f0856d3 100644 --- a/core/modules/field/src/Tests/EntityReference/EntityReferenceFieldTranslatedReferenceViewTest.php +++ b/core/modules/field/src/Tests/EntityReference/EntityReferenceFieldTranslatedReferenceViewTest.php @@ -223,7 +223,7 @@ protected function setUpEntityReferenceField() { 'bundle' => $this->referrerType->id(), 'entity_type' => $this->testEntityTypeName, ]) - ->save(); + ->save(); entity_get_form_display($this->testEntityTypeName, $this->referrerType->id(), 'default') ->setComponent($this->referenceFieldName, array( 'type' => 'entity_reference_autocomplete', diff --git a/core/modules/field/src/Tests/FormTest.php b/core/modules/field/src/Tests/FormTest.php index 17cbc86..0a3f9af 100644 --- a/core/modules/field/src/Tests/FormTest.php +++ b/core/modules/field/src/Tests/FormTest.php @@ -334,7 +334,7 @@ public function testFieldFormUnlimitedRequired() { $this->assertTrue(isset($element[0]), 'Required symbol added field label.'); // Check that the label of the field input is visually hidden and contains // the field title and an indication of the delta for a11y. - $element = $this->xpath('//label[@for=:for and contains(@class, "js-form-required") and contains(text(), :value)]', array(':for' => 'edit-field-unlimited-0-value', ':value' => $this->field['label'] . ' (value 1)')); + $element = $this->xpath('//label[@for=:for and contains(@class, "visually-hidden") and contains(text(), :value)]', array(':for' => 'edit-field-unlimited-0-value', ':value' => $this->field['label'] . ' (value 1)')); $this->assertTrue(isset($element[0]), 'Required symbol not added for field input.'); } @@ -575,7 +575,7 @@ function testFieldFormAccess() { $this->assertEqual($entity->$field_name->value, 2, 'New revision has the expected value for the field with edit access.'); // Check that the revision is also saved in the revisions table. -// $entity = entity_revision_load($entity_type, $entity->getRevisionId()); + // $entity = entity_revision_load($entity_type, $entity->getRevisionId()); $this->assertEqual($entity->$field_name_no_access->value, 99, 'New revision has the expected value for the field with no edit access.'); $this->assertEqual($entity->$field_name->value, 2, 'New revision has the expected value for the field with edit access.'); } diff --git a/core/modules/field/tests/src/Kernel/BulkDeleteTest.php b/core/modules/field/tests/src/Kernel/BulkDeleteTest.php index 78af244..b01926a 100644 --- a/core/modules/field/tests/src/Kernel/BulkDeleteTest.php +++ b/core/modules/field/tests/src/Kernel/BulkDeleteTest.php @@ -212,6 +212,98 @@ function testDeleteField() { } /** + * Tests that recreating a field with the name as a deleted field works. + */ + public function testPurgeWithDeletedAndActiveField() { + $bundle = reset($this->bundles); + // Create another field storage. + $field_name = 'bf_3'; + $deleted_field_storage = FieldStorageConfig::create(array( + 'field_name' => $field_name, + 'entity_type' => $this->entityTypeId, + 'type' => 'test_field', + 'cardinality' => 1 + )); + $deleted_field_storage->save(); + // Create the field. + FieldConfig::create([ + 'field_storage' => $deleted_field_storage, + 'bundle' => $bundle, + ])->save(); + + for ($i = 0; $i < 20; $i++) { + $entity = $this->container->get('entity_type.manager') + ->getStorage($this->entityTypeId) + ->create(array('type' => $bundle)); + $entity->{$field_name}->setValue($this->_generateTestFieldValues(1)); + $entity->save(); + } + + // Delete the field. + $deleted_field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name); + $deleted_field->delete(); + $deleted_field_uuid = $deleted_field->uuid(); + + // Reload the field storage. + $field_storages = entity_load_multiple_by_properties('field_storage_config', array('uuid' => $deleted_field_storage->uuid(), 'include_deleted' => TRUE)); + $deleted_field_storage = reset($field_storages); + + // Create the field again. + $field_storage = FieldStorageConfig::create(array( + 'field_name' => $field_name, + 'entity_type' => $this->entityTypeId, + 'type' => 'test_field', + 'cardinality' => 1 + )); + $field_storage->save(); + FieldConfig::create([ + 'field_storage' => $field_storage, + 'bundle' => $bundle, + ])->save(); + + // The field still exists, deleted, with the same field name. + $fields = entity_load_multiple_by_properties('field_config', array('uuid' => $deleted_field_uuid, 'include_deleted' => TRUE)); + $this->assertTrue(isset($fields[$deleted_field_uuid]) && $fields[$deleted_field_uuid]->isDeleted(), 'The field exists and is deleted'); + $this->assertTrue(isset($fields[$deleted_field_uuid]) && $fields[$deleted_field_uuid]->getName() == $field_name); + + for ($i = 0; $i < 10; $i++) { + $entity = $this->container->get('entity_type.manager') + ->getStorage($this->entityTypeId) + ->create(array('type' => $bundle)); + $entity->{$field_name}->setValue($this->_generateTestFieldValues(1)); + $entity->save(); + } + + // Check that the two field storages have different tables. + $storage = \Drupal::entityManager()->getStorage($this->entityTypeId); + /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ + $table_mapping = $storage->getTableMapping(); + $deleted_table_name = $table_mapping->getDedicatedDataTableName($deleted_field_storage, TRUE); + $active_table_name = $table_mapping->getDedicatedDataTableName($field_storage); + + field_purge_batch(50); + + // Ensure the new field still has its table and the deleted one has been + // removed. + $this->assertTrue(\Drupal::database()->schema()->tableExists($active_table_name)); + $this->assertFalse(\Drupal::database()->schema()->tableExists($deleted_table_name)); + + // The field has been removed from the system. + $fields = entity_load_multiple_by_properties('field_config', array('field_storage_uuid' => $deleted_field_storage->uuid(), 'deleted' => TRUE, 'include_deleted' => TRUE)); + $this->assertEqual(count($fields), 0, 'The field is gone'); + + // Verify there are still 10 entries in the main table. + $count = \Drupal::database() + ->select('entity_test__' . $field_name, 'f') + ->fields('f', array('entity_id')) + ->condition('bundle', $bundle) + ->countQuery() + ->execute() + ->fetchField(); + $this->assertEqual($count, 10); + } + + /** * Verify that field data items and fields are purged when a field storage is * deleted. */ diff --git a/core/modules/field/tests/src/Kernel/Migrate/d7/RollbackFieldInstanceTest.php b/core/modules/field/tests/src/Kernel/Migrate/d7/RollbackFieldInstanceTest.php new file mode 100644 index 0000000..e71ac3c --- /dev/null +++ b/core/modules/field/tests/src/Kernel/Migrate/d7/RollbackFieldInstanceTest.php @@ -0,0 +1,80 @@ +executeRollback('d7_field_instance'); + $this->executeRollback('d7_field'); + + // Check that field instances have been rolled back. + $field_instance_ids = [ + 'comment.comment_node_page.comment_body', + 'node.page.body', + 'comment.comment_node_article.comment_body', + 'node.article.body', + 'node.article.field_tags', + 'node.article.field_image', + 'comment.comment_node_blog.comment_body', + 'node.blog.body', + 'comment.comment_node_book.comment_body', + 'node.book.body', + 'node.forum.taxonomy_forums', + 'comment.comment_node_forum.comment_body', + 'node.forum.body', + 'comment.comment_node_test_content_type.comment_body', + 'node.test_content_type.field_boolean', + 'node.test_content_type.field_email', + 'node.test_content_type.field_phone', + 'node.test_content_type.field_date', + 'node.test_content_type.field_date_with_end_time', + 'node.test_content_type.field_file', + 'node.test_content_type.field_float', + 'node.test_content_type.field_images', + 'node.test_content_type.field_integer', + 'node.test_content_type.field_link', + 'node.test_content_type.field_text_list', + 'node.test_content_type.field_integer_list', + 'node.test_content_type.field_long_text', + 'node.test_content_type.field_term_reference', + 'node.test_content_type.field_text', + 'comment.comment_node_test_content_type.field_integer', + 'user.user.field_file', + ]; + foreach ($field_instance_ids as $field_instance_id) { + $this->assertNull(FieldConfig::load($field_instance_id)); + } + } + + /** + * Executes a single rollback. + * + * @param string|\Drupal\migrate\Plugin\MigrationInterface $migration + * The migration to rollback, or its ID. + */ + protected function executeRollback($migration) { + if (is_string($migration)) { + $this->migration = $this->getMigration($migration); + } + else { + $this->migration = $migration; + } + (new MigrateExecutable($this->migration, $this))->rollback(); + } + +} diff --git a/core/modules/field/tests/src/Kernel/Migrate/d7/RollbackFieldTest.php b/core/modules/field/tests/src/Kernel/Migrate/d7/RollbackFieldTest.php new file mode 100644 index 0000000..7c5c6fd4 --- /dev/null +++ b/core/modules/field/tests/src/Kernel/Migrate/d7/RollbackFieldTest.php @@ -0,0 +1,77 @@ +executeRollback('d7_field'); + + // Check that fields have been rolled back. + $rolled_back_field_ids = [ + 'comment.field_integer', + 'node.taxonomy_forums', + 'node.field_integer', + 'node.field_tags', + 'node.field_term_reference', + 'node.field_text_list', + 'node.field_text', + 'node.field_phone', + 'node.field_file', + 'node.field_images', + 'node.field_image', + 'node.field_long_text', + 'node.field_date_with_end_time', + 'node.field_integer_list', + 'node.field_date', + 'node.field_link', + 'node.field_float', + 'node.field_boolean', + 'node.field_email', + 'user.field_file', + ]; + foreach ($rolled_back_field_ids as $field_id) { + $this->assertNull(FieldStorageConfig::load($field_id)); + } + + // Check that fields that should persist have not been rolled back. + $non_rolled_back_field_ids = [ + 'node.body', + 'comment.comment_body', + ]; + foreach ($non_rolled_back_field_ids as $field_id) { + $this->assertNotNull(FieldStorageConfig::load($field_id)); + } + } + + /** + * Executes a single rollback. + * + * @param string|\Drupal\migrate\Plugin\MigrationInterface $migration + * The migration to rollback, or its ID. + */ + protected function executeRollback($migration) { + if (is_string($migration)) { + $this->migration = $this->getMigration($migration); + } + else { + $this->migration = $migration; + } + (new MigrateExecutable($this->migration, $this))->rollback(); + } + +} diff --git a/core/modules/field/tests/src/Kernel/Migrate/d7/RollbackViewModesTest.php b/core/modules/field/tests/src/Kernel/Migrate/d7/RollbackViewModesTest.php new file mode 100644 index 0000000..b069387 --- /dev/null +++ b/core/modules/field/tests/src/Kernel/Migrate/d7/RollbackViewModesTest.php @@ -0,0 +1,52 @@ +executeRollback('d7_view_modes'); + + // Check that view modes have been rolled back. + $view_mode_ids = [ + 'comment.full', + 'node.teaser', + 'node.full', + 'user.full', + ]; + foreach ($view_mode_ids as $view_mode_id) { + $this->assertNull(EntityViewMode::load($view_mode_id)); + } + } + + /** + * Executes a single rollback. + * + * @param string|\Drupal\migrate\Plugin\MigrationInterface $migration + * The migration to rollback, or its ID. + */ + protected function executeRollback($migration) { + if (is_string($migration)) { + $this->migration = $this->getMigration($migration); + } + else { + $this->migration = $migration; + } + (new MigrateExecutable($this->migration, $this))->rollback(); + } + +} diff --git a/core/modules/field_ui/src/Form/FieldConfigEditForm.php b/core/modules/field_ui/src/Form/FieldConfigEditForm.php index 827bf1b..ff6d157 100644 --- a/core/modules/field_ui/src/Form/FieldConfigEditForm.php +++ b/core/modules/field_ui/src/Form/FieldConfigEditForm.php @@ -167,7 +167,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $default_value = $items->defaultValuesFormSubmit($form['default_value'], $form, $form_state); } $this->entity->setDefaultValue($default_value); -} + } /** * {@inheritdoc} diff --git a/core/modules/file/file.module b/core/modules/file/file.module index 9f6e9fc..c63fff4 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -1318,7 +1318,7 @@ function file_icon_class($mime_type) { * @param string $mime_type * A MIME type. * - * @return string|FALSE + * @return string|false * The generic icon MIME package expected for this file. */ function file_icon_map($mime_type) { diff --git a/core/modules/file/src/Entity/File.php b/core/modules/file/src/Entity/File.php index b82d5a8..cd7a658 100644 --- a/core/modules/file/src/Entity/File.php +++ b/core/modules/file/src/Entity/File.php @@ -229,7 +229,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields['uuid']->setDescription(t('The file UUID.')); $fields['langcode']->setLabel(t('Language code')) - ->setDescription(t('The file language code.')); + ->setDescription(t('The file language code.')); $fields['uid'] = BaseFieldDefinition::create('entity_reference') ->setLabel(t('User ID')) diff --git a/core/modules/filter/src/FilterFormatInterface.php b/core/modules/filter/src/FilterFormatInterface.php index 30b612c..6008f3f 100644 --- a/core/modules/filter/src/FilterFormatInterface.php +++ b/core/modules/filter/src/FilterFormatInterface.php @@ -70,7 +70,7 @@ public function getFilterTypes(); * It is up to the caller to handle this in whatever way it sees fit; this way * no information granularity is lost. * - * @return array|FALSE + * @return array|false * A structured array as returned by FilterInterface::getHTMLRestrictions(), * but with the intersection of all filters in this text format. * Will either indicate blacklisting of tags or whitelisting of tags. In diff --git a/core/modules/filter/src/Plugin/FilterInterface.php b/core/modules/filter/src/Plugin/FilterInterface.php index d42d5cc..0b7fcf0 100644 --- a/core/modules/filter/src/Plugin/FilterInterface.php +++ b/core/modules/filter/src/Plugin/FilterInterface.php @@ -77,25 +77,25 @@ */ interface FilterInterface extends ConfigurablePluginInterface, PluginInspectionInterface { - /** - * Non-HTML markup language filters that generate HTML. - */ - const TYPE_MARKUP_LANGUAGE = 0; + /** + * Non-HTML markup language filters that generate HTML. + */ + const TYPE_MARKUP_LANGUAGE = 0; - /** - * HTML tag and attribute restricting filters to prevent XSS attacks. - */ - const TYPE_HTML_RESTRICTOR = 1; + /** + * HTML tag and attribute restricting filters to prevent XSS attacks. + */ + const TYPE_HTML_RESTRICTOR = 1; - /** - * Reversible transformation filters. - */ - const TYPE_TRANSFORM_REVERSIBLE = 2; + /** + * Reversible transformation filters. + */ + const TYPE_TRANSFORM_REVERSIBLE = 2; - /** - * Irreversible transformation filters. - */ - const TYPE_TRANSFORM_IRREVERSIBLE = 3; + /** + * Irreversible transformation filters. + */ + const TYPE_TRANSFORM_IRREVERSIBLE = 3; /** * Returns the processing type of this filter plugin. @@ -182,7 +182,7 @@ public function process($text, $langcode); * a generic manner into which HTML tags and attributes are allowed by a * format. * - * @return array|FALSE + * @return array|false * A nested array with *either* of the following keys: * - 'allowed': (optional) the allowed tags as keys, and for each of those * tags (keys) either of the following values: diff --git a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php index fd8158b..712d52c 100644 --- a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php +++ b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php @@ -190,7 +190,7 @@ public function denormalize($data, $class, $format = NULL, array $context = arra /** * Constructs the entity URI. * - * @param \Drupal\Core\Entity\EntityInterface + * @param \Drupal\Core\Entity\EntityInterface $entity * The entity. * @return string * The entity URI. diff --git a/core/modules/image/src/Controller/ImageStyleDownloadController.php b/core/modules/image/src/Controller/ImageStyleDownloadController.php index 885557c..8859bdd 100644 --- a/core/modules/image/src/Controller/ImageStyleDownloadController.php +++ b/core/modules/image/src/Controller/ImageStyleDownloadController.php @@ -113,14 +113,9 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st // If using the private scheme, let other modules provide headers and // control access to the file. if ($scheme == 'private') { - if (file_exists($derivative_uri)) { - return parent::download($request, $scheme); - } - else { - $headers = $this->moduleHandler()->invokeAll('file_download', array($image_uri)); - if (in_array(-1, $headers) || empty($headers)) { - throw new AccessDeniedHttpException(); - } + $headers = $this->moduleHandler()->invokeAll('file_download', array($image_uri)); + if (in_array(-1, $headers) || empty($headers)) { + throw new AccessDeniedHttpException(); } } @@ -144,8 +139,8 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st // Don't start generating the image if the derivative already exists or if // generation is in progress in another thread. - $lock_name = 'image_style_deliver:' . $image_style->id() . ':' . Crypt::hashBase64($image_uri); if (!file_exists($derivative_uri)) { + $lock_name = 'image_style_deliver:' . $image_style->id() . ':' . Crypt::hashBase64($image_uri); $lock_acquired = $this->lock->acquire($lock_name); if (!$lock_acquired) { // Tell client to retry again in 3 seconds. Currently no browsers are diff --git a/core/modules/image/src/ImageStyleListBuilder.php b/core/modules/image/src/ImageStyleListBuilder.php index e4e0be6..140ed46 100644 --- a/core/modules/image/src/ImageStyleListBuilder.php +++ b/core/modules/image/src/ImageStyleListBuilder.php @@ -4,10 +4,7 @@ use Drupal\Core\Config\Entity\ConfigEntityListBuilder; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityStorageInterface; -use Drupal\Core\Entity\EntityTypeInterface; -use Drupal\Core\Routing\UrlGeneratorInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Url; /** * Defines a class to build a listing of image style entities. @@ -17,40 +14,6 @@ class ImageStyleListBuilder extends ConfigEntityListBuilder { /** - * The URL generator. - * - * @var \Drupal\Core\Routing\UrlGeneratorInterface - */ - protected $urlGenerator; - - /** - * Constructs a new ImageStyleListBuilder object. - * - * @param EntityTypeInterface $entity_type - * The entity type definition. - * @param \Drupal\Core\Entity\EntityStorageInterface $image_style_storage - * The image style entity storage class. - * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator - * The URL generator. - */ - public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $image_style_storage, UrlGeneratorInterface $url_generator) { - parent::__construct($entity_type, $image_style_storage); - $this->urlGenerator = $url_generator; - } - - /** - * {@inheritdoc} - */ - public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { - return new static( - $entity_type, - $container->get('entity.manager')->getStorage($entity_type->id()), - $container->get('url_generator'), - $container->get('string_translation') - ); - } - - /** * {@inheritdoc} */ public function buildHeader() { @@ -87,7 +50,7 @@ public function getDefaultOperations(EntityInterface $entity) { public function render() { $build = parent::render(); $build['table']['#empty'] = $this->t('There are currently no styles. Add a new one.', [ - ':url' => $this->urlGenerator->generateFromRoute('image.style_add'), + ':url' => Url::fromRoute('image.style_add')->toString(), ]); return $build; } diff --git a/core/modules/image/src/Tests/ImageFieldWidgetTest.php b/core/modules/image/src/Tests/ImageFieldWidgetTest.php index ea2332d..52d9e4d 100644 --- a/core/modules/image/src/Tests/ImageFieldWidgetTest.php +++ b/core/modules/image/src/Tests/ImageFieldWidgetTest.php @@ -13,7 +13,7 @@ class ImageFieldWidgetTest extends ImageFieldTestBase { * Tests file widget element. */ public function testWidgetElement() { - // Check for image widget in add/node/article page + // Check for image widget in add/node/article page $field_name = strtolower($this->randomMachineName()); $min_resolution = 50; $max_resolution = 100; diff --git a/core/modules/image/src/Tests/ImageStylesPathAndUrlTest.php b/core/modules/image/src/Tests/ImageStylesPathAndUrlTest.php index f146ccc..d03bc3b 100644 --- a/core/modules/image/src/Tests/ImageStylesPathAndUrlTest.php +++ b/core/modules/image/src/Tests/ImageStylesPathAndUrlTest.php @@ -62,9 +62,9 @@ function testImageStyleUrlAndPathPrivate() { /** * Tests an image style URL with the "public://" scheme and unclean URLs. */ - function testImageStyleUrlAndPathPublicUnclean() { - $this->doImageStyleUrlAndPathTests('public', FALSE); - } + function testImageStyleUrlAndPathPublicUnclean() { + $this->doImageStyleUrlAndPathTests('public', FALSE); + } /** * Tests an image style URL with the "private://" schema and unclean URLs. @@ -155,6 +155,11 @@ function doImageStyleUrlAndPathTests($scheme, $clean_url = TRUE, $extra_slash = $image = $this->container->get('image.factory')->get($generated_uri); $this->assertEqual($this->drupalGetHeader('Content-Type'), $image->getMimeType(), 'Expected Content-Type was reported.'); $this->assertEqual($this->drupalGetHeader('Content-Length'), $image->getFileSize(), 'Expected Content-Length was reported.'); + + // Check that we did not download the original file. + $original_image = $this->container->get('image.factory')->get($original_uri); + $this->assertNotEqual($this->drupalGetHeader('Content-Length'), $original_image->getFileSize()); + if ($scheme == 'private') { $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.'); $this->assertNotEqual(strpos($this->drupalGetHeader('Cache-Control'), 'no-cache'), FALSE, 'Cache-Control header contains \'no-cache\' to prevent caching.'); @@ -165,6 +170,12 @@ function doImageStyleUrlAndPathTests($scheme, $clean_url = TRUE, $extra_slash = $this->drupalGet($generate_url); $this->assertResponse(200, 'Image was generated at the URL.'); + // Check that the second request also returned the generated image. + $this->assertEqual($this->drupalGetHeader('Content-Length'), $image->getFileSize()); + + // Check that we did not download the original file. + $this->assertNotEqual($this->drupalGetHeader('Content-Length'), $original_image->getFileSize()); + // Make sure that access is denied for existing style files if we do not // have access. \Drupal::state()->delete('image.test_file_download'); diff --git a/core/modules/image/tests/src/Kernel/Migrate/d6/MigrateImageCacheTest.php b/core/modules/image/tests/src/Kernel/Migrate/d6/MigrateImageCacheTest.php index 28e11bd..98db7b1 100644 --- a/core/modules/image/tests/src/Kernel/Migrate/d6/MigrateImageCacheTest.php +++ b/core/modules/image/tests/src/Kernel/Migrate/d6/MigrateImageCacheTest.php @@ -86,64 +86,64 @@ public function testPassingMigration() { /** * Test that missing actions causes failures. */ - public function testMissingEffectPlugin() { - Database::getConnection('default', 'migrate')->insert("imagecache_action") + public function testMissingEffectPlugin() { + Database::getConnection('default', 'migrate')->insert("imagecache_action") ->fields([ - 'presetid', - 'weight', - 'module', - 'action', - 'data', - ]) + 'presetid', + 'weight', + 'module', + 'action', + 'data', + ]) ->values([ - 'presetid' => '1', - 'weight' => '0', - 'module' => 'imagecache', - 'action' => 'imagecache_deprecated_scale', - 'data' => 'a:3:{s:3:"fit";s:7:"outside";s:5:"width";s:3:"200";s:6:"height";s:3:"200";}', - ])->execute(); - - $this->startCollectingMessages(); - $this->executeMigration('d6_imagecache_presets'); - $messages = $this->migration->getIdMap()->getMessageIterator(); - $count = 0; - foreach ($messages as $message) { - $count++; - $this->assertEqual($message->message, 'The "image_deprecated_scale" plugin does not exist.'); - $this->assertEqual($message->level, MigrationInterface::MESSAGE_ERROR); - } - // There should be only the one message. - $this->assertEqual($count, 1); + 'presetid' => '1', + 'weight' => '0', + 'module' => 'imagecache', + 'action' => 'imagecache_deprecated_scale', + 'data' => 'a:3:{s:3:"fit";s:7:"outside";s:5:"width";s:3:"200";s:6:"height";s:3:"200";}', + ])->execute(); + + $this->startCollectingMessages(); + $this->executeMigration('d6_imagecache_presets'); + $messages = $this->migration->getIdMap()->getMessageIterator(); + $count = 0; + foreach ($messages as $message) { + $count++; + $this->assertEqual($message->message, 'The "image_deprecated_scale" plugin does not exist.'); + $this->assertEqual($message->level, MigrationInterface::MESSAGE_ERROR); + } + // There should be only the one message. + $this->assertEqual($count, 1); } /** * Test that missing action's causes failures. */ - public function testInvalidCropValues() { - Database::getConnection('default', 'migrate')->insert("imagecache_action") + public function testInvalidCropValues() { + Database::getConnection('default', 'migrate')->insert("imagecache_action") ->fields([ - 'presetid', - 'weight', - 'module', - 'action', - 'data', - ]) + 'presetid', + 'weight', + 'module', + 'action', + 'data', + ]) ->values([ - 'presetid' => '1', - 'weight' => '0', - 'module' => 'imagecache', - 'action' => 'imagecache_crop', - 'data' => serialize([ - 'xoffset' => '10', - 'yoffset' => '10', - ]), - ])->execute(); - - $this->startCollectingMessages(); - $this->executeMigration('d6_imagecache_presets'); - $this->assertEqual(['error' => [ - 'The Drupal 8 image crop effect does not support numeric values for x and y offsets. Use keywords to set crop effect offsets instead.' - ]], $this->migrateMessages); + 'presetid' => '1', + 'weight' => '0', + 'module' => 'imagecache', + 'action' => 'imagecache_crop', + 'data' => serialize([ + 'xoffset' => '10', + 'yoffset' => '10', + ]), + ])->execute(); + + $this->startCollectingMessages(); + $this->executeMigration('d6_imagecache_presets'); + $this->assertEqual(['error' => [ + 'The Drupal 8 image crop effect does not support numeric values for x and y offsets. Use keywords to set crop effect offsets instead.' + ]], $this->migrateMessages); } /** diff --git a/core/modules/language/tests/src/Unit/LanguageNegotiationUrlTest.php b/core/modules/language/tests/src/Unit/LanguageNegotiationUrlTest.php index 8ecdbf7..735abd6 100644 --- a/core/modules/language/tests/src/Unit/LanguageNegotiationUrlTest.php +++ b/core/modules/language/tests/src/Unit/LanguageNegotiationUrlTest.php @@ -1,6 +1,6 @@ uri, $this->options); + return Url::fromUri($this->uri, (array) $this->options); } /** diff --git a/core/modules/link/src/Plugin/migrate/cckfield/LinkField.php b/core/modules/link/src/Plugin/migrate/cckfield/LinkField.php index 47b3830..314e8df 100644 --- a/core/modules/link/src/Plugin/migrate/cckfield/LinkField.php +++ b/core/modules/link/src/Plugin/migrate/cckfield/LinkField.php @@ -38,11 +38,11 @@ public function getFieldFormatterMap() { * {@inheritdoc} */ public function processCckFieldValues(MigrationInterface $migration, $field_name, $data) { - $process = [ - 'plugin' => 'd6_cck_link', - 'source' => $field_name, - ]; - $migration->mergeProcessOfProperty($field_name, $process); + $process = [ + 'plugin' => 'd6_cck_link', + 'source' => $field_name, + ]; + $migration->mergeProcessOfProperty($field_name, $process); } } diff --git a/core/modules/link/tests/src/Kernel/LinkItemTest.php b/core/modules/link/tests/src/Kernel/LinkItemTest.php index c1f97ae..5e2c45d 100644 --- a/core/modules/link/tests/src/Kernel/LinkItemTest.php +++ b/core/modules/link/tests/src/Kernel/LinkItemTest.php @@ -5,6 +5,7 @@ use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FieldItemInterface; +use Drupal\Core\Url; use Drupal\entity_test\Entity\EntityTest; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; @@ -154,6 +155,11 @@ public function testLinkItem() { $this->assertNull($entity->field_test->title); $this->assertIdentical($entity->field_test->options, []); + // Check that setting options to NULL does not trigger an error when + // calling getUrl(); + $entity->field_test->options = NULL; + $this->assertInstanceOf(Url::class, $entity->field_test[0]->getUrl()); + // Check that setting LinkItem value NULL doesn't generate any error or // warning. $entity->field_test[0] = NULL; diff --git a/core/modules/locale/src/LocaleConfigManager.php b/core/modules/locale/src/LocaleConfigManager.php index 2de875e..bc598e2 100644 --- a/core/modules/locale/src/LocaleConfigManager.php +++ b/core/modules/locale/src/LocaleConfigManager.php @@ -434,7 +434,7 @@ public function reset() { * @param string $context * The string context. * - * @return \Drupal\locale\TranslationString|FALSE + * @return \Drupal\locale\TranslationString|false * The translation object if the string was not empty or FALSE otherwise. */ public function getStringTranslation($name, $langcode, $source, $context) { diff --git a/core/modules/locale/src/StringDatabaseStorage.php b/core/modules/locale/src/StringDatabaseStorage.php index 01a0544..058c689 100644 --- a/core/modules/locale/src/StringDatabaseStorage.php +++ b/core/modules/locale/src/StringDatabaseStorage.php @@ -175,9 +175,9 @@ protected function checkVersion($string, $version) { if ($string->getId() && $string->getVersion() != $version) { $string->setVersion($version); $this->connection->update('locales_source', $this->options) - ->condition('lid', $string->getId()) - ->fields(array('version' => $version)) - ->execute(); + ->condition('lid', $string->getId()) + ->fields(array('version' => $version)) + ->execute(); } } diff --git a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php index 669d1e6..d23f876 100644 --- a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php +++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php @@ -117,7 +117,9 @@ public function isExpanded() { * {@inheritdoc} */ public function getParentId() { - return $this->get('parent')->value; + // Cast the parent ID to a string, only an empty string means no parent, + // NULL keeps the existing parent. + return (string) $this->get('parent')->value; } /** @@ -331,7 +333,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { 'type' => 'boolean', 'weight' => 0, )) - ->setDisplayOptions('form', array( + ->setDisplayOptions('form', array( 'settings' => array('display_label' => TRUE), 'weight' => 0, )); diff --git a/core/modules/menu_link_content/src/Tests/LinksTest.php b/core/modules/menu_link_content/src/Tests/LinksTest.php index 5fd0ca1..84fecde 100644 --- a/core/modules/menu_link_content/src/Tests/LinksTest.php +++ b/core/modules/menu_link_content/src/Tests/LinksTest.php @@ -188,6 +188,20 @@ function testMenuLinkReparenting($module = 'menu_test') { ); $this->assertMenuLinkParents($links, $expected_hierarchy); + // Try changing the parent at the entity level. + $definition = $this->menuLinkManager->getDefinition($links['child-1-2']); + $entity = MenuLinkContent::load($definition['metadata']['entity_id']); + $entity->parent->value = ''; + $entity->save(); + + $expected_hierarchy = array( + 'parent' => '', + 'child-1-1' => 'parent', + 'child-1-2' => '', + 'child-2' => 'parent', + ); + $this->assertMenuLinkParents($links, $expected_hierarchy); + // @todo Figure out what makes sense to test in terms of automatic // re-parenting. https://www.drupal.org/node/2309531 } diff --git a/core/modules/menu_ui/src/Tests/MenuTest.php b/core/modules/menu_ui/src/Tests/MenuTest.php index f640cc7..e9464ab 100644 --- a/core/modules/menu_ui/src/Tests/MenuTest.php +++ b/core/modules/menu_ui/src/Tests/MenuTest.php @@ -916,7 +916,7 @@ private function verifyAccess($response = 200) { // View tools menu customization page. $this->drupalGet('admin/structure/menu/manage/' . $this->menu->id()); - $this->assertResponse($response); + $this->assertResponse($response); if ($response == 200) { $this->assertText(t('Tools'), 'Tools menu page was displayed'); } diff --git a/core/modules/migrate/src/Event/MigrateEvents.php b/core/modules/migrate/src/Event/MigrateEvents.php index edc2bec..e2de42e 100644 --- a/core/modules/migrate/src/Event/MigrateEvents.php +++ b/core/modules/migrate/src/Event/MigrateEvents.php @@ -81,7 +81,7 @@ * * This event allows modules to perform an action whenever a specific item * is about to be saved by the destination plugin. The event listener method - * receives a \Drupal\migrate\Event\MigratePreSaveEvent instance. + * receives a \Drupal\migrate\Event\MigratePreRowSaveEvent instance. * * @Event * diff --git a/core/modules/migrate/src/Event/MigratePreRowSaveEvent.php b/core/modules/migrate/src/Event/MigratePreRowSaveEvent.php index 5e563bb..98a1c0a 100644 --- a/core/modules/migrate/src/Event/MigratePreRowSaveEvent.php +++ b/core/modules/migrate/src/Event/MigratePreRowSaveEvent.php @@ -28,7 +28,7 @@ class MigratePreRowSaveEvent extends EventBase { * @param \Drupal\migrate\Row $row */ public function __construct(MigrationInterface $migration, MigrateMessageInterface $message, Row $row) { - parent::__construct($migration, $message); + parent::__construct($migration, $message); $this->row = $row; } diff --git a/core/modules/migrate/src/MigrateExecutable.php b/core/modules/migrate/src/MigrateExecutable.php index 7368acd..50bf018 100644 --- a/core/modules/migrate/src/MigrateExecutable.php +++ b/core/modules/migrate/src/MigrateExecutable.php @@ -185,11 +185,17 @@ public function import() { } catch (RequirementsException $e) { $this->message->display( - $this->t('Migration @id did not meet the requirements. @message @requirements', array( - '@id' => $this->migration->id(), - '@message' => $e->getMessage(), - '@requirements' => $e->getRequirementsString(), - )), 'error'); + $this->t( + 'Migration @id did not meet the requirements. @message @requirements', + array( + '@id' => $this->migration->id(), + '@message' => $e->getMessage(), + '@requirements' => $e->getRequirementsString(), + ) + ), + 'error' + ); + return MigrationInterface::RESULT_FAILED; } @@ -476,30 +482,44 @@ protected function memoryExceeded() { } if ($pct_memory > $threshold) { $this->message->display( - $this->t('Memory usage is @usage (@pct% of limit @limit), reclaiming memory.', - array('@pct' => round($pct_memory * 100), - '@usage' => $this->formatSize($usage), - '@limit' => $this->formatSize($this->memoryLimit))), - 'warning'); + $this->t( + 'Memory usage is @usage (@pct% of limit @limit), reclaiming memory.', + array( + '@pct' => round($pct_memory * 100), + '@usage' => $this->formatSize($usage), + '@limit' => $this->formatSize($this->memoryLimit), + ) + ), + 'warning' + ); $usage = $this->attemptMemoryReclaim(); $pct_memory = $usage / $this->memoryLimit; // Use a lower threshold - we don't want to be in a situation where we keep // coming back here and trimming a tiny amount if ($pct_memory > (0.90 * $threshold)) { $this->message->display( - $this->t('Memory usage is now @usage (@pct% of limit @limit), not enough reclaimed, starting new batch', - array('@pct' => round($pct_memory * 100), - '@usage' => $this->formatSize($usage), - '@limit' => $this->formatSize($this->memoryLimit))), - 'warning'); + $this->t( + 'Memory usage is now @usage (@pct% of limit @limit), not enough reclaimed, starting new batch', + array( + '@pct' => round($pct_memory * 100), + '@usage' => $this->formatSize($usage), + '@limit' => $this->formatSize($this->memoryLimit), + ) + ), + 'warning' + ); return TRUE; } else { $this->message->display( - $this->t('Memory usage is now @usage (@pct% of limit @limit), reclaimed enough, continuing', - array('@pct' => round($pct_memory * 100), - '@usage' => $this->formatSize($usage), - '@limit' => $this->formatSize($this->memoryLimit))), + $this->t( + 'Memory usage is now @usage (@pct% of limit @limit), reclaimed enough, continuing', + array( + '@pct' => round($pct_memory * 100), + '@usage' => $this->formatSize($usage), + '@limit' => $this->formatSize($this->memoryLimit), + ) + ), 'warning'); return FALSE; } diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php index 3617f38..d3adce8 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php @@ -147,7 +147,7 @@ public function getIds() { * @param \Drupal\migrate\Row $row * The row object to update from. * - * @return NULL|\Drupal\Core\Entity\EntityInterface + * @return \Drupal\Core\Entity\EntityInterface|null * An updated entity, or NULL if it's the same as the one passed in. */ protected function updateEntity(EntityInterface $entity, Row $row) { diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldInstance.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldInstance.php index 54bc358..966cb37 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldInstance.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldInstance.php @@ -21,4 +21,12 @@ public function getIds() { return $ids; } + /** + * {@inheritdoc} + */ + public function rollback(array $destination_identifier) { + $destination_identifier = implode('.', $destination_identifier); + parent::rollback(array($destination_identifier)); + } + } diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldStorageConfig.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldStorageConfig.php index 6851f27..7ad0104 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldStorageConfig.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldStorageConfig.php @@ -20,4 +20,12 @@ public function getIds() { return $ids; } + /** + * {@inheritdoc} + */ + public function rollback(array $destination_identifier) { + $destination_identifier = implode('.', $destination_identifier); + parent::rollback(array($destination_identifier)); + } + } diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityViewMode.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityViewMode.php index 810152a..fad171b 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityViewMode.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityViewMode.php @@ -20,4 +20,12 @@ public function getIds() { return $ids; } + /** + * {@inheritdoc} + */ + public function rollback(array $destination_identifier) { + $destination_identifier = implode('.', $destination_identifier); + parent::rollback(array($destination_identifier)); + } + } diff --git a/core/modules/migrate/tests/src/Unit/MigrateSourceTest.php b/core/modules/migrate/tests/src/Unit/MigrateSourceTest.php index 5e2a06e..2b7f12f 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateSourceTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateSourceTest.php @@ -170,7 +170,7 @@ public function testCount() { $this->assertEquals(-1, $source->count()); } - /** + /** * Test that the key can be set for the count cache. * * @covers ::count diff --git a/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php index a7aab64..6838232 100644 --- a/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php @@ -125,7 +125,7 @@ protected function getMigrations($database_state_key, $drupal_version) { * @param \Drupal\Core\Database\Connection $connection * The database connection object. * - * @return int|FALSE + * @return int|false * An integer representing the major branch of Drupal core (e.g. '6' for * Drupal 6.x), or FALSE if no valid version is matched. */ diff --git a/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManager.php b/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManager.php index 44151cc..7920040 100644 --- a/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManager.php +++ b/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManager.php @@ -27,9 +27,22 @@ class MigrateCckFieldPluginManager extends MigratePluginManager { const DEFAULT_CORE_VERSION = 6; /** - * {@inheritdoc} + * Get the plugin ID from the field type. + * + * @param string $field_type + * The field type being migrated. + * @param array $configuration + * (optioanl) An array of configuration relevant to the plugin instance. + * @param \Drupal\migrate\Plugin\MigrationInterface|null $migration + * (optional) The current migration instance. + * + * @return string + * The ID of the plugin for the field_type if available. + * + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + * If the plugin cannot be determined, such as if the field type is invalid. */ - public function createInstance($field_type, array $configuration = array(), MigrationInterface $migration = NULL) { + public function getPluginIdFromFieldType($field_type, array $configuration = [], MigrationInterface $migration = NULL) { $core = static::DEFAULT_CORE_VERSION; if (!empty($configuration['core'])) { $core = $configuration['core']; @@ -45,7 +58,7 @@ public function createInstance($field_type, array $configuration = array(), Migr foreach ($this->getDefinitions() as $plugin_id => $definition) { if (in_array($core, $definition['core'])) { if (array_key_exists($field_type, $definition['type_map']) || $field_type === $plugin_id) { - return parent::createInstance($plugin_id, $configuration, $migration); + return $plugin_id; } } } diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/CckMigration.php b/core/modules/migrate_drupal/src/Plugin/migrate/CckMigration.php index bffba2c..4042ecd 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/CckMigration.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/CckMigration.php @@ -2,6 +2,7 @@ namespace Drupal\migrate_drupal\Plugin\migrate; +use Drupal\Component\Plugin\Exception\PluginNotFoundException; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\migrate\Exception\RequirementsException; use Drupal\migrate\Plugin\MigrateDestinationPluginManager; @@ -106,12 +107,19 @@ public function getProcess() { } foreach ($source_plugin as $row) { $field_type = $row->getSourceProperty('type'); - if (!isset($this->processedFieldTypes[$field_type]) && $this->cckPluginManager->hasDefinition($field_type)) { + try { + $plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, [], $this); + } + catch (PluginNotFoundException $ex) { + continue; + } + + if (!isset($this->processedFieldTypes[$field_type])) { $this->processedFieldTypes[$field_type] = TRUE; // Allow the cckfield plugin to alter the migration as necessary so // that it knows how to handle fields of this type. if (!isset($this->cckPluginCache[$field_type])) { - $this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($field_type, [], $this); + $this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($plugin_id, [], $this); } call_user_func([$this->cckPluginCache[$field_type], $this->pluginDefinition['cck_plugin_method']], $this); } diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php index 8c70551..2c3230f 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php @@ -23,7 +23,7 @@ use DependencyTrait; - /** + /** * The contents of the system table. * * @var array @@ -53,12 +53,12 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition } /** - * Retrieves all system data information from origin system. - * - * @return array - * List of system table information keyed by type and name. - */ - public function getSystemData() { + * Retrieves all system data information from origin system. + * + * @return array + * List of system table information keyed by type and name. + */ + public function getSystemData() { if (!isset($this->systemData)) { $this->systemData = array(); try { @@ -102,7 +102,7 @@ public function checkRequirements() { } } else { - throw new RequirementsException('Missing source provider ' . $this->pluginDefinition['source_provider'], ['source_provider' => $this->pluginDefinition['source_provider']]); + throw new RequirementsException('The module ' . $this->pluginDefinition['source_provider'] . ' is not enabled in the source site.'); } } } diff --git a/core/modules/migrate_drupal/tests/fixtures/drupal7.php b/core/modules/migrate_drupal/tests/fixtures/drupal7.php index bbc8088..a02d12f 100644 --- a/core/modules/migrate_drupal/tests/fixtures/drupal7.php +++ b/core/modules/migrate_drupal/tests/fixtures/drupal7.php @@ -3835,6 +3835,18 @@ 'body_summary' => '', 'body_format' => 'filtered_html', )) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '3', + 'revision_id' => '3', + 'language' => 'und', + 'delta' => '0', + 'body_value' => "is - ...is that it's the absolute best show ever. Trust me, I would know.", + 'body_summary' => '', + 'body_format' => 'filtered_html', +)) ->execute(); $connection->schema()->createTable('field_data_comment_body', array( @@ -4930,6 +4942,18 @@ 'field_link_title' => 'Home', 'field_link_attributes' => 'a:0:{}', )) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '3', + 'revision_id' => '3', + 'language' => 'und', + 'delta' => '0', + 'field_link_url' => '', + 'field_link_title' => 'Home', + 'field_link_attributes' => 'a:1:{s:5:"title";s:0:"";}', +)) ->execute(); $connection->schema()->createTable('field_data_field_long_text', array( @@ -5171,6 +5195,16 @@ 'entity_type' => 'node', 'bundle' => 'article', 'deleted' => '0', + 'entity_id' => '3', + 'revision_id' => '3', + 'language' => 'und', + 'delta' => '0', + 'field_tags_tid' => '9', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', 'entity_id' => '2', 'revision_id' => '2', 'language' => 'und', @@ -5181,12 +5215,32 @@ 'entity_type' => 'node', 'bundle' => 'article', 'deleted' => '0', + 'entity_id' => '3', + 'revision_id' => '3', + 'language' => 'und', + 'delta' => '1', + 'field_tags_tid' => '14', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', 'entity_id' => '2', 'revision_id' => '2', 'language' => 'und', 'delta' => '2', 'field_tags_tid' => '17', )) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '3', + 'revision_id' => '3', + 'language' => 'und', + 'delta' => '2', + 'field_tags_tid' => '17', +)) ->execute(); $connection->schema()->createTable('field_data_field_term_reference', array( @@ -5603,6 +5657,18 @@ 'body_summary' => '', 'body_format' => 'filtered_html', )) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '3', + 'revision_id' => '3', + 'language' => 'und', + 'delta' => '0', + 'body_value' => "is - ...is that it's the absolute best show ever. Trust me, I would know.", + 'body_summary' => '', + 'body_format' => 'filtered_html', +)) ->execute(); $connection->schema()->createTable('field_revision_comment_body', array( @@ -6710,6 +6776,18 @@ 'field_link_title' => 'Home', 'field_link_attributes' => 'a:0:{}', )) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '3', + 'revision_id' => '3', + 'language' => 'und', + 'delta' => '0', + 'field_link_url' => '', + 'field_link_title' => 'Home', + 'field_link_attributes' => 'a:1:{s:5:"title";s:0:"";}', +)) ->execute(); $connection->schema()->createTable('field_revision_field_long_text', array( @@ -6954,6 +7032,16 @@ 'entity_type' => 'node', 'bundle' => 'article', 'deleted' => '0', + 'entity_id' => '3', + 'revision_id' => '3', + 'language' => 'und', + 'delta' => '0', + 'field_tags_tid' => '9', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', 'entity_id' => '2', 'revision_id' => '2', 'language' => 'und', @@ -6964,12 +7052,32 @@ 'entity_type' => 'node', 'bundle' => 'article', 'deleted' => '0', + 'entity_id' => '3', + 'revision_id' => '3', + 'language' => 'und', + 'delta' => '1', + 'field_tags_tid' => '14', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', 'entity_id' => '2', 'revision_id' => '2', 'language' => 'und', 'delta' => '2', 'field_tags_tid' => '17', )) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '3', + 'revision_id' => '3', + 'language' => 'und', + 'delta' => '2', + 'field_tags_tid' => '17', +)) ->execute(); $connection->schema()->createTable('field_revision_field_term_reference', array( @@ -29674,7 +29782,23 @@ 'comment' => '2', 'promote' => '1', 'sticky' => '0', - 'tnid' => '0', + 'tnid' => '2', + 'translate' => '0', +)) +->values(array( + 'nid' => '3', + 'vid' => '3', + 'type' => 'article', + 'language' => 'is', + 'title' => 'is - The thing about Deep Space 9', + 'uid' => '1', + 'status' => '1', + 'created' => '1471428152', + 'changed' => '1471428152', + 'comment' => '2', + 'promote' => '1', + 'sticky' => '0', + 'tnid' => '2', 'translate' => '0', )) ->execute(); @@ -29813,6 +29937,14 @@ 'last_comment_uid' => '1', 'comment_count' => '1', )) +->values(array( + 'nid' => '3', + 'cid' => '0', + 'last_comment_timestamp' => '1471428152', + 'last_comment_name' => NULL, + 'last_comment_uid' => '1', + 'comment_count' => '0', +)) ->execute(); $connection->schema()->createTable('node_counter', array( @@ -29864,6 +29996,18 @@ 'daycount' => '0', 'timestamp' => '1421727536', )) +->values(array( + 'nid' => '2', + 'totalcount' => '1', + 'daycount' => '1', + 'timestamp' => '1471428059', +)) +->values(array( + 'nid' => '3', + 'totalcount' => '1', + 'daycount' => '1', + 'timestamp' => '1471428153', +)) ->execute(); $connection->schema()->createTable('node_revision', array( @@ -29972,6 +30116,18 @@ 'promote' => '1', 'sticky' => '0', )) +->values(array( + 'nid' => '3', + 'vid' => '3', + 'uid' => '1', + 'title' => 'is - The thing about Deep Space 9', + 'log' => '', + 'timestamp' => '1471428152', + 'status' => '1', + 'comment' => '2', + 'promote' => '1', + 'sticky' => '0', +)) ->execute(); $connection->schema()->createTable('node_type', array( @@ -40112,6 +40268,24 @@ 'sticky' => '0', 'created' => '1441306772', )) +->values(array( + 'nid' => '3', + 'tid' => '9', + 'sticky' => '0', + 'created' => '1471428152', +)) +->values(array( + 'nid' => '3', + 'tid' => '14', + 'sticky' => '0', + 'created' => '1471428152', +)) +->values(array( + 'nid' => '3', + 'tid' => '17', + 'sticky' => '0', + 'created' => '1471428152', +)) ->execute(); $connection->schema()->createTable('taxonomy_term_data', array( @@ -41342,7 +41516,7 @@ )) ->values(array( 'name' => 'language_content_type_article', - 'value' => 's:1:"0";', + 'value' => 's:1:"2";', )) ->values(array( 'name' => 'language_content_type_blog', @@ -41442,7 +41616,7 @@ )) ->values(array( 'name' => 'menu_override_parent_selector', - 'value' => 'b:1;', + 'value' => 'b:0;', )) ->values(array( 'name' => 'menu_parent_article', diff --git a/core/modules/migrate_drupal/tests/src/Kernel/MigrateCckFieldPluginManagerTest.php b/core/modules/migrate_drupal/tests/src/Kernel/MigrateCckFieldPluginManagerTest.php index 8cea74c..1754cfe 100644 --- a/core/modules/migrate_drupal/tests/src/Kernel/MigrateCckFieldPluginManagerTest.php +++ b/core/modules/migrate_drupal/tests/src/Kernel/MigrateCckFieldPluginManagerTest.php @@ -22,32 +22,33 @@ class MigrateCckFieldPluginManagerTest extends MigrateDrupalTestBase { public function testPluginSelection() { $plugin_manager = \Drupal::service('plugin.manager.migrate.cckfield'); - $this->assertIdentical('Drupal\\file\\Plugin\\migrate\\cckfield\\d6\\FileField', get_class($plugin_manager->createInstance('filefield', ['core' => 6]))); + $plugin_id = $plugin_manager->getPluginIdFromFieldType('filefield', ['core' => 6]); + $this->assertIdentical('Drupal\\file\\Plugin\\migrate\\cckfield\\d6\\FileField', get_class($plugin_manager->createInstance($plugin_id, ['core' => 6]))); try { - // If this test passes, createInstance will raise a + // If this test passes, getPluginIdFromFieldType will raise a // PluginNotFoundException and we'll never reach fail(). - $plugin_manager->createInstance('filefield', ['core' => 7]); + $plugin_manager->getPluginIdFromFieldType('filefield', ['core' => 7]); $this->fail('Expected Drupal\Component\Plugin\Exception\PluginNotFoundException.'); } catch (PluginNotFoundException $e) { $this->assertIdentical($e->getMessage(), "Plugin ID 'filefield' was not found."); } - $this->assertIdentical('Drupal\\file\\Plugin\\migrate\\cckfield\\d7\\ImageField', get_class($plugin_manager->createInstance('image', ['core' => 7]))); - $this->assertIdentical('Drupal\\file\\Plugin\\migrate\\cckfield\\d7\\FileField', get_class($plugin_manager->createInstance('file', ['core' => 7]))); - $this->assertIdentical('Drupal\\migrate_cckfield_plugin_manager_test\\Plugin\\migrate\\cckfield\\D6FileField', get_class($plugin_manager->createInstance('file', ['core' => 6]))); + $this->assertIdentical('image', $plugin_manager->getPluginIdFromFieldType('image', ['core' => 7])); + $this->assertIdentical('file', $plugin_manager->getPluginIdFromFieldType('file', ['core' => 7])); + $this->assertIdentical('d6_file', $plugin_manager->getPluginIdFromFieldType('file', ['core' => 6])); - $this->assertIdentical('Drupal\\text\\Plugin\\migrate\\cckfield\\TextField', get_class($plugin_manager->createInstance('text', ['core' => 6]))); - $this->assertIdentical('Drupal\\text\\Plugin\\migrate\\cckfield\\TextField', get_class($plugin_manager->createInstance('text', ['core' => 7]))); + $this->assertIdentical('text', $plugin_manager->getPluginIdFromFieldType('text', ['core' => 6])); + $this->assertIdentical('text', $plugin_manager->getPluginIdFromFieldType('text', ['core' => 7])); // Test fallback when no core version is specified. - $this->assertIdentical('Drupal\\migrate_cckfield_plugin_manager_test\\Plugin\\migrate\\cckfield\\D6NoCoreVersionSpecified', get_class($plugin_manager->createInstance('d6_no_core_version_specified', ['core' => 6]))); + $this->assertIdentical('d6_no_core_version_specified', $plugin_manager->getPluginIdFromFieldType('d6_no_core_version_specified', ['core' => 6])); try { - // If this test passes, createInstance will raise a + // If this test passes, getPluginIdFromFieldType will raise a // PluginNotFoundException and we'll never reach fail(). - $plugin_manager->createInstance('d6_no_core_version_specified', ['core' => 7]); + $plugin_manager->getPluginIdFromFieldType('d6_no_core_version_specified', ['core' => 7]); $this->fail('Expected Drupal\Component\Plugin\Exception\PluginNotFoundException.'); } catch (PluginNotFoundException $e) { diff --git a/core/modules/migrate_drupal/tests/src/Unit/source/DrupalSqlBaseTest.php b/core/modules/migrate_drupal/tests/src/Unit/source/DrupalSqlBaseTest.php new file mode 100644 index 0000000..4d07922 --- /dev/null +++ b/core/modules/migrate_drupal/tests/src/Unit/source/DrupalSqlBaseTest.php @@ -0,0 +1,102 @@ + 'DrupalSqlBase', + ); + + /** + * @var \Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase + */ + protected $base; + + /** + * Minimum database contents needed to test DrupalSqlBase. + */ + protected $databaseContents = array( + 'system' => array( + array( + 'filename' => 'sites/all/modules/module1', + 'name' => 'module1', + 'type' => 'module', + 'status' => 0, + 'schema_version' => -1, + ), + ), + ); + + /** + * @covers ::checkRequirements + */ + public function testSourceProviderNotActive() { + $this->setExpectedException(RequirementsException::class, 'The module module1 is not enabled in the source site.'); + $plugin_definition['requirements_met'] = TRUE; + $plugin_definition['source_provider'] = 'module1'; + /** @var \Drupal\Core\State\StateInterface $state */ + $state = $this->getMock('Drupal\Core\State\StateInterface'); + /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */ + $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); + $plugin = new TestDrupalSqlBase([], 'placeholder_id', $plugin_definition, $this->getMigration(), $state, $entity_manager); + $plugin->setDatabase($this->getDatabase($this->databaseContents)); + $system_data = $plugin->getSystemData(); + $plugin->checkRequirements(); + } + +} + +namespace Drupal\Tests\migrate_drupal\Unit\source; + +use Drupal\Core\Database\Connection; +use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase; + +/** + * Extends the DrupalSqlBase abstract class. + */ +class TestDrupalSqlBase extends DrupalSqlBase { + + /** + * {@inheritdoc} + */ + public function fields() { + return []; + } + + /** + * {@inheritdoc} + */ + public function query() { + } + + /** + * Tweaks DrupalSqlBase to set a new database connection for tests. + * + * @param \Drupal\Core\Database\Connection $database + * The new connection to use. + * + * @see \Drupal\Tests\migrate\Unit\MigrateSourceSqlTestCase + */ + public function setDatabase(Connection $database) { + $this->database = $database; + } + + /** + * {@inheritdoc} + */ + public function getIds() { + return []; + } + +} diff --git a/core/modules/migrate_drupal/tests/src/Unit/source/d6/Drupal6SqlBaseTest.php b/core/modules/migrate_drupal/tests/src/Unit/source/d6/Drupal6SqlBaseTest.php index e27a353..845ce78 100644 --- a/core/modules/migrate_drupal/tests/src/Unit/source/d6/Drupal6SqlBaseTest.php +++ b/core/modules/migrate_drupal/tests/src/Unit/source/d6/Drupal6SqlBaseTest.php @@ -170,7 +170,7 @@ public function query() { /** * Tweaks Drupal6SqlBase to set a new database connection for tests. * - * @param \Drupal\Core\Database\Connection + * @param \Drupal\Core\Database\Connection $database * The new connection to use. * * @see \Drupal\Tests\migrate\Unit\MigrateSqlTestCase @@ -182,7 +182,7 @@ public function setDatabase(Connection $database) { /** * Tweaks Drupal6SqlBase to set a new module handler for tests. * - * @param \Drupal\Core\Extension\ModuleHandlerInterface + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The new module handler to use. * * @see \Drupal\Tests\migrate\Unit\MigrateSqlTestCase diff --git a/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php b/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php index 6b30ade..2769b44 100644 --- a/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php +++ b/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php @@ -48,9 +48,9 @@ protected function getEntityCounts() { 'file' => 1, 'filter_format' => 7, 'image_style' => 6, - 'language_content_settings' => 1, + 'language_content_settings' => 2, 'migration' => 59, - 'node' => 2, + 'node' => 3, 'node_type' => 6, 'rdf_mapping' => 5, 'search_page' => 2, diff --git a/core/modules/node/node.module b/core/modules/node/node.module index aade3e8..470ddfc 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -320,7 +320,7 @@ function node_type_load($name) { * A Body field object. */ function node_add_body_field(NodeTypeInterface $type, $label = 'Body') { - // Add or remove the body field, as needed. + // Add or remove the body field, as needed. $field_storage = FieldStorageConfig::loadByName('node', 'body'); $field = FieldConfig::loadByName('node', $type->id(), 'body'); if (empty($field)) { @@ -751,7 +751,7 @@ function node_get_recent($number = 10) { // If not, restrict the query to published nodes. $query->condition('status', NODE_PUBLISHED); } - } + } $nids = $query ->sort('changed', 'DESC') ->range(0, $number) @@ -1213,7 +1213,7 @@ function _node_access_rebuild_batch_operation(&$context) { // Initiate multistep processing. $context['sandbox']['progress'] = 0; $context['sandbox']['current_node'] = 0; - $context['sandbox']['max'] = \Drupal::entityQuery('node')->count()->execute(); + $context['sandbox']['max'] = \Drupal::entityQuery('node')->accessCheck(FALSE)->count()->execute(); } // Process the next 20 nodes. diff --git a/core/modules/node/src/Controller/NodeController.php b/core/modules/node/src/Controller/NodeController.php index 654ba30..048b949 100644 --- a/core/modules/node/src/Controller/NodeController.php +++ b/core/modules/node/src/Controller/NodeController.php @@ -285,7 +285,7 @@ public function addPageTitle(NodeTypeInterface $node_type) { /** * Gets a list of node revision IDs for a specific node. * - * @param \Drupal\node\NodeInterface + * @param \Drupal\node\NodeInterface $node * The node entity. * @param \Drupal\node\NodeStorageInterface $node_storage * The node storage handler. diff --git a/core/modules/node/src/NodeStorageInterface.php b/core/modules/node/src/NodeStorageInterface.php index b09d7b7..19668c2 100644 --- a/core/modules/node/src/NodeStorageInterface.php +++ b/core/modules/node/src/NodeStorageInterface.php @@ -14,7 +14,7 @@ /** * Gets a list of node revision IDs for a specific node. * - * @param \Drupal\node\NodeInterface + * @param \Drupal\node\NodeInterface $node * The node entity. * * @return int[] @@ -36,7 +36,7 @@ public function userRevisionIds(AccountInterface $account); /** * Counts the number of revisions in the default language. * - * @param \Drupal\node\NodeInterface + * @param \Drupal\node\NodeInterface $node * The node entity. * * @return int diff --git a/core/modules/node/src/NodeTypeAccessControlHandler.php b/core/modules/node/src/NodeTypeAccessControlHandler.php index ad82232..b14bae3 100644 --- a/core/modules/node/src/NodeTypeAccessControlHandler.php +++ b/core/modules/node/src/NodeTypeAccessControlHandler.php @@ -21,7 +21,6 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter switch ($operation) { case 'view': return AccessResult::allowedIfHasPermission($account, 'access content'); - break; case 'delete': if ($entity->isLocked()) { @@ -34,7 +33,7 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter default: return parent::checkAccess($entity, $operation, $account); - break; + } } diff --git a/core/modules/node/src/Plugin/migrate/D6NodeDeriver.php b/core/modules/node/src/Plugin/migrate/D6NodeDeriver.php index f3e1a92..51ce1de 100644 --- a/core/modules/node/src/Plugin/migrate/D6NodeDeriver.php +++ b/core/modules/node/src/Plugin/migrate/D6NodeDeriver.php @@ -3,6 +3,7 @@ namespace Drupal\node\Plugin\migrate; use Drupal\Component\Plugin\Derivative\DeriverBase; +use Drupal\Component\Plugin\Exception\PluginNotFoundException; use Drupal\Component\Plugin\PluginManagerInterface; use Drupal\Core\Database\DatabaseExceptionWrapper; use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; @@ -128,14 +129,15 @@ public function getDerivativeDefinitions($base_plugin_definition) { if (isset($fields[$node_type])) { foreach ($fields[$node_type] as $field_name => $info) { $field_type = $info['type']; - if ($this->cckPluginManager->hasDefinition($info['type'])) { + try { + $plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, ['core' => 6], $migration); if (!isset($this->cckPluginCache[$field_type])) { - $this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($field_type, ['core' => 6], $migration); + $this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($plugin_id, ['core' => 6], $migration); } $this->cckPluginCache[$field_type] ->processCckFieldValues($migration, $field_name, $info); } - else { + catch (PluginNotFoundException $ex) { $migration->setProcessOfProperty($field_name, $field_name); } } diff --git a/core/modules/node/src/Plugin/migrate/D7NodeDeriver.php b/core/modules/node/src/Plugin/migrate/D7NodeDeriver.php index 6efa1c7..2a8c0e4 100644 --- a/core/modules/node/src/Plugin/migrate/D7NodeDeriver.php +++ b/core/modules/node/src/Plugin/migrate/D7NodeDeriver.php @@ -3,6 +3,7 @@ namespace Drupal\node\Plugin\migrate; use Drupal\Component\Plugin\Derivative\DeriverBase; +use Drupal\Component\Plugin\Exception\PluginNotFoundException; use Drupal\Component\Plugin\PluginManagerInterface; use Drupal\Core\Database\DatabaseExceptionWrapper; use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; @@ -98,14 +99,15 @@ public function getDerivativeDefinitions($base_plugin_definition) { if (isset($fields[$node_type])) { foreach ($fields[$node_type] as $field_name => $info) { $field_type = $info['type']; - if ($this->cckPluginManager->hasDefinition($field_type)) { + try { + $plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, ['core' => 7], $migration); if (!isset($this->cckPluginCache[$field_type])) { - $this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($field_type, ['core' => 7], $migration); + $this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($plugin_id, ['core' => 7], $migration); } $this->cckPluginCache[$field_type] ->processCckFieldValues($migration, $field_name, $info); } - else { + catch (PluginNotFoundException $ex) { $migration->setProcessOfProperty($field_name, $field_name); } } diff --git a/core/modules/node/src/Plugin/migrate/source/d6/Node.php b/core/modules/node/src/Plugin/migrate/source/d6/Node.php index 5ca698f..8686415 100644 --- a/core/modules/node/src/Plugin/migrate/source/d6/Node.php +++ b/core/modules/node/src/Plugin/migrate/source/d6/Node.php @@ -261,7 +261,7 @@ public function getIds() { /** * Adapt our query for translations. * - * @param \Drupal\Core\Database\Query\SelectInterface + * @param \Drupal\Core\Database\Query\SelectInterface $query * The generated query. */ protected function handleTranslations(SelectInterface $query) { diff --git a/core/modules/node/src/Plugin/views/argument/Vid.php b/core/modules/node/src/Plugin/views/argument/Vid.php index 77889b8..8788b67 100644 --- a/core/modules/node/src/Plugin/views/argument/Vid.php +++ b/core/modules/node/src/Plugin/views/argument/Vid.php @@ -39,7 +39,7 @@ class Vid extends NumericArgument { * The plugin implementation definition. * @param \Drupal\Core\Database\Connection $database * Database Service Object. - * @param \Drupal\node\NodeStorageInterface + * @param \Drupal\node\NodeStorageInterface $node_storage * The node storage. */ public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, NodeStorageInterface $node_storage) { diff --git a/core/modules/node/src/Plugin/views/wizard/Node.php b/core/modules/node/src/Plugin/views/wizard/Node.php index c5842a8..ce267e6 100644 --- a/core/modules/node/src/Plugin/views/wizard/Node.php +++ b/core/modules/node/src/Plugin/views/wizard/Node.php @@ -49,7 +49,7 @@ class Node extends WizardPluginBase { public function getAvailableSorts() { // You can't execute functions in properties, so override the method return array( - 'node_field_data-title:DESC' => $this->t('Title') + 'node_field_data-title:ASC' => $this->t('Title') ); } diff --git a/core/modules/node/src/Tests/NodeAccessGrantsTest.php b/core/modules/node/src/Tests/NodeAccessGrantsTest.php deleted file mode 100644 index 4017411..0000000 --- a/core/modules/node/src/Tests/NodeAccessGrantsTest.php +++ /dev/null @@ -1,33 +0,0 @@ -drupalCreateUser(['access content']); - $node = $this->drupalCreateNode(); - $this->assertNodeAccess(['random_operation' => FALSE], $node, $web_user); - } - -} diff --git a/core/modules/node/src/Tests/NodeAccessLanguageAwareCombinationTest.php b/core/modules/node/src/Tests/NodeAccessLanguageAwareCombinationTest.php index acf9217..1ee5bc2 100644 --- a/core/modules/node/src/Tests/NodeAccessLanguageAwareCombinationTest.php +++ b/core/modules/node/src/Tests/NodeAccessLanguageAwareCombinationTest.php @@ -254,9 +254,9 @@ function testNodeAccessLanguageAwareCombination() { // Query with no language specified. The fallback (hu or und) will be used. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $this->webUser) - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $this->webUser) + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // Four nodes should be returned with public Hungarian translations or the @@ -269,10 +269,10 @@ function testNodeAccessLanguageAwareCombination() { // Query with Hungarian (hu) specified. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $this->webUser) - ->addMetaData('langcode', 'hu') - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $this->webUser) + ->addMetaData('langcode', 'hu') + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // Three nodes should be returned (with public Hungarian translations). @@ -283,10 +283,10 @@ function testNodeAccessLanguageAwareCombination() { // Query with Catalan (ca) specified. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $this->webUser) - ->addMetaData('langcode', 'ca') - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $this->webUser) + ->addMetaData('langcode', 'ca') + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // Three nodes should be returned (with public Catalan translations). @@ -297,10 +297,10 @@ function testNodeAccessLanguageAwareCombination() { // Query with German (de) specified. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $this->webUser) - ->addMetaData('langcode', 'de') - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $this->webUser) + ->addMetaData('langcode', 'de') + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // There are no nodes with German translations, so no results are returned. @@ -309,9 +309,9 @@ function testNodeAccessLanguageAwareCombination() { // Query the nodes table as admin user (full access) with the node access // tag and no specific langcode. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $this->adminUser) - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $this->adminUser) + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // All nodes are returned. @@ -320,10 +320,10 @@ function testNodeAccessLanguageAwareCombination() { // Query the nodes table as admin user (full access) with the node access // tag and langcode de. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $this->adminUser) - ->addMetaData('langcode', 'de') - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $this->adminUser) + ->addMetaData('langcode', 'de') + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // Even though there is no German translation, all nodes are returned diff --git a/core/modules/node/src/Tests/NodeAccessLanguageAwareTest.php b/core/modules/node/src/Tests/NodeAccessLanguageAwareTest.php index ea5a883..0609b7b 100644 --- a/core/modules/node/src/Tests/NodeAccessLanguageAwareTest.php +++ b/core/modules/node/src/Tests/NodeAccessLanguageAwareTest.php @@ -195,9 +195,9 @@ function testNodeAccessLanguageAware() { // Query with no language specified. The fallback (hu) will be used. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $this->webUser) - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $this->webUser) + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // Three nodes should be returned: @@ -211,10 +211,10 @@ function testNodeAccessLanguageAware() { // Query with Hungarian (hu) specified. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $this->webUser) - ->addMetaData('langcode', 'hu') - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $this->webUser) + ->addMetaData('langcode', 'hu') + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // Two nodes should be returned: the node with both translations public, and @@ -225,10 +225,10 @@ function testNodeAccessLanguageAware() { // Query with Catalan (ca) specified. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $this->webUser) - ->addMetaData('langcode', 'ca') - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $this->webUser) + ->addMetaData('langcode', 'ca') + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // Two nodes should be returned: the node with both translations public, and @@ -239,10 +239,10 @@ function testNodeAccessLanguageAware() { // Query with German (de) specified. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $this->webUser) - ->addMetaData('langcode', 'de') - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $this->webUser) + ->addMetaData('langcode', 'de') + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // There are no nodes with German translations, so no results are returned. @@ -251,9 +251,9 @@ function testNodeAccessLanguageAware() { // Query the nodes table as admin user (full access) with the node access // tag and no specific langcode. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $this->adminUser) - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $this->adminUser) + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // All nodes are returned. @@ -262,10 +262,10 @@ function testNodeAccessLanguageAware() { // Query the nodes table as admin user (full access) with the node access // tag and langcode de. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $this->adminUser) - ->addMetaData('langcode', 'de') - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $this->adminUser) + ->addMetaData('langcode', 'de') + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // Even though there is no German translation, all nodes are returned diff --git a/core/modules/node/src/Tests/NodeAccessLanguageTest.php b/core/modules/node/src/Tests/NodeAccessLanguageTest.php index 5d55a5b..e0393a0 100644 --- a/core/modules/node/src/Tests/NodeAccessLanguageTest.php +++ b/core/modules/node/src/Tests/NodeAccessLanguageTest.php @@ -204,9 +204,9 @@ function testNodeAccessQueryTag() { // Query the nodes table as the web user with the node access tag and no // specific langcode. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $web_user) - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $web_user) + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // The public node and no language node should be returned. Because no @@ -218,10 +218,10 @@ function testNodeAccessQueryTag() { // Query the nodes table as the web user with the node access tag and // langcode de. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $web_user) - ->addMetaData('langcode', 'de') - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $web_user) + ->addMetaData('langcode', 'de') + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // Because no nodes are created in German, no nodes are returned. @@ -230,9 +230,9 @@ function testNodeAccessQueryTag() { // Query the nodes table as admin user (full access) with the node access // tag and no specific langcode. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $admin_user) - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $admin_user) + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // All nodes are returned. @@ -241,10 +241,10 @@ function testNodeAccessQueryTag() { // Query the nodes table as admin user (full access) with the node access // tag and langcode de. $select = db_select('node', 'n') - ->fields('n', array('nid')) - ->addMetaData('account', $admin_user) - ->addMetaData('langcode', 'de') - ->addTag('node_access'); + ->fields('n', array('nid')) + ->addMetaData('account', $admin_user) + ->addMetaData('langcode', 'de') + ->addTag('node_access'); $nids = $select->execute()->fetchAllAssoc('nid'); // All nodes are returned because node access tag is not invoked when the diff --git a/core/modules/node/src/Tests/NodeAccessRebuildNodeGrantsTest.php b/core/modules/node/src/Tests/NodeAccessRebuildNodeGrantsTest.php index d5cca95..d151aad 100644 --- a/core/modules/node/src/Tests/NodeAccessRebuildNodeGrantsTest.php +++ b/core/modules/node/src/Tests/NodeAccessRebuildNodeGrantsTest.php @@ -2,6 +2,8 @@ namespace Drupal\node\Tests; +use Drupal\node\Entity\NodeType; + /** * Ensures that node access rebuild functions work correctly even * when other modules implements hook_node_grants(). @@ -11,20 +13,27 @@ class NodeAccessRebuildNodeGrantsTest extends NodeTestBase { /** - * A user to test the rebuild nodes feature. + * A user to create nodes that only it has access to. * * @var \Drupal\user\UserInterface */ protected $webUser; /** + * A user to test the rebuild nodes feature which can't access the nodes. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** * {@inheritdoc} */ protected function setUp() { parent::setUp(); - $admin_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports', 'bypass node access')); - $this->drupalLogin($admin_user); + $this->adminUser = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports')); + $this->drupalLogin($this->adminUser); $this->webUser = $this->drupalCreateUser(); } @@ -34,25 +43,54 @@ protected function setUp() { */ public function testNodeAccessRebuildNodeGrants() { \Drupal::service('module_installer')->install(['node_access_test']); + \Drupal::state()->set('node_access_test.private', TRUE); + node_access_test_add_field(NodeType::load('page')); $this->resetAll(); - $node = $this->drupalCreateNode(array( - 'uid' => $this->webUser->id(), - )); - + // Create 30 nodes so that _node_access_rebuild_batch_operation() has to run + // more than once. + for ($i = 0; $i < 30; $i++) { + $nodes[] = $this->drupalCreateNode(array( + 'uid' => $this->webUser->id(), + 'private' => [['value' => 1]] + )); + } + + /** @var \Drupal\node\NodeGrantDatabaseStorageInterface $grant_storage */ + $grant_storage = \Drupal::service('node.grant_storage'); // Default realm access and node records are present. - $this->assertTrue(\Drupal::service('node.grant_storage')->access($node, 'view', $this->webUser), 'The expected node access records are present'); + foreach ($nodes as $node) { + $this->assertTrue($node->private->value); + $this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'Prior to rebuilding node access the grant storage returns allowed for the node author.'); + $this->assertTrue($grant_storage->access($node, 'view', $this->adminUser)->isAllowed(), 'Prior to rebuilding node access the grant storage returns allowed for the admin user.'); + } + $this->assertEqual(1, \Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is an all realm access record'); $this->assertTrue(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions need to be rebuilt'); // Rebuild permissions. - $this->drupalGet('admin/reports/status/rebuild'); + $this->drupalGet('admin/reports/status'); + $this->clickLink(t('Rebuild permissions')); $this->drupalPostForm(NULL, array(), t('Rebuild permissions')); $this->assertText(t('The content access permissions have been rebuilt.')); - // Test if the rebuild has been successful. + // Test if the rebuild by user that cannot bypass node access and does not + // have access to the nodes has been successful. + $this->assertFalse($this->adminUser->hasPermission('bypass node access')); $this->assertNull(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions have been rebuilt'); - $this->assertTrue(\Drupal::service('node.grant_storage')->access($node, 'view', $this->webUser), 'The expected node access records are present'); + foreach ($nodes as $node) { + $this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'After rebuilding node access the grant storage returns allowed for the node author.'); + $this->assertFalse($grant_storage->access($node, 'view', $this->adminUser)->isForbidden(), 'After rebuilding node access the grant storage returns forbidden for the admin user.'); + } + $this->assertFalse(\Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is no all realm access record'); + + // Test an anonymous node access rebuild from code. + $this->drupalLogout(); + node_access_rebuild(); + foreach ($nodes as $node) { + $this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'After rebuilding node access the grant storage returns allowed for the node author.'); + $this->assertFalse($grant_storage->access($node, 'view', $this->adminUser)->isForbidden(), 'After rebuilding node access the grant storage returns forbidden for the admin user.'); + } $this->assertFalse(\Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is no all realm access record'); } diff --git a/core/modules/node/src/Tests/NodeAccessRebuildTest.php b/core/modules/node/src/Tests/NodeAccessRebuildTest.php deleted file mode 100644 index 187f3a0..0000000 --- a/core/modules/node/src/Tests/NodeAccessRebuildTest.php +++ /dev/null @@ -1,36 +0,0 @@ -drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports')); - $this->drupalLogin($web_user); - $this->webUser = $web_user; - } - - /** - * Tests rebuilding the node access permissions table. - */ - function testNodeAccessRebuild() { - $this->drupalGet('admin/reports/status'); - $this->clickLink(t('Rebuild permissions')); - $this->drupalPostForm(NULL, array(), t('Rebuild permissions')); - $this->assertText(t('Content permissions have been rebuilt.')); - } - -} diff --git a/core/modules/node/src/Tests/NodeAccessTest.php b/core/modules/node/src/Tests/NodeAccessTest.php deleted file mode 100644 index 9913c38..0000000 --- a/core/modules/node/src/Tests/NodeAccessTest.php +++ /dev/null @@ -1,72 +0,0 @@ -config('user.role.' . RoleInterface::AUTHENTICATED_ID)->set('permissions', array())->save(); - } - - /** - * Runs basic tests for node_access function. - */ - function testNodeAccess() { - // Ensures user without 'access content' permission can do nothing. - $web_user1 = $this->drupalCreateUser(array('create page content', 'edit any page content', 'delete any page content')); - $node1 = $this->drupalCreateNode(array('type' => 'page')); - $this->assertNodeCreateAccess($node1->bundle(), FALSE, $web_user1); - $this->assertNodeAccess(array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE), $node1, $web_user1); - - // Ensures user with 'bypass node access' permission can do everything. - $web_user2 = $this->drupalCreateUser(array('bypass node access')); - $node2 = $this->drupalCreateNode(array('type' => 'page')); - $this->assertNodeCreateAccess($node2->bundle(), TRUE, $web_user2); - $this->assertNodeAccess(array('view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $node2, $web_user2); - - // User cannot 'view own unpublished content'. - $web_user3 = $this->drupalCreateUser(array('access content')); - $node3 = $this->drupalCreateNode(array('status' => 0, 'uid' => $web_user3->id())); - $this->assertNodeAccess(array('view' => FALSE), $node3, $web_user3); - - // User cannot create content without permission. - $this->assertNodeCreateAccess($node3->bundle(), FALSE, $web_user3); - - // User can 'view own unpublished content', but another user cannot. - $web_user4 = $this->drupalCreateUser(array('access content', 'view own unpublished content')); - $web_user5 = $this->drupalCreateUser(array('access content', 'view own unpublished content')); - $node4 = $this->drupalCreateNode(array('status' => 0, 'uid' => $web_user4->id())); - $this->assertNodeAccess(array('view' => TRUE, 'update' => FALSE), $node4, $web_user4); - $this->assertNodeAccess(array('view' => FALSE), $node4, $web_user5); - - // Tests the default access provided for a published node. - $node5 = $this->drupalCreateNode(); - $this->assertNodeAccess(array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE), $node5, $web_user3); - - // Tests the "edit any BUNDLE" and "delete any BUNDLE" permissions. - $web_user6 = $this->drupalCreateUser(array('access content', 'edit any page content', 'delete any page content')); - $node6 = $this->drupalCreateNode(array('type' => 'page')); - $this->assertNodeAccess(array('view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $node6, $web_user6); - - // Tests the "edit own BUNDLE" and "delete own BUNDLE" permission. - $web_user7 = $this->drupalCreateUser(array('access content', 'edit own page content', 'delete own page content')); - // User should not be able to edit or delete nodes they do not own. - $this->assertNodeAccess(array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE), $node6, $web_user7); - - // User should be able to edit or delete nodes they own. - $node7 = $this->drupalCreateNode(array('type' => 'page', 'uid' => $web_user7->id())); - $this->assertNodeAccess(array('view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $node7, $web_user7); - } - -} diff --git a/core/modules/node/src/Tests/NodeFieldMultilingualTest.php b/core/modules/node/src/Tests/NodeFieldMultilingualTest.php index 9beae5d..3bd1a51 100644 --- a/core/modules/node/src/Tests/NodeFieldMultilingualTest.php +++ b/core/modules/node/src/Tests/NodeFieldMultilingualTest.php @@ -119,7 +119,7 @@ function testMultilingualDisplaySettings() { // Check if node body is showed. $this->drupalGet('node/' . $node->id()); - $body = $this->xpath('//article[contains(concat(" ", normalize-space(@class), " "), :node-class)]//div[contains(concat(" ", normalize-space(@class), " "), :content-class)]/descendant::p', array( + $body = $this->xpath('//article[contains(concat(" ", normalize-space(@class), " "), :node-class)]//div[contains(concat(" ", normalize-space(@class), " "), :content-class)]/descendant::p', array( ':node-class' => ' node ', ':content-class' => 'node__content', )); diff --git a/core/modules/node/src/Tests/Views/BulkFormAccessTest.php b/core/modules/node/src/Tests/Views/BulkFormAccessTest.php index a9f9ba9..6dada72 100644 --- a/core/modules/node/src/Tests/Views/BulkFormAccessTest.php +++ b/core/modules/node/src/Tests/Views/BulkFormAccessTest.php @@ -116,6 +116,10 @@ public function testNodeEditAccess() { 'action' => 'node_unpublish_action', ); $this->drupalPostForm('test-node-bulk-form', $edit, t('Apply to selected items')); + // Test that the action message isn't shown. + $this->assertNoRaw(SafeMarkup::format('%action was applied to 1 item.', [ + '%action' => 'Unpublish content', + ])); // Re-load the node and check the status. $node = Node::load($node->id()); $this->assertTrue($node->isPublished(), 'The node is still published.'); diff --git a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php index bbec07c..f7b98bd 100644 --- a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php +++ b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php @@ -47,7 +47,7 @@ protected function setUp() { 'd7_taxonomy_vocabulary', 'd7_field', 'd7_field_instance', - 'd7_node:test_content_type', + 'd7_node', 'd7_node:article', ]); } diff --git a/core/modules/node/tests/src/Kernel/NodeAccessTest.php b/core/modules/node/tests/src/Kernel/NodeAccessTest.php new file mode 100644 index 0000000..b8e2251 --- /dev/null +++ b/core/modules/node/tests/src/Kernel/NodeAccessTest.php @@ -0,0 +1,262 @@ +installSchema('system', 'sequences'); + $this->installSchema('node', 'node_access'); + $this->installEntitySchema('user'); + $this->installEntitySchema('node'); + $this->installConfig('filter'); + $this->installConfig('node'); + $this->accessHandler = $this->container->get('entity_type.manager') + ->getAccessControlHandler('node'); + // Clear permissions for authenticated users. + $this->config('user.role.' . RoleInterface::AUTHENTICATED_ID) + ->set('permissions', []) + ->save(); + + // Create user 1 who has special permissions. + $this->drupalCreateUser(); + + // Create a node type. + $this->drupalCreateContentType(array( + 'type' => 'page', + 'name' => 'Basic page', + 'display_submitted' => FALSE, + )); + } + + /** + * Runs basic tests for node_access function. + */ + public function testNodeAccess() { + // Ensures user without 'access content' permission can do nothing. + $web_user1 = $this->drupalCreateUser([ + 'create page content', + 'edit any page content', + 'delete any page content', + ]); + $node1 = $this->drupalCreateNode(['type' => 'page']); + $this->assertNodeCreateAccess($node1->bundle(), FALSE, $web_user1); + $this->assertNodeAccess([ + 'view' => FALSE, + 'update' => FALSE, + 'delete' => FALSE, + ], $node1, $web_user1); + + // Ensures user with 'bypass node access' permission can do everything. + $web_user2 = $this->drupalCreateUser(['bypass node access']); + $node2 = $this->drupalCreateNode(['type' => 'page']); + $this->assertNodeCreateAccess($node2->bundle(), TRUE, $web_user2); + $this->assertNodeAccess([ + 'view' => TRUE, + 'update' => TRUE, + 'delete' => TRUE, + ], $node2, $web_user2); + + // User cannot 'view own unpublished content'. + $web_user3 = $this->drupalCreateUser(['access content']); + $node3 = $this->drupalCreateNode([ + 'status' => 0, + 'uid' => $web_user3->id(), + ]); + $this->assertNodeAccess(['view' => FALSE], $node3, $web_user3); + + // User cannot create content without permission. + $this->assertNodeCreateAccess($node3->bundle(), FALSE, $web_user3); + + // User can 'view own unpublished content', but another user cannot. + $web_user4 = $this->drupalCreateUser([ + 'access content', + 'view own unpublished content', + ]); + $web_user5 = $this->drupalCreateUser([ + 'access content', + 'view own unpublished content', + ]); + $node4 = $this->drupalCreateNode([ + 'status' => 0, + 'uid' => $web_user4->id(), + ]); + $this->assertNodeAccess([ + 'view' => TRUE, + 'update' => FALSE, + ], $node4, $web_user4); + $this->assertNodeAccess(['view' => FALSE], $node4, $web_user5); + + // Tests the default access provided for a published node. + $node5 = $this->drupalCreateNode(); + $this->assertNodeAccess([ + 'view' => TRUE, + 'update' => FALSE, + 'delete' => FALSE, + ], $node5, $web_user3); + + // Tests the "edit any BUNDLE" and "delete any BUNDLE" permissions. + $web_user6 = $this->drupalCreateUser([ + 'access content', + 'edit any page content', + 'delete any page content', + ]); + $node6 = $this->drupalCreateNode(['type' => 'page']); + $this->assertNodeAccess([ + 'view' => TRUE, + 'update' => TRUE, + 'delete' => TRUE, + ], $node6, $web_user6); + + // Tests the "edit own BUNDLE" and "delete own BUNDLE" permission. + $web_user7 = $this->drupalCreateUser([ + 'access content', + 'edit own page content', + 'delete own page content', + ]); + // User should not be able to edit or delete nodes they do not own. + $this->assertNodeAccess([ + 'view' => TRUE, + 'update' => FALSE, + 'delete' => FALSE, + ], $node6, $web_user7); + + // User should be able to edit or delete nodes they own. + $node7 = $this->drupalCreateNode([ + 'type' => 'page', + 'uid' => $web_user7->id(), + ]); + $this->assertNodeAccess([ + 'view' => TRUE, + 'update' => TRUE, + 'delete' => TRUE, + ], $node7, $web_user7); + } + + /** + * Test operations not supported by node grants. + */ + public function testUnsupportedOperation() { + $this->enableModules(['node_access_test_empty']); + $web_user = $this->drupalCreateUser(['access content']); + $node = $this->drupalCreateNode(); + $this->assertNodeAccess(['random_operation' => FALSE], $node, $web_user); + } + + /** + * Asserts that node access correctly grants or denies access. + * + * @param array $ops + * An associative array of the expected node access grants for the node + * and account, with each key as the name of an operation (e.g. 'view', + * 'delete') and each value a Boolean indicating whether access to that + * operation should be granted. + * @param \Drupal\node\NodeInterface $node + * The node object to check. + * @param \Drupal\Core\Session\AccountInterface $account + * The user account for which to check access. + */ + public function assertNodeAccess(array $ops, NodeInterface $node, AccountInterface $account) { + foreach ($ops as $op => $result) { + $this->assertEquals($result, $this->accessHandler->access($node, $op, $account), $this->nodeAccessAssertMessage($op, $result, $node->language() + ->getId())); + } + } + + /** + * Asserts that node create access correctly grants or denies access. + * + * @param string $bundle + * The node bundle to check access to. + * @param bool $result + * Whether access should be granted or not. + * @param \Drupal\Core\Session\AccountInterface $account + * The user account for which to check access. + * @param string|null $langcode + * (optional) The language code indicating which translation of the node + * to check. If NULL, the untranslated (fallback) access is checked. + */ + public function assertNodeCreateAccess($bundle, $result, AccountInterface $account, $langcode = NULL) { + $this->assertEquals($result, $this->accessHandler->createAccess($bundle, $account, [ + 'langcode' => $langcode, + ]), $this->nodeAccessAssertMessage('create', $result, $langcode)); + } + + /** + * Constructs an assert message to display which node access was tested. + * + * @param string $operation + * The operation to check access for. + * @param bool $result + * Whether access should be granted or not. + * @param string|null $langcode + * (optional) The language code indicating which translation of the node + * to check. If NULL, the untranslated (fallback) access is checked. + * + * @return string + * An assert message string which contains information in plain English + * about the node access permission test that was performed. + */ + public function nodeAccessAssertMessage($operation, $result, $langcode = NULL) { + return new FormattableMarkup( + 'Node access returns @result with operation %op, language code %langcode.', + [ + '@result' => $result ? 'true' : 'false', + '%op' => $operation, + '%langcode' => !empty($langcode) ? $langcode : 'empty', + ] + ); + } + +} diff --git a/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php b/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php index 55108d5..aaea32f 100644 --- a/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php +++ b/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php @@ -65,11 +65,11 @@ public function getSettableOptions(AccountInterface $account = NULL) { /** * {@inheritdoc} */ - public static function generateSampleValue(FieldDefinitionInterface $field_definition) { - $allowed_options = options_allowed_values($field_definition->getFieldStorageDefinition()); - $values['value'] = array_rand($allowed_options); - return $values; - } + public static function generateSampleValue(FieldDefinitionInterface $field_definition) { + $allowed_options = options_allowed_values($field_definition->getFieldStorageDefinition()); + $values['value'] = array_rand($allowed_options); + return $values; + } /** * {@inheritdoc} diff --git a/core/modules/outside_in/outside_in.module b/core/modules/outside_in/outside_in.module index d7aa0ce..2822e24 100644 --- a/core/modules/outside_in/outside_in.module +++ b/core/modules/outside_in/outside_in.module @@ -52,24 +52,22 @@ function outside_in_block_view_alter(array &$build) { } /** - * Implements hook_page_top(). + * Implements hook_element_info_alter(). */ -function outside_in_page_top(array &$page_top) { - // Opens a div for consistent wrapping to {{ page }} render in all themes. - $page_top['outside_in_tray_open'] = [ - '#markup' => '
', - '#weight' => 1000, - ]; +function outside_in_element_info_alter(&$type) { + if (isset($type['page'])) { + $type['page']['#theme_wrappers']['outside_in_page_wrapper'] = ['#weight' => -1000]; + } } /** - * Implements hook_page_bottom(). + * Implements hook_theme(). */ -function outside_in_page_bottom(array &$page_bottom) { - // Closes a div for consistent wrapping to {{ page }} render in all themes. - $page_bottom['outside_in_tray_close'] = [ - '#markup' => '
', - '#weight' => -1000, +function outside_in_theme() { + return [ + 'outside_in_page_wrapper' => [ + 'variables' => ['children' => NULL], + ], ]; } diff --git a/core/modules/outside_in/templates/outside-in-page-wrapper.html.twig b/core/modules/outside_in/templates/outside-in-page-wrapper.html.twig new file mode 100644 index 0000000..76ff5ce --- /dev/null +++ b/core/modules/outside_in/templates/outside-in-page-wrapper.html.twig @@ -0,0 +1,22 @@ +{# +/** + * @file + * Default theme implementation for a page wrapper. + * + * For consistent wrapping to {{ page }} render in all themes. + * + * Available variables: + * - children: Contains the child elements of the page. + * + * @ingroup themeable + */ +#} +{% if children %} + {% spaceless %} +
+
+ {{ children }} +
+
+ {% endspaceless %} +{% endif %} diff --git a/core/modules/path/tests/src/Kernel/Migrate/d6/MigrateUrlAliasTest.php b/core/modules/path/tests/src/Kernel/Migrate/d6/MigrateUrlAliasTest.php index 363846f..120d072 100644 --- a/core/modules/path/tests/src/Kernel/Migrate/d6/MigrateUrlAliasTest.php +++ b/core/modules/path/tests/src/Kernel/Migrate/d6/MigrateUrlAliasTest.php @@ -29,7 +29,7 @@ protected function setUp() { /** * Assert a path. * - * @param string pid + * @param string $pid * The path id. * @param array $conditions * The path conditions. diff --git a/core/modules/quickedit/src/EditorSelector.php b/core/modules/quickedit/src/EditorSelector.php index 9e2252e..a531b1a 100644 --- a/core/modules/quickedit/src/EditorSelector.php +++ b/core/modules/quickedit/src/EditorSelector.php @@ -36,9 +36,9 @@ class EditorSelector implements EditorSelectorInterface { /** * Constructs a new EditorSelector. * - * @param \Drupal\Component\Plugin\PluginManagerInterface + * @param \Drupal\Component\Plugin\PluginManagerInterface $editor_manager * The manager for editor plugins. - * @param \Drupal\Core\Field\FormatterPluginManager + * @param \Drupal\Core\Field\FormatterPluginManager $formatter_manager * The manager for formatter plugins. */ public function __construct(PluginManagerInterface $editor_manager, FormatterPluginManager $formatter_manager) { diff --git a/core/modules/quickedit/src/MetadataGenerator.php b/core/modules/quickedit/src/MetadataGenerator.php index 31bde4a..a22bf65 100644 --- a/core/modules/quickedit/src/MetadataGenerator.php +++ b/core/modules/quickedit/src/MetadataGenerator.php @@ -41,7 +41,7 @@ class MetadataGenerator implements MetadataGeneratorInterface { * An object that checks if a user has access to edit a given field. * @param \Drupal\quickedit\EditorSelectorInterface $editor_selector * An object that determines which editor to attach to a given field. - * @param \Drupal\Component\Plugin\PluginManagerInterface + * @param \Drupal\Component\Plugin\PluginManagerInterface $editor_manager * The manager for editor plugins. */ public function __construct(EditEntityFieldAccessCheckInterface $access_checker, EditorSelectorInterface $editor_selector, PluginManagerInterface $editor_manager) { diff --git a/core/modules/rdf/tests/src/Kernel/Field/FieldRdfaTestBase.php b/core/modules/rdf/tests/src/Kernel/Field/FieldRdfaTestBase.php index 99e64ac..7431185 100644 --- a/core/modules/rdf/tests/src/Kernel/Field/FieldRdfaTestBase.php +++ b/core/modules/rdf/tests/src/Kernel/Field/FieldRdfaTestBase.php @@ -167,7 +167,7 @@ protected function parseContent($content) { * @param array $arguments * Some arguments for the xpath. * - * @return array|FALSE + * @return array|false * The return value of the xpath search. For details on the xpath string * format and return values see the SimpleXML documentation, * http://php.net/manual/function.simplexml-element-xpath.php. diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php index 5cf42dd..18a1101 100644 --- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php @@ -65,7 +65,7 @@ class EntityResource extends ResourceBase implements DependentPluginInterface { * The available serialization formats. * @param \Psr\Log\LoggerInterface $logger * A logger instance. - * @param \Drupal\Core\Config\ConfigFactoryInterface + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory. */ public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, $serializer_formats, LoggerInterface $logger, ConfigFactoryInterface $config_factory) { diff --git a/core/modules/rest/src/Plugin/views/display/RestExport.php b/core/modules/rest/src/Plugin/views/display/RestExport.php index cf5e232..1a7a7f1 100644 --- a/core/modules/rest/src/Plugin/views/display/RestExport.php +++ b/core/modules/rest/src/Plugin/views/display/RestExport.php @@ -353,12 +353,17 @@ public function collectRoutes(RouteCollection $collection) { public static function buildResponse($view_id, $display_id, array $args = []) { $build = static::buildBasicRenderable($view_id, $display_id, $args); + // Setup an empty response so headers can be added as needed during views + // rendering and processing. + $response = new CacheableResponse('', 200); + $build['#response'] = $response; + /** @var \Drupal\Core\Render\RendererInterface $renderer */ $renderer = \Drupal::service('renderer'); - $output = $renderer->renderRoot($build); + $output = (string) $renderer->renderRoot($build); - $response = new CacheableResponse($output, 200); + $response->setContent($output); $cache_metadata = CacheableMetadata::createFromRenderArray($build); $response->addCacheableDependency($cache_metadata); diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php index 6724045..2a75227 100644 --- a/core/modules/rest/src/Tests/RESTTestBase.php +++ b/core/modules/rest/src/Tests/RESTTestBase.php @@ -115,15 +115,15 @@ protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL, $ ); break; - case 'HEAD': - $curl_options = array( - CURLOPT_HTTPGET => FALSE, - CURLOPT_CUSTOMREQUEST => 'HEAD', - CURLOPT_URL => $url, - CURLOPT_NOBODY => TRUE, - CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type), - ); - break; + case 'HEAD': + $curl_options = array( + CURLOPT_HTTPGET => FALSE, + CURLOPT_CUSTOMREQUEST => 'HEAD', + CURLOPT_URL => $url, + CURLOPT_NOBODY => TRUE, + CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type), + ); + break; case 'POST': $curl_options = array( @@ -440,7 +440,7 @@ protected function entityPermissions($entity_type_id, $operation) { * @param string $location_url * The URL returned in the Location header. * - * @return \Drupal\Core\Entity\Entity|FALSE. + * @return \Drupal\Core\Entity\Entity|false * The entity or FALSE if there is no matching entity. */ protected function loadEntityFromLocationHeader($location_url) { diff --git a/core/modules/rest/src/Tests/Views/StyleSerializerTest.php b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php index 50e0e74..4c004fc 100644 --- a/core/modules/rest/src/Tests/Views/StyleSerializerTest.php +++ b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php @@ -353,7 +353,7 @@ public function testResponseFormatConfiguration() { $this->drupalGetWithFormat('test/serialize/field', 'json'); $this->assertHeader('content-type', 'application/json'); $this->assertResponse(406, 'A 406 response was returned when JSON was requested.'); - // Should return a 200. + // Should return a 200. $this->drupalGetWithFormat('test/serialize/field', 'xml'); $this->assertHeader('content-type', 'text/xml; charset=UTF-8'); $this->assertResponse(200, 'A 200 response was returned when XML was requested.'); @@ -551,7 +551,7 @@ public function testLivePreview() { // We set up a request so it looks like an request in the live preview. $request = new Request(); $request->query->add([MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']); - /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */ + /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */ $request_stack = \Drupal::service('request_stack'); $request_stack->push($request); diff --git a/core/modules/rest/tests/modules/rest_test_views/rest_test_views.module b/core/modules/rest/tests/modules/rest_test_views/rest_test_views.module new file mode 100644 index 0000000..e46d809 --- /dev/null +++ b/core/modules/rest/tests/modules/rest_test_views/rest_test_views.module @@ -0,0 +1,21 @@ +id() === 'test_serializer_display_entity') { + if ($value = \Drupal::state()->get('rest_test_views_set_header', FALSE)) { + $view->getResponse()->headers->set('Custom-Header', $value); + } + } + +} diff --git a/core/modules/rest/tests/src/Kernel/Views/RestExportTest.php b/core/modules/rest/tests/src/Kernel/Views/RestExportTest.php new file mode 100644 index 0000000..f3a8f04 --- /dev/null +++ b/core/modules/rest/tests/src/Kernel/Views/RestExportTest.php @@ -0,0 +1,69 @@ +installEntitySchema('entity_test'); + } + + /** + * @covers ::buildResponse + */ + public function testBuildResponse() { + /** @var \Drupal\views\Entity\View $view */ + $view = View::load('test_serializer_display_entity'); + $display = &$view->getDisplay('rest_export_1'); + + $display['display_options']['defaults']['style'] = FALSE; + $display['display_options']['style']['type'] = 'serializer'; + $display['display_options']['style']['options']['formats'] = ['json', 'xml']; + $view->save(); + + // No custom header should be set yet. + $response = RestExport::buildResponse('test_serializer_display_entity', 'rest_export_1', []); + $this->assertFalse($response->headers->get('Custom-Header')); + + // Clear render cache. + /** @var \Drupal\Core\Cache\MemoryBackend $render_cache */ + $render_cache = $this->container->get('cache_factory')->get('render'); + $render_cache->deleteAll(); + + // A custom header should now be added. + // @see rest_test_views_views_post_execute() + $header = $this->randomString(); + $this->container->get('state')->set('rest_test_views_set_header', $header); + $response = RestExport::buildResponse('test_serializer_display_entity', 'rest_export_1', []); + $this->assertEquals($header, $response->headers->get('Custom-Header')); + } + +} diff --git a/core/modules/search/src/Form/SearchBlockForm.php b/core/modules/search/src/Form/SearchBlockForm.php index cc9d89e..cd198fd 100644 --- a/core/modules/search/src/Form/SearchBlockForm.php +++ b/core/modules/search/src/Form/SearchBlockForm.php @@ -42,7 +42,7 @@ class SearchBlockForm extends FormBase { * The search page repository. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory. - * @param \Drupal\Core\Render\RendererInterface + * @param \Drupal\Core\Render\RendererInterface $renderer * The renderer. */ public function __construct(SearchPageRepositoryInterface $search_page_repository, ConfigFactoryInterface $config_factory, RendererInterface $renderer) { diff --git a/core/modules/serialization/src/Normalizer/NormalizerBase.php b/core/modules/serialization/src/Normalizer/NormalizerBase.php index 934093e..90d9b75 100644 --- a/core/modules/serialization/src/Normalizer/NormalizerBase.php +++ b/core/modules/serialization/src/Normalizer/NormalizerBase.php @@ -72,6 +72,6 @@ protected function checkFormat($format = NULL) { } return in_array($format, (array) $this->format); - } + } } diff --git a/core/modules/shortcut/src/Form/ShortcutSetDeleteForm.php b/core/modules/shortcut/src/Form/ShortcutSetDeleteForm.php index 1a3cbf2..3c732d6 100644 --- a/core/modules/shortcut/src/Form/ShortcutSetDeleteForm.php +++ b/core/modules/shortcut/src/Form/ShortcutSetDeleteForm.php @@ -70,6 +70,6 @@ public function buildForm(array $form, FormStateInterface $form_state) { ); return parent::buildForm($form, $form_state); - } + } } diff --git a/core/modules/shortcut/src/ShortcutSetStorageInterface.php b/core/modules/shortcut/src/ShortcutSetStorageInterface.php index a367800..47036a6 100644 --- a/core/modules/shortcut/src/ShortcutSetStorageInterface.php +++ b/core/modules/shortcut/src/ShortcutSetStorageInterface.php @@ -46,7 +46,7 @@ public function deleteAssignedShortcutSets(ShortcutSetInterface $entity); /** * Get the name of the set assigned to this user. * - * @param \Drupal\user\Entity\User + * @param \Drupal\user\Entity\User $account * The user account. * * @return string diff --git a/core/modules/simpletest/simpletest.api.php b/core/modules/simpletest/simpletest.api.php index e5251b7..d1b5f44 100644 --- a/core/modules/simpletest/simpletest.api.php +++ b/core/modules/simpletest/simpletest.api.php @@ -50,7 +50,7 @@ function hook_test_group_finished() { * $results The results of the test as gathered by * \Drupal\simpletest\WebTestBase. * - * @see \Drupal\simpletest\WebTestBase->results() + * @see \Drupal\simpletest\WebTestBase::results() */ function hook_test_finished($results) { } diff --git a/core/modules/simpletest/simpletest.install b/core/modules/simpletest/simpletest.install index c1422e6..0e3cd00 100644 --- a/core/modules/simpletest/simpletest.install +++ b/core/modules/simpletest/simpletest.install @@ -18,9 +18,19 @@ function simpletest_requirements($phase) { $requirements = array(); + $has_phpunit = class_exists('\PHPUnit_Framework_TestCase'); $has_curl = function_exists('curl_init'); $open_basedir = ini_get('open_basedir'); + $requirements['phpunit'] = array( + 'title' => t('PHPUnit dependency'), + 'value' => $has_phpunit ? t('Found') : t('Not found'), + ); + if (!$has_phpunit) { + $requirements['phpunit']['severity'] = REQUIREMENT_ERROR; + $requirements['phpunit']['description'] = t("The testing framework requires the PHPUnit package. Please run 'composer install --dev' to ensure it is present."); + } + $requirements['curl'] = array( 'title' => t('cURL'), 'value' => $has_curl ? t('Enabled') : t('Not found'), diff --git a/core/modules/simpletest/src/AssertContentTrait.php b/core/modules/simpletest/src/AssertContentTrait.php index e428e71..e19f319 100644 --- a/core/modules/simpletest/src/AssertContentTrait.php +++ b/core/modules/simpletest/src/AssertContentTrait.php @@ -117,7 +117,7 @@ protected function setDrupalSettings($settings) { /** * Parse content returned from curlExec using DOM and SimpleXML. * - * @return \SimpleXMLElement|FALSE + * @return \SimpleXMLElement|false * A SimpleXMLElement or FALSE on failure. */ protected function parse() { diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php index 4258f80..944810a 100644 --- a/core/modules/simpletest/src/TestBase.php +++ b/core/modules/simpletest/src/TestBase.php @@ -527,7 +527,7 @@ protected function getAssertionCall() { // The first element is the call. The second element is the caller. // We skip calls that occurred in one of the methods of our base classes // or in an assertion function. - while (($caller = $backtrace[1]) && + while (($caller = $backtrace[1]) && ((isset($caller['class']) && isset($this->skipClasses[$caller['class']])) || substr($caller['function'], 0, 6) == 'assert')) { // We remove that call. diff --git a/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php b/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php index 5d1854b..91e37d3 100644 --- a/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php +++ b/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php @@ -131,7 +131,7 @@ public function testTestingThroughUI() { // A PHPUnit unit test. 'Drupal\Tests\action\Unit\Menu\ActionLocalTasksTest', // A PHPUnit functional test. - 'Drupal\Tests\simpletest\Functional\BrowserTestBaseTest', + 'Drupal\FunctionalTests\BrowserTestBaseTest', ); foreach ($tests as $test) { diff --git a/core/modules/simpletest/tests/src/Unit/TestInfoParsingTest.php b/core/modules/simpletest/tests/src/Unit/TestInfoParsingTest.php index 9a69d78..df98377 100644 --- a/core/modules/simpletest/tests/src/Unit/TestInfoParsingTest.php +++ b/core/modules/simpletest/tests/src/Unit/TestInfoParsingTest.php @@ -5,7 +5,7 @@ * Contains \Drupal\Tests\simpletest\Unit\TestInfoParsingTest. */ -namespace Drupal\Tests\simpletest\Unit { +namespace Drupal\Tests\simpletest\Unit; use Composer\Autoload\ClassLoader; use Drupal\Core\Extension\Extension; @@ -60,13 +60,13 @@ public function infoParserProvider() { $tests[] = [ // Expected result. [ - 'name' => 'Drupal\Tests\simpletest\Functional\BrowserTestBaseTest', - 'group' => 'simpletest', + 'name' => 'Drupal\FunctionalTests\BrowserTestBaseTest', + 'group' => 'browsertestbase', 'description' => 'Tests BrowserTestBase functionality.', 'type' => 'PHPUnit-Functional', ], // Classname. - 'Drupal\Tests\simpletest\Functional\BrowserTestBaseTest', + 'Drupal\FunctionalTests\BrowserTestBaseTest', ]; // kernel PHPUnit test. @@ -400,7 +400,7 @@ public function providerTestGetPhpunitTestSuite() { $data['simpletest-kerneltest'] = ['\Drupal\hal\Tests\FileNormalizeTest', FALSE]; $data['module-unittest'] = [static::class, 'Unit']; $data['module-kerneltest'] = ['\Drupal\KernelTests\Core\Theme\TwigMarkupInterfaceTest', 'Kernel']; - $data['module-functionaltest'] = ['\Drupal\Tests\simpletest\Functional\BrowserTestBaseTest', 'Functional']; + $data['module-functionaltest'] = ['\Drupal\FunctionalTests\BrowserTestBaseTest', 'Functional']; $data['module-functionaljavascripttest'] = ['\Drupal\Tests\toolbar\FunctionalJavascript\ToolbarIntegrationTest', 'FunctionalJavascript']; $data['core-unittest'] = ['\Drupal\Tests\ComposerIntegrationTest', 'Unit']; $data['core-unittest2'] = ['Drupal\Tests\Core\DrupalTest', 'Unit']; @@ -413,9 +413,7 @@ public function providerTestGetPhpunitTestSuite() { } -} - -namespace Drupal\simpletest\Tests { +namespace Drupal\simpletest\Tests; use Drupal\simpletest\WebTestBase; @@ -426,5 +424,3 @@ public function providerTestGetPhpunitTestSuite() { */ class ExampleSimpleTest extends WebTestBase { } - -} diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module index 5079e43..68d88f5 100644 --- a/core/modules/statistics/statistics.module +++ b/core/modules/statistics/statistics.module @@ -95,7 +95,7 @@ function statistics_cron() { * @param int $dbrows * The number of rows to be returned. * - * @return SelectQuery|FALSE + * @return SelectQuery|false * A query result containing the node ID, title, user ID that owns the node, * and the username for the selected node(s), or FALSE if the query could not * be executed correctly. diff --git a/core/modules/system/src/Controller/SystemController.php b/core/modules/system/src/Controller/SystemController.php index 062a11b..41ed824 100644 --- a/core/modules/system/src/Controller/SystemController.php +++ b/core/modules/system/src/Controller/SystemController.php @@ -74,7 +74,7 @@ class SystemController extends ControllerBase { * The form builder. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler * The theme handler. - * @param \Drupal\Core\Menu\MenuLinkTreeInterface + * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_link_tree * The menu link tree service. */ public function __construct(SystemManager $systemManager, QueryFactory $queryFactory, ThemeAccessCheck $theme_access, FormBuilderInterface $form_builder, ThemeHandlerInterface $theme_handler, MenuLinkTreeInterface $menu_link_tree) { diff --git a/core/modules/system/src/Plugin/views/field/BulkForm.php b/core/modules/system/src/Plugin/views/field/BulkForm.php index f0f83a4..f9f581b 100644 --- a/core/modules/system/src/Plugin/views/field/BulkForm.php +++ b/core/modules/system/src/Plugin/views/field/BulkForm.php @@ -380,7 +380,6 @@ public function viewsFormSubmit(&$form, FormStateInterface $form_state) { else { // Don't display the message unless there are some elements affected and // there is no confirmation form. - $count = count(array_filter($form_state->getValue($this->options['id']))); if ($count) { drupal_set_message($this->formatPlural($count, '%action was applied to @count item.', '%action was applied to @count items.', array( '%action' => $action->label(), diff --git a/core/modules/system/src/Tests/Ajax/AjaxFormPageCacheTest.php b/core/modules/system/src/Tests/Ajax/AjaxFormPageCacheTest.php index 04a7e98..a537b73 100644 --- a/core/modules/system/src/Tests/Ajax/AjaxFormPageCacheTest.php +++ b/core/modules/system/src/Tests/Ajax/AjaxFormPageCacheTest.php @@ -33,61 +33,61 @@ protected function getFormBuildId() { * Create a simple form, then submit the form via AJAX to change to it. */ public function testSimpleAJAXFormValue() { - $this->drupalGet('ajax_forms_test_get_form'); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.'); - $build_id_initial = $this->getFormBuildId(); + $this->drupalGet('ajax_forms_test_get_form'); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.'); + $build_id_initial = $this->getFormBuildId(); - $edit = ['select' => 'green']; - $commands = $this->drupalPostAjaxForm(NULL, $edit, 'select'); - $build_id_first_ajax = $this->getFormBuildId(); - $this->assertNotEqual($build_id_initial, $build_id_first_ajax, 'Build id is changed in the simpletest-DOM on first AJAX submission'); - $expected = [ - 'command' => 'update_build_id', - 'old' => $build_id_initial, - 'new' => $build_id_first_ajax, - ]; - $this->assertCommand($commands, $expected, 'Build id change command issued on first AJAX submission'); + $edit = ['select' => 'green']; + $commands = $this->drupalPostAjaxForm(NULL, $edit, 'select'); + $build_id_first_ajax = $this->getFormBuildId(); + $this->assertNotEqual($build_id_initial, $build_id_first_ajax, 'Build id is changed in the simpletest-DOM on first AJAX submission'); + $expected = [ + 'command' => 'update_build_id', + 'old' => $build_id_initial, + 'new' => $build_id_first_ajax, + ]; + $this->assertCommand($commands, $expected, 'Build id change command issued on first AJAX submission'); - $edit = ['select' => 'red']; - $commands = $this->drupalPostAjaxForm(NULL, $edit, 'select'); - $build_id_second_ajax = $this->getFormBuildId(); - $this->assertNotEqual($build_id_first_ajax, $build_id_second_ajax, 'Build id changes on subsequent AJAX submissions'); - $expected = [ - 'command' => 'update_build_id', - 'old' => $build_id_first_ajax, - 'new' => $build_id_second_ajax, - ]; - $this->assertCommand($commands, $expected, 'Build id change command issued on subsequent AJAX submissions'); + $edit = ['select' => 'red']; + $commands = $this->drupalPostAjaxForm(NULL, $edit, 'select'); + $build_id_second_ajax = $this->getFormBuildId(); + $this->assertNotEqual($build_id_first_ajax, $build_id_second_ajax, 'Build id changes on subsequent AJAX submissions'); + $expected = [ + 'command' => 'update_build_id', + 'old' => $build_id_first_ajax, + 'new' => $build_id_second_ajax, + ]; + $this->assertCommand($commands, $expected, 'Build id change command issued on subsequent AJAX submissions'); - // Repeat the test sequence but this time with a page loaded from the cache. - $this->drupalGet('ajax_forms_test_get_form'); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.'); - $build_id_from_cache_initial = $this->getFormBuildId(); - $this->assertEqual($build_id_initial, $build_id_from_cache_initial, 'Build id is the same as on the first request'); + // Repeat the test sequence but this time with a page loaded from the cache. + $this->drupalGet('ajax_forms_test_get_form'); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.'); + $build_id_from_cache_initial = $this->getFormBuildId(); + $this->assertEqual($build_id_initial, $build_id_from_cache_initial, 'Build id is the same as on the first request'); - $edit = ['select' => 'green']; - $commands = $this->drupalPostAjaxForm(NULL, $edit, 'select'); - $build_id_from_cache_first_ajax = $this->getFormBuildId(); - $this->assertNotEqual($build_id_from_cache_initial, $build_id_from_cache_first_ajax, 'Build id is changed in the simpletest-DOM on first AJAX submission'); - $this->assertNotEqual($build_id_first_ajax, $build_id_from_cache_first_ajax, 'Build id from first user is not reused'); - $expected = [ - 'command' => 'update_build_id', - 'old' => $build_id_from_cache_initial, - 'new' => $build_id_from_cache_first_ajax, - ]; - $this->assertCommand($commands, $expected, 'Build id change command issued on first AJAX submission'); + $edit = ['select' => 'green']; + $commands = $this->drupalPostAjaxForm(NULL, $edit, 'select'); + $build_id_from_cache_first_ajax = $this->getFormBuildId(); + $this->assertNotEqual($build_id_from_cache_initial, $build_id_from_cache_first_ajax, 'Build id is changed in the simpletest-DOM on first AJAX submission'); + $this->assertNotEqual($build_id_first_ajax, $build_id_from_cache_first_ajax, 'Build id from first user is not reused'); + $expected = [ + 'command' => 'update_build_id', + 'old' => $build_id_from_cache_initial, + 'new' => $build_id_from_cache_first_ajax, + ]; + $this->assertCommand($commands, $expected, 'Build id change command issued on first AJAX submission'); - $edit = ['select' => 'red']; - $commands = $this->drupalPostAjaxForm(NULL, $edit, 'select'); - $build_id_from_cache_second_ajax = $this->getFormBuildId(); - $this->assertNotEqual($build_id_from_cache_first_ajax, $build_id_from_cache_second_ajax, 'Build id changes on subsequent AJAX submissions'); - $expected = [ - 'command' => 'update_build_id', - 'old' => $build_id_from_cache_first_ajax, - 'new' => $build_id_from_cache_second_ajax, - ]; - $this->assertCommand($commands, $expected, 'Build id change command issued on subsequent AJAX submissions'); - } + $edit = ['select' => 'red']; + $commands = $this->drupalPostAjaxForm(NULL, $edit, 'select'); + $build_id_from_cache_second_ajax = $this->getFormBuildId(); + $this->assertNotEqual($build_id_from_cache_first_ajax, $build_id_from_cache_second_ajax, 'Build id changes on subsequent AJAX submissions'); + $expected = [ + 'command' => 'update_build_id', + 'old' => $build_id_from_cache_first_ajax, + 'new' => $build_id_from_cache_second_ajax, + ]; + $this->assertCommand($commands, $expected, 'Build id change command issued on subsequent AJAX submissions'); + } /** * Tests a form that uses an #ajax callback. diff --git a/core/modules/system/src/Tests/Form/ElementsVerticalTabsTest.php b/core/modules/system/src/Tests/Form/ElementsVerticalTabsTest.php index 3de1867..2e630cc 100644 --- a/core/modules/system/src/Tests/Form/ElementsVerticalTabsTest.php +++ b/core/modules/system/src/Tests/Form/ElementsVerticalTabsTest.php @@ -70,9 +70,9 @@ function testWrapperNotShownWhenEmpty() { $this->assertFalse(isset($wrapper[0]), 'Vertical tab wrappers are not displayed to unprivileged users.'); } - /** - * Ensures that default vertical tab is correctly selected. - */ + /** + * Ensures that default vertical tab is correctly selected. + */ function testDefaultTab() { $this->drupalGet('form_test/vertical-tabs'); $this->assertFieldByName('vertical_tabs__active_tab', 'edit-tab3', t('The default vertical tab is correctly selected.')); diff --git a/core/modules/system/src/Tests/Module/ClassLoaderTest.php b/core/modules/system/src/Tests/Module/ClassLoaderTest.php index 5d1db03..1e7a0a5 100644 --- a/core/modules/system/src/Tests/Module/ClassLoaderTest.php +++ b/core/modules/system/src/Tests/Module/ClassLoaderTest.php @@ -28,22 +28,44 @@ function testClassLoading() { // Check twice to test an unprimed and primed system_list() cache. for ($i = 0; $i < 2; $i++) { $this->drupalGet('module-test/class-loading'); + $this->assertResponse(200); $this->assertText($this->expected, 'Autoloader loads classes from an enabled module.'); } } /** + * Tests that module-provided classes can't be loaded if module not installed. + * + * @see \Drupal\module_autoload_test\SomeClass + */ + function testClassLoadingNotInstalledModules() { + // Enable the module_test module. + \Drupal::service('module_installer')->install(array('module_test'), FALSE); + $this->resetAll(); + // Check twice to test an unprimed and primed system_list() cache. + for ($i = 0; $i < 2; $i++) { + $this->drupalGet('module-test/class-loading'); + $this->assertResponse(200); + $this->assertNoText($this->expected, 'Autoloader does not load classes from a disabled module.'); + } + } + + /** * Tests that module-provided classes can't be loaded from disabled modules. * * @see \Drupal\module_autoload_test\SomeClass */ function testClassLoadingDisabledModules() { + // Enable the module_test and module_autoload_test modules. + \Drupal::service('module_installer')->install(array('module_test', 'module_autoload_test'), FALSE); + $this->resetAll(); // Ensure that module_autoload_test is disabled. $this->container->get('module_installer')->uninstall(array('module_autoload_test'), FALSE); $this->resetAll(); // Check twice to test an unprimed and primed system_list() cache. for ($i = 0; $i < 2; $i++) { $this->drupalGet('module-test/class-loading'); + $this->assertResponse(200); $this->assertNoText($this->expected, 'Autoloader does not load classes from a disabled module.'); } } diff --git a/core/modules/system/src/Tests/Theme/EngineTwigTest.php b/core/modules/system/src/Tests/Theme/EngineTwigTest.php index a6b7acf..ae08fc8 100644 --- a/core/modules/system/src/Tests/Theme/EngineTwigTest.php +++ b/core/modules/system/src/Tests/Theme/EngineTwigTest.php @@ -72,7 +72,7 @@ public function testTwigUrlGenerator() { public function testTwigLinkGenerator() { $this->drupalGet('twig-theme-test/link-generator'); - /** @var \Drupal\Core\Utility\LinkGenerator $link_generator */ + /** @var \Drupal\Core\Utility\LinkGenerator $link_generator */ $link_generator = $this->container->get('link_generator'); $expected = [ diff --git a/core/modules/system/src/Tests/Theme/TwigTransTest.php b/core/modules/system/src/Tests/Theme/TwigTransTest.php index 6473d6e..53119b9 100644 --- a/core/modules/system/src/Tests/Theme/TwigTransTest.php +++ b/core/modules/system/src/Tests/Theme/TwigTransTest.php @@ -229,7 +229,7 @@ protected function installLanguages() { * @param string $langcode * The langcode of the specified language. * - * @return string|FALSE + * @return string|false * The .po contents for the specified language or FALSE if none exists. */ protected function poFileContents($langcode) { diff --git a/core/modules/system/src/Tests/Update/ConfigOverridesUpdateTest.php b/core/modules/system/src/Tests/Update/ConfigOverridesUpdateTest.php new file mode 100644 index 0000000..6efa318 --- /dev/null +++ b/core/modules/system/src/Tests/Update/ConfigOverridesUpdateTest.php @@ -0,0 +1,56 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../tests/fixtures/update/drupal-8.filled.standard.php.gz', + __DIR__ . '/../../../tests/fixtures/update/drupal-8.config-override-fix.php', + ]; + } + + /** + * Tests that configuration has been updated. + */ + public function testUpdatedSite() { + $key_to_be_removed = 'display.default.display_options.fields.nid'; + /** @var \Drupal\Core\Config\Config $config_override */ + $language_config_override = \Drupal::service('language.config_factory_override'); + $config_override = $language_config_override->getOverride('es', 'views.view.content'); + $this->assertEqual('Spanish ID', $config_override->get($key_to_be_removed)['label'], 'The spanish override for the missing field exists before updating.'); + // Since the above view will be fixed by other updates that fix views + // configuration for example, + // views_post_update_update_cacheability_metadata(), also test configuration + // that has yet to be modified in an update path. + $config_override = $language_config_override->getOverride('es', 'system.cron'); + $this->assertEqual('Should be cleaned by system_update_8200', $config_override->get('bogus_key'), 'The spanish override in system.cron exists before updating.'); + + $this->runUpdates(); + + /** @var \Drupal\Core\Config\Config $config_override */ + $config_override = \Drupal::service('language.config_factory_override')->getOverride('es', 'views.view.content'); + $this->assertNull($config_override->get($key_to_be_removed), 'The spanish override for the missing field has been removed.'); + $config_override = $language_config_override->getOverride('es', 'system.cron'); + $this->assertTrue($config_override->isNew(), 'After updating the system.cron spanish override does not exist.'); + $this->assertTrue(empty($config_override->get()), 'After updating the system.cron spanish override has no data.'); + + // Test that the spanish overrides still work. + $this->drupalLogin($this->createUser(['access content overview'])); + $this->drupalGet('admin/content', ['language' => \Drupal::languageManager()->getLanguage('es')]); + $this->assertText('Spanish Title'); + $this->assertText('Spanish Author'); + } + +} diff --git a/core/modules/system/src/Tests/Update/UpdatePathTestBase.php b/core/modules/system/src/Tests/Update/UpdatePathTestBase.php index 82169ad..2596cdf 100644 --- a/core/modules/system/src/Tests/Update/UpdatePathTestBase.php +++ b/core/modules/system/src/Tests/Update/UpdatePathTestBase.php @@ -45,7 +45,7 @@ */ protected static $modules = []; - /** + /** * The file path(s) to the dumped database(s) to load into the child site. * * The file system/tests/fixtures/update/drupal-8.bare.standard.php.gz is diff --git a/core/modules/system/src/Tests/Update/UpdateScriptTest.php b/core/modules/system/src/Tests/Update/UpdateScriptTest.php index d788636..b80ef82 100644 --- a/core/modules/system/src/Tests/Update/UpdateScriptTest.php +++ b/core/modules/system/src/Tests/Update/UpdateScriptTest.php @@ -232,9 +232,9 @@ function testMaintenanceModeUpdateFunctionality() { $this->assertEqual($final_maintenance_mode, $initial_maintenance_mode, 'Maintenance mode should not have changed after database updates.'); } - /** - * Tests perfoming updates with update.php in a multilingual environment. - */ + /** + * Tests perfoming updates with update.php in a multilingual environment. + */ function testSuccessfulMultilingualUpdateFunctionality() { // Add some custom languages. foreach (array('aa', 'bb') as $language_code) { @@ -242,7 +242,7 @@ function testSuccessfulMultilingualUpdateFunctionality() { 'id' => $language_code, 'label' => $this->randomMachineName(), ))->save(); - } + } $config = \Drupal::service('config.factory')->getEditable('language.negotiation'); // Ensure path prefix is used to determine the language. diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 7f8bc24..154a55b 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -1219,10 +1219,10 @@ function system_update_8005() { case 'seven': $name = 'block.block.seven_local_actions'; - $values = [ - 'id' => 'seven_local_actions', - 'weight' => -10, - ] + $local_actions_default_settings; + $values = [ + 'id' => 'seven_local_actions', + 'weight' => -10, + ] + $local_actions_default_settings; _system_update_create_block($name, $theme_name, $values); $name = 'block.block.seven_primary_local_tasks'; @@ -1657,3 +1657,26 @@ function system_update_8014() { /** * @} End of "addtogroup updates-8.0.0-rc". */ + +/** + * Fix configuration overrides to not override non existing keys. + */ +function system_update_8200(&$sandbox) { + $config_factory = \Drupal::configFactory(); + if (!array_key_exists('config_names', $sandbox)) { + $sandbox['config_names'] = $config_factory->listAll(); + $sandbox['max'] = count($sandbox['config_names']); + } + + // Get a list of 50 to work on at a time. + $config_names_to_process = array_slice($sandbox['config_names'], 0, 50); + // Preload in a single query. + $config_factory->loadMultiple($config_names_to_process); + foreach ($config_names_to_process as $config_name) { + $config_factory->getEditable($config_name)->save(); + } + + // Update the list of names to process. + $sandbox['config_names'] = array_diff($sandbox['config_names'], $config_names_to_process); + $sandbox['#finished'] = empty($sandbox['config_names']) ? 1 : ($sandbox['max'] - count($sandbox['config_names'])) / $sandbox['max']; +} diff --git a/core/modules/system/tests/fixtures/update/drupal-8.config-override-fix.php b/core/modules/system/tests/fixtures/update/drupal-8.config-override-fix.php new file mode 100644 index 0000000..f631bf9 --- /dev/null +++ b/core/modules/system/tests/fixtures/update/drupal-8.config-override-fix.php @@ -0,0 +1,37 @@ +delete('config') + ->condition('name', $config_name) + ->condition('collection', 'language.es') + ->execute(); + $connection->insert('config') + ->fields(['data', 'name', 'collection']) + ->values([ + 'name' => $config_name, + 'data' => serialize($config), + 'collection' => 'language.es', + ]) + ->execute(); +} diff --git a/core/modules/system/tests/fixtures/update/drupal-8.views-entity-views-data-2455125.php b/core/modules/system/tests/fixtures/update/drupal-8.views-entity-views-data-2455125.php index 6ff4d81..2fcda10 100644 --- a/core/modules/system/tests/fixtures/update/drupal-8.views-entity-views-data-2455125.php +++ b/core/modules/system/tests/fixtures/update/drupal-8.views-entity-views-data-2455125.php @@ -17,16 +17,16 @@ $views_configs[] = Yaml::decode(file_get_contents(__DIR__ . '/drupal-8.views-entity-views-data-2455125.yml')); foreach ($views_configs as $views_config) { -$connection->insert('config') - ->fields(array( + $connection->insert('config') + ->fields(array( 'collection', 'name', 'data', )) - ->values(array( + ->values(array( 'collection' => '', 'name' => 'views.view.' . $views_config['id'], 'data' => serialize($views_config), )) - ->execute(); + ->execute(); } diff --git a/core/modules/system/tests/fixtures/update/es-system.cron.yml b/core/modules/system/tests/fixtures/update/es-system.cron.yml new file mode 100644 index 0000000..09d0f82 --- /dev/null +++ b/core/modules/system/tests/fixtures/update/es-system.cron.yml @@ -0,0 +1 @@ +bogus_key: 'Should be cleaned by system_update_8200' diff --git a/core/modules/system/tests/fixtures/update/es-views.view.content.yml b/core/modules/system/tests/fixtures/update/es-views.view.content.yml new file mode 100644 index 0000000..d9c9cce --- /dev/null +++ b/core/modules/system/tests/fixtures/update/es-views.view.content.yml @@ -0,0 +1,15 @@ +label: 'Spanish Content' +description: 'Spanish Find and manage content.' +display: + default: + display_options: + fields: + title: + label: 'Spanish Title' + name: + label: 'Spanish Author' + nid: + label: 'Spanish ID' + display_title: 'Spanish Master' + page_1: + display_title: 'Spanish Page' diff --git a/core/modules/system/tests/modules/common_test/src/Render/MainContent/JsonRenderer.php b/core/modules/system/tests/modules/common_test/src/Render/MainContent/JsonRenderer.php index babf031..bcecfbc 100644 --- a/core/modules/system/tests/modules/common_test/src/Render/MainContent/JsonRenderer.php +++ b/core/modules/system/tests/modules/common_test/src/Render/MainContent/JsonRenderer.php @@ -46,19 +46,19 @@ public function __construct(TitleResolverInterface $title_resolver, RendererInte * {@inheritdoc} */ public function renderResponse(array $main_content, Request $request, RouteMatchInterface $route_match) { - $json = []; + $json = []; - $json['content'] = (string) $this->renderer->renderRoot($main_content); - if (!empty($main_content['#title'])) { - $json['title'] = (string) $main_content['#title']; - } - else { - $json['title'] = (string) $this->titleResolver->getTitle($request, $route_match->getRouteObject()); - } + $json['content'] = (string) $this->renderer->renderRoot($main_content); + if (!empty($main_content['#title'])) { + $json['title'] = (string) $main_content['#title']; + } + else { + $json['title'] = (string) $this->titleResolver->getTitle($request, $route_match->getRouteObject()); + } - $response = new CacheableJsonResponse($json, 200); - $response->addCacheableDependency(CacheableMetadata::createFromRenderArray($main_content)); - return $response; + $response = new CacheableJsonResponse($json, 200); + $response->addCacheableDependency(CacheableMetadata::createFromRenderArray($main_content)); + return $response; } } diff --git a/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php b/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php index 7dcd61e..a753eab 100644 --- a/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php +++ b/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php @@ -22,7 +22,7 @@ class EntityTestController extends ControllerBase { /** * Constructs a new EntityTestController. * - * @param \Drupal\Core\Entity\Query\QueryFactory + * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query_factory * The entity query factory. */ public function __construct(QueryFactory $entity_query_factory) { diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php index b299d90..47035ea 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php @@ -67,6 +67,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setLabel(t('Non Revisionable Field')) ->setDescription(t('A non-revisionable test field.')) ->setRevisionable(FALSE) + ->setTranslatable(TRUE) ->setCardinality(1) ->setReadOnly(TRUE); diff --git a/core/modules/system/tests/modules/httpkernel_test/src/HttpKernel/TestMiddleware.php b/core/modules/system/tests/modules/httpkernel_test/src/HttpKernel/TestMiddleware.php index 47842b7..f9e9849 100644 --- a/core/modules/system/tests/modules/httpkernel_test/src/HttpKernel/TestMiddleware.php +++ b/core/modules/system/tests/modules/httpkernel_test/src/HttpKernel/TestMiddleware.php @@ -10,12 +10,12 @@ */ class TestMiddleware implements HttpKernelInterface { - /** - * The decorated kernel. - * - * @var \Symfony\Component\HttpKernel\HttpKernelInterface - */ - protected $kernel; + /** + * The decorated kernel. + * + * @var \Symfony\Component\HttpKernel\HttpKernelInterface + */ + protected $kernel; /** * An optional argument. @@ -35,21 +35,21 @@ class TestMiddleware implements HttpKernelInterface { public function __construct(HttpKernelInterface $kernel, $optional_argument = NULL) { $this->kernel = $kernel; $this->optionalArgument = $optional_argument; - } - - /** - * {@inheritdoc} - */ - public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) { - $request->attributes->set('_hello', 'world'); - if ($request->attributes->has('_optional_argument')) { - $request->attributes->set('_previous_optional_argument', $request->attributes->get('_optional_argument')); - } - elseif (isset($this->optionalArgument)) { - $request->attributes->set('_optional_argument', $this->optionalArgument); - } - - return $this->kernel->handle($request, $type, $catch); - } + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) { + $request->attributes->set('_hello', 'world'); + if ($request->attributes->has('_optional_argument')) { + $request->attributes->set('_previous_optional_argument', $request->attributes->get('_optional_argument')); + } + elseif (isset($this->optionalArgument)) { + $request->attributes->set('_optional_argument', $this->optionalArgument); + } + + return $this->kernel->handle($request, $type, $catch); + } } diff --git a/core/modules/system/tests/modules/plugin_test/src/Plugin/DefaultsTestPluginManager.php b/core/modules/system/tests/modules/plugin_test/src/Plugin/DefaultsTestPluginManager.php index 457dbe5..eda8f88 100644 --- a/core/modules/system/tests/modules/plugin_test/src/Plugin/DefaultsTestPluginManager.php +++ b/core/modules/system/tests/modules/plugin_test/src/Plugin/DefaultsTestPluginManager.php @@ -12,12 +12,12 @@ */ class DefaultsTestPluginManager extends DefaultPluginManager { - /** - * Constructs a new DefaultsTestPluginManager instance. - * - * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler - * The module handler. - */ + /** + * Constructs a new DefaultsTestPluginManager instance. + * + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + */ public function __construct(ModuleHandlerInterface $module_handler) { // Create the object that can be used to return definitions for all the // plugins available for this type. Most real plugin managers use a richer diff --git a/core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php b/core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php index c11770d..1aa2f66 100644 --- a/core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php +++ b/core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php @@ -99,10 +99,10 @@ public function testPrefix() { $this->assertEquals('extra2', $command->getDatabaseConnection($command_tester->getInput())->tablePrefix()); // This breaks simpletest cleanup. -// $command_tester->execute([ -// '--prefix' => 'notsimpletest', -// ]); -// $this->assertEquals('notsimpletest', $command->getDatabaseConnection($command_tester->getInput())->tablePrefix()); + // $command_tester->execute([ + // '--prefix' => 'notsimpletest', + // ]); + // $this->assertEquals('notsimpletest', $command->getDatabaseConnection($command_tester->getInput())->tablePrefix()); } } diff --git a/core/modules/taxonomy/src/TermAccessControlHandler.php b/core/modules/taxonomy/src/TermAccessControlHandler.php index 7a6a991..04c2c4f 100644 --- a/core/modules/taxonomy/src/TermAccessControlHandler.php +++ b/core/modules/taxonomy/src/TermAccessControlHandler.php @@ -21,15 +21,12 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter switch ($operation) { case 'view': return AccessResult::allowedIfHasPermission($account, 'access content'); - break; case 'update': return AccessResult::allowedIfHasPermissions($account, ["edit terms in {$entity->bundle()}", 'administer taxonomy'], 'OR'); - break; case 'delete': return AccessResult::allowedIfHasPermissions($account, ["delete terms in {$entity->bundle()}", 'administer taxonomy'], 'OR'); - break; default: // No opinion. diff --git a/core/modules/taxonomy/tests/src/Kernel/ForwardRevisionTest.php b/core/modules/taxonomy/tests/src/Kernel/ForwardRevisionTest.php index 6f751b1..abc0846 100644 --- a/core/modules/taxonomy/tests/src/Kernel/ForwardRevisionTest.php +++ b/core/modules/taxonomy/tests/src/Kernel/ForwardRevisionTest.php @@ -113,9 +113,9 @@ public function testTaxonomyIndexWithForwardRevision() { protected function getTaxonomyIndex() { return \Drupal::database()->select('taxonomy_index') - ->fields('taxonomy_index') - ->execute() - ->fetchAllAssoc('nid'); + ->fields('taxonomy_index') + ->execute() + ->fetchAllAssoc('nid'); } } diff --git a/core/modules/text/src/Plugin/migrate/cckfield/TextField.php b/core/modules/text/src/Plugin/migrate/cckfield/TextField.php index cc4c1ec..89475ff 100644 --- a/core/modules/text/src/Plugin/migrate/cckfield/TextField.php +++ b/core/modules/text/src/Plugin/migrate/cckfield/TextField.php @@ -120,7 +120,6 @@ public function getFieldType(Row $row) { return 'text_long'; default: return parent::getFieldType($row); - break; } } } diff --git a/core/modules/toolbar/tests/modules/toolbar_test/toolbar_test.module b/core/modules/toolbar/tests/modules/toolbar_test/toolbar_test.module index 78c2d09..4a82ca7 100644 --- a/core/modules/toolbar/tests/modules/toolbar_test/toolbar_test.module +++ b/core/modules/toolbar/tests/modules/toolbar_test/toolbar_test.module @@ -12,7 +12,7 @@ */ function toolbar_test_toolbar() { - $items['testing'] = array( + $items['testing'] = array( '#type' => 'toolbar_item', 'tab' => array( '#type' => 'link', diff --git a/core/modules/tour/src/Tests/TourCacheTagsTest.php b/core/modules/tour/src/Tests/TourCacheTagsTest.php index 4e0972d..4f2164e 100644 --- a/core/modules/tour/src/Tests/TourCacheTagsTest.php +++ b/core/modules/tour/src/Tests/TourCacheTagsTest.php @@ -29,7 +29,7 @@ protected function setUp() { // Give anonymous users permission to view nodes, so that we can verify the // cache tags of cached versions of node pages. Role::load(RoleInterface::ANONYMOUS_ID)->grantPermission('access tour') - ->save(); + ->save(); } /** diff --git a/core/modules/tracker/tests/src/Kernel/Migrate/d7/MigrateTrackerNodeTest.php b/core/modules/tracker/tests/src/Kernel/Migrate/d7/MigrateTrackerNodeTest.php index 1b6a4ed..d72f13d 100644 --- a/core/modules/tracker/tests/src/Kernel/Migrate/d7/MigrateTrackerNodeTest.php +++ b/core/modules/tracker/tests/src/Kernel/Migrate/d7/MigrateTrackerNodeTest.php @@ -36,7 +36,7 @@ protected function setUp() { 'd7_user_role', 'd7_user', 'd7_node_type', - 'd7_node:test_content_type', + 'd7_node', 'd7_tracker_node', ]); } diff --git a/core/modules/tracker/tests/src/Kernel/Migrate/d7/MigrateTrackerUserTest.php b/core/modules/tracker/tests/src/Kernel/Migrate/d7/MigrateTrackerUserTest.php index 8e679c4..e3ed88e 100644 --- a/core/modules/tracker/tests/src/Kernel/Migrate/d7/MigrateTrackerUserTest.php +++ b/core/modules/tracker/tests/src/Kernel/Migrate/d7/MigrateTrackerUserTest.php @@ -36,7 +36,7 @@ protected function setUp() { 'd7_user_role', 'd7_user', 'd7_node_type', - 'd7_node:test_content_type', + 'd7_node', 'd7_tracker_node', ]); } diff --git a/core/modules/update/src/Form/UpdateReady.php b/core/modules/update/src/Form/UpdateReady.php index ba68614..c8f64a3 100644 --- a/core/modules/update/src/Form/UpdateReady.php +++ b/core/modules/update/src/Form/UpdateReady.php @@ -42,7 +42,7 @@ class UpdateReady extends FormBase { * * @var string */ - protected $sitePath; + protected $sitePath; /** * Constructs a new UpdateReady object. diff --git a/core/modules/update/src/UpdateSettingsForm.php b/core/modules/update/src/UpdateSettingsForm.php index 1565f66..d830de5 100644 --- a/core/modules/update/src/UpdateSettingsForm.php +++ b/core/modules/update/src/UpdateSettingsForm.php @@ -137,7 +137,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) { */ public function submitForm(array &$form, FormStateInterface $form_state) { $config = $this->config('update.settings'); - // See if the update_check_disabled setting is being changed, and if so, + // See if the update_check_disabled setting is being changed, and if so, // invalidate all update status data. if ($form_state->getValue('update_check_disabled') != $config->get('check.disabled_extensions')) { update_storage_clear(); diff --git a/core/modules/update/update.api.php b/core/modules/update/update.api.php index 362c1a2..37ef66a 100644 --- a/core/modules/update/update.api.php +++ b/core/modules/update/update.api.php @@ -32,7 +32,7 @@ * examples of how to populate the array with real values. * * @see \Drupal\Update\UpdateManager::getProjects() - * @see \Drupal\Core\Utility\ProjectInfo->processInfoList() + * @see \Drupal\Core\Utility\ProjectInfo::processInfoList() */ function hook_update_projects_alter(&$projects) { // Hide a site-specific module from the list. diff --git a/core/modules/update/update.module b/core/modules/update/update.module index 349f694..f45b589 100644 --- a/core/modules/update/update.module +++ b/core/modules/update/update.module @@ -393,7 +393,7 @@ function update_refresh() { * @see \Drupal\update\UpdateProcessor::fetchData() */ function update_fetch_data() { - \Drupal::service('update.processor')->fetchData(); + \Drupal::service('update.processor')->fetchData(); } /** diff --git a/core/modules/user/src/Authentication/Provider/Cookie.php b/core/modules/user/src/Authentication/Provider/Cookie.php index 2f8624e..ebcdae7 100644 --- a/core/modules/user/src/Authentication/Provider/Cookie.php +++ b/core/modules/user/src/Authentication/Provider/Cookie.php @@ -62,7 +62,7 @@ public function authenticate(Request $request) { * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session * The session. * - * @return \Drupal\Core\Session\AccountInterface|NULL + * @return \Drupal\Core\Session\AccountInterface|null * The UserSession object for the current user, or NULL if this is an * anonymous session. */ diff --git a/core/modules/user/src/Controller/UserController.php b/core/modules/user/src/Controller/UserController.php index 0eb2729..779cf90 100644 --- a/core/modules/user/src/Controller/UserController.php +++ b/core/modules/user/src/Controller/UserController.php @@ -6,10 +6,13 @@ use Drupal\Component\Utility\Xss; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Datetime\DateFormatterInterface; +use Drupal\user\Form\UserPasswordResetForm; use Drupal\user\UserDataInterface; use Drupal\user\UserInterface; use Drupal\user\UserStorageInterface; +use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; /** @@ -39,6 +42,13 @@ class UserController extends ControllerBase { protected $userData; /** + * A logger instance. + * + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + + /** * Constructs a UserController object. * * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter @@ -47,11 +57,14 @@ class UserController extends ControllerBase { * The user storage. * @param \Drupal\user\UserDataInterface $user_data * The user data service. + * @param \Psr\Log\LoggerInterface $logger + * A logger instance. */ - public function __construct(DateFormatterInterface $date_formatter, UserStorageInterface $user_storage, UserDataInterface $user_data) { + public function __construct(DateFormatterInterface $date_formatter, UserStorageInterface $user_storage, UserDataInterface $user_data, LoggerInterface $logger) { $this->dateFormatter = $date_formatter; $this->userStorage = $user_storage; $this->userData = $user_data; + $this->logger = $logger; } /** @@ -61,38 +74,51 @@ public static function create(ContainerInterface $container) { return new static( $container->get('date.formatter'), $container->get('entity.manager')->getStorage('user'), - $container->get('user.data') + $container->get('user.data'), + $container->get('logger.factory')->get('user') ); } /** - * Returns the user password reset page. + * Redirects to the user password reset form. * + * In order to never disclose a reset link via a referrer header this + * controller must always return a redirect response. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request. * @param int $uid - * UID of user requesting reset. + * User ID of the user requesting reset. * @param int $timestamp * The current timestamp. * @param string $hash * Login link hash. * - * @return array|\Symfony\Component\HttpFoundation\RedirectResponse - * The form structure or a redirect response. - * - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException - * If the login link is for a blocked user or invalid user ID. + * @return \Symfony\Component\HttpFoundation\RedirectResponse + * The redirect response. */ - public function resetPass($uid, $timestamp, $hash) { + public function resetPass(Request $request, $uid, $timestamp, $hash) { $account = $this->currentUser(); - $config = $this->config('user.settings'); // When processing the one-time login link, we have to make sure that a user // isn't already logged in. if ($account->isAuthenticated()) { // The current user is already logged in. if ($account->id() == $uid) { user_logout(); + // We need to begin the redirect process again because logging out will + // destroy the session. + return $this->redirect( + 'user.reset', + [ + 'uid' => $uid, + 'timestamp' => $timestamp, + 'hash' => $hash, + ] + ); } // A different user is already logged in on the computer. else { + /** @var \Drupal\user\UserInterface $reset_link_user */ if ($reset_link_user = $this->userStorage->load($uid)) { drupal_set_message($this->t('Another user (%other_user) is already logged into the site on this computer, but you tried to use a one-time link for user %resetting_user. Please log out and try using the link again.', array('%other_user' => $account->getUsername(), '%resetting_user' => $reset_link_user->getUsername(), ':logout' => $this->url('user.logout'))), 'warning'); @@ -104,33 +130,117 @@ public function resetPass($uid, $timestamp, $hash) { return $this->redirect(''); } } - // The current user is not logged in, so check the parameters. + + $session = $request->getSession(); + $session->set('pass_reset_hash', $hash); + $session->set('pass_reset_timeout', $timestamp); + return $this->redirect( + 'user.reset.form', + ['uid' => $uid] + ); + } + + /** + * Returns the user password reset form. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request. + * @param int $uid + * User ID of the user requesting reset. + * + * @return array|\Symfony\Component\HttpFoundation\RedirectResponse + * The form structure or a redirect response. + * + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * If the pass_reset_timeout or pass_reset_hash are not available in the + * session. Or if $uid is for a blocked user or invalid user ID. + */ + public function getResetPassForm(Request $request, $uid) { + $session = $request->getSession(); + $timestamp = $session->get('pass_reset_timeout'); + $hash = $session->get('pass_reset_hash'); + // As soon as the session variables are used they are removed to prevent the + // hash and timestamp from being leaked unexpectedly. This could occur if + // the user does not click on the log in button on the form. + $session->remove('pass_reset_timeout'); + $session->remove('pass_reset_hash'); + if (!$hash || !$timestamp) { + throw new AccessDeniedHttpException(); + } + + /** @var \Drupal\user\UserInterface $user */ + $user = $this->userStorage->load($uid); + if ($user === NULL || !$user->isActive()) { + // Blocked or invalid user ID, so deny access. The parameters will be in + // the watchdog's URL for the administrator to check. + throw new AccessDeniedHttpException(); + } + // Time out, in seconds, until login URL expires. - $timeout = $config->get('password_reset_timeout'); - $current = REQUEST_TIME; + $timeout = $this->config('user.settings')->get('password_reset_timeout'); + + $expiration_date = $user->getLastLoginTime() ? $this->dateFormatter->format($timestamp + $timeout) : NULL; + return $this->formBuilder()->getForm(UserPasswordResetForm::class, $user, $expiration_date, $timestamp, $hash); + } - /* @var \Drupal\user\UserInterface $user */ + /** + * Validates user, hash, and timestamp; logs the user in if correct. + * + * @param int $uid + * User ID of the user requesting reset. + * @param int $timestamp + * The current timestamp. + * @param string $hash + * Login link hash. + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + * Returns a redirect to the user edit form if the information is correct. + * If the information is incorrect redirects to 'user.pass' route with a + * message for the user. + * + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * If $uid is for a blocked user or invalid user ID. + */ + public function resetPassLogin($uid, $timestamp, $hash) { + // The current user is not logged in, so check the parameters. + $current = REQUEST_TIME; + /** @var \Drupal\user\UserInterface $user */ $user = $this->userStorage->load($uid); // Verify that the user exists and is active. - if ($user && $user->isActive()) { - // No time out for first time login. - if ($user->getLastLoginTime() && $current - $timestamp > $timeout) { - drupal_set_message($this->t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'error'); - return $this->redirect('user.pass'); - } - elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && Crypt::hashEquals($hash, user_pass_rehash($user, $timestamp))) { - $expiration_date = $user->getLastLoginTime() ? $this->dateFormatter->format($timestamp + $timeout) : NULL; - return $this->formBuilder()->getForm('Drupal\user\Form\UserPasswordResetForm', $user, $expiration_date, $timestamp, $hash); - } - else { - drupal_set_message($this->t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'), 'error'); - return $this->redirect('user.pass'); - } + if ($user === NULL || !$user->isActive()) { + // Blocked or invalid user ID, so deny access. The parameters will be in + // the watchdog's URL for the administrator to check. + throw new AccessDeniedHttpException(); } - // Blocked or invalid user ID, so deny access. The parameters will be in the - // watchdog's URL for the administrator to check. - throw new AccessDeniedHttpException(); + + // Time out, in seconds, until login URL expires. + $timeout = $this->config('user.settings')->get('password_reset_timeout'); + // No time out for first time login. + if ($user->getLastLoginTime() && $current - $timestamp > $timeout) { + drupal_set_message($this->t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'error'); + return $this->redirect('user.pass'); + } + elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && Crypt::hashEquals($hash, user_pass_rehash($user, $timestamp))) { + user_login_finalize($user); + $this->logger->notice('User %name used one-time login link at time %timestamp.', ['%name' => $user->getDisplayName(), '%timestamp' => $timestamp]); + drupal_set_message($this->t('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.')); + // Let the user's password be changed without the current password + // check. + $token = Crypt::randomBytesBase64(55); + $_SESSION['pass_reset_' . $user->id()] = $token; + return $this->redirect( + 'entity.user.edit_form', + ['user' => $user->id()], + [ + 'query' => ['pass-reset-token' => $token], + 'absolute' => TRUE, + ] + ); + } + + drupal_set_message($this->t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'), 'error'); + return $this->redirect('user.pass'); } /** diff --git a/core/modules/user/src/Form/UserPasswordResetForm.php b/core/modules/user/src/Form/UserPasswordResetForm.php index 432941a..1372415 100644 --- a/core/modules/user/src/Form/UserPasswordResetForm.php +++ b/core/modules/user/src/Form/UserPasswordResetForm.php @@ -4,10 +4,8 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Session\AccountInterface; -use Drupal\Component\Utility\Crypt; use Drupal\Core\Form\FormBase; -use Psr\Log\LoggerInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Url; /** * Form controller for the user password forms. @@ -15,32 +13,6 @@ class UserPasswordResetForm extends FormBase { /** - * A logger instance. - * - * @var \Psr\Log\LoggerInterface - */ - protected $logger; - - /** - * Constructs a new UserPasswordResetForm. - * - * @param \Psr\Log\LoggerInterface $logger - * A logger instance. - */ - public function __construct(LoggerInterface $logger) { - $this->logger = $logger; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('logger.factory')->get('user') - ); - } - - /** * {@inheritdoc} */ public function getFormId() { @@ -75,20 +47,17 @@ public function buildForm(array $form, FormStateInterface $form_state, AccountIn $form['#title'] = $this->t('Set password'); } - $form['user'] = array( - '#type' => 'value', - '#value' => $user, - ); - $form['timestamp'] = array( - '#type' => 'value', - '#value' => $timestamp, - ); $form['help'] = array('#markup' => '

' . $this->t('This login can be used only once.') . '

'); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', '#value' => $this->t('Log in'), ); + $form['#action'] = Url::fromRoute('user.reset.login', [ + 'uid' => $user->id(), + 'timestamp' => $timestamp, + 'hash' => $hash, + ])->toString(); return $form; } @@ -96,22 +65,8 @@ public function buildForm(array $form, FormStateInterface $form_state, AccountIn * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - /** @var $user \Drupal\user\UserInterface */ - $user = $form_state->getValue('user'); - user_login_finalize($user); - $this->logger->notice('User %name used one-time login link at time %timestamp.', array('%name' => $user->getUsername(), '%timestamp' => $form_state->getValue('timestamp'))); - drupal_set_message($this->t('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.')); - // Let the user's password be changed without the current password check. - $token = Crypt::randomBytesBase64(55); - $_SESSION['pass_reset_' . $user->id()] = $token; - $form_state->setRedirect( - 'entity.user.edit_form', - array('user' => $user->id()), - array( - 'query' => array('pass-reset-token' => $token), - 'absolute' => TRUE, - ) - ); + // This form works by submitting the hash and timestamp to the user.reset + // route with a 'login' action. } } diff --git a/core/modules/user/src/Form/UserPermissionsForm.php b/core/modules/user/src/Form/UserPermissionsForm.php index 7a06219..75bbaf2 100644 --- a/core/modules/user/src/Form/UserPermissionsForm.php +++ b/core/modules/user/src/Form/UserPermissionsForm.php @@ -42,7 +42,7 @@ class UserPermissionsForm extends FormBase { * The permission handler. * @param \Drupal\user\RoleStorageInterface $role_storage * The role storage. - * @param \Drupal\Core\Extension\ModuleHandlerInterface + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. */ public function __construct(PermissionHandlerInterface $permission_handler, RoleStorageInterface $role_storage, ModuleHandlerInterface $module_handler) { diff --git a/core/modules/user/src/Plugin/migrate/process/d6/UserUpdate7002.php b/core/modules/user/src/Plugin/migrate/process/d6/UserUpdate7002.php index 2f95a75..0efae5d 100644 --- a/core/modules/user/src/Plugin/migrate/process/d6/UserUpdate7002.php +++ b/core/modules/user/src/Plugin/migrate/process/d6/UserUpdate7002.php @@ -25,11 +25,11 @@ class UserUpdate7002 extends ProcessPluginBase implements ContainerFactoryPlugin */ protected static $timezones; - /** - * Contains the system.theme configuration object. - * - * @var \Drupal\Core\Config\Config - */ + /** + * Contains the system.theme configuration object. + * + * @var \Drupal\Core\Config\Config + */ protected $dateConfig; /** diff --git a/core/modules/user/src/Plugin/views/field/Permissions.php b/core/modules/user/src/Plugin/views/field/Permissions.php index 42c78d2..bd5c11d 100644 --- a/core/modules/user/src/Plugin/views/field/Permissions.php +++ b/core/modules/user/src/Plugin/views/field/Permissions.php @@ -112,16 +112,4 @@ function render_item($count, $item) { return $item['permission']; } - /* - protected function documentSelfTokens(&$tokens) { - $tokens['[' . $this->options['id'] . '-role' . ']'] = $this->t('The name of the role.'); - $tokens['[' . $this->options['id'] . '-rid' . ']'] = $this->t('The role ID of the role.'); - } - - protected function addSelfTokens(&$tokens, $item) { - $tokens['[' . $this->options['id'] . '-role' . ']'] = $item['role']; - $tokens['[' . $this->options['id'] . '-rid' . ']'] = $item['rid']; - } - */ - } diff --git a/core/modules/user/src/Plugin/views/filter/Name.php b/core/modules/user/src/Plugin/views/filter/Name.php index 5f63cb3..e241e87 100644 --- a/core/modules/user/src/Plugin/views/filter/Name.php +++ b/core/modules/user/src/Plugin/views/filter/Name.php @@ -96,9 +96,9 @@ protected function valueSubmit($form, FormStateInterface $form_state) { // prevent array filter from removing our anonymous user. } -/** - * {@inheritdoc} - */ + /** + * {@inheritdoc} + */ public function getValueOptions() { return $this->valueOptions; } diff --git a/core/modules/user/src/Tests/UserBlocksTest.php b/core/modules/user/src/Tests/UserBlocksTest.php index 40e63d8..f66007e 100644 --- a/core/modules/user/src/Tests/UserBlocksTest.php +++ b/core/modules/user/src/Tests/UserBlocksTest.php @@ -34,9 +34,9 @@ protected function setUp() { $this->drupalLogout($this->adminUser); } - /** - * Tests that user login block is hidden from user/login. - */ + /** + * Tests that user login block is hidden from user/login. + */ function testUserLoginBlockVisibility() { // Array keyed list where key being the URL address and value being expected // visibility as boolean type. @@ -51,7 +51,7 @@ function testUserLoginBlockVisibility() { $elements = $this->xpath('//div[contains(@class,"block-user-login-block") and @role="form"]'); if ($expected_visibility) { $this->assertTrue(!empty($elements), 'User login block in path "' . $path . '" should be visible'); - } + } else { $this->assertTrue(empty($elements), 'User login block in path "' . $path . '" should not be visible'); } diff --git a/core/modules/user/src/Tests/UserPasswordResetTest.php b/core/modules/user/src/Tests/UserPasswordResetTest.php index 0fa89e5..c012e9f 100644 --- a/core/modules/user/src/Tests/UserPasswordResetTest.php +++ b/core/modules/user/src/Tests/UserPasswordResetTest.php @@ -2,6 +2,8 @@ namespace Drupal\user\Tests; +use Drupal\Component\Render\FormattableMarkup; +use Drupal\Core\Url; use Drupal\system\Tests\Cache\PageCacheTagsTestBase; use Drupal\user\Entity\User; @@ -68,6 +70,11 @@ protected function setUp() { * Tests password reset functionality. */ function testUserPasswordReset() { + // Verify that accessing the password reset form without having the session + // variables set results in an access denied message. + $this->drupalGet(Url::fromRoute('user.reset.form', ['uid' => $this->account->id()])); + $this->assertResponse(403); + // Try to reset the password for an invalid account. $this->drupalGet('user/password'); @@ -81,13 +88,16 @@ function testUserPasswordReset() { $edit['name'] = $this->account->getUsername(); $this->drupalPostForm(NULL, $edit, t('Submit')); - // Verify that the user was sent an email. + // Verify that the user was sent an email. $this->assertMail('to', $this->account->getEmail(), 'Password email sent to user.'); $subject = t('Replacement login information for @username at @site', array('@username' => $this->account->getUsername(), '@site' => $this->config('system.site')->get('name'))); $this->assertMail('subject', $subject, 'Password reset email subject is correct.'); $resetURL = $this->getResetURL(); $this->drupalGet($resetURL); + // Ensure that the current url does not contain the hash and timestamp. + $this->assertUrl(Url::fromRoute('user.reset.form', ['uid' => $this->account->id()])); + $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache')); // Ensure the password reset URL is not cached. @@ -125,6 +135,7 @@ function testUserPasswordReset() { // Log out, and try to log in again using the same one-time link. $this->drupalLogout(); $this->drupalGet($resetURL); + $this->drupalPostForm(NULL, NULL, t('Log in')); $this->assertText(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'), 'One-time link is no longer valid.'); // Request a new password again, this time using the email address. @@ -149,6 +160,7 @@ function testUserPasswordReset() { $bogus_timestamp = REQUEST_TIME - $timeout - 60; $_uid = $this->account->id(); $this->drupalGet("user/reset/$_uid/$bogus_timestamp/" . user_pass_rehash($this->account, $bogus_timestamp)); + $this->drupalPostForm(NULL, NULL, t('Log in')); $this->assertText(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'Expired password reset request rejected.'); // Create a user, block the account, and verify that a login link is denied. @@ -175,7 +187,31 @@ function testUserPasswordReset() { $this->account->setEmail("1" . $this->account->getEmail()); $this->account->save(); $this->drupalGet($old_email_reset_link); + $this->drupalPostForm(NULL, NULL, t('Log in')); $this->assertText(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'), 'One-time link is no longer valid.'); + + // Verify a password reset link will automatically log a user when /login is + // appended. + $this->drupalGet('user/password'); + $edit = array('name' => $this->account->getUsername()); + $this->drupalPostForm(NULL, $edit, t('Submit')); + $reset_url = $this->getResetURL(); + $this->drupalGet($reset_url . '/login'); + $this->assertLink(t('Log out')); + $this->assertTitle(t('@name | @site', array('@name' => $this->account->getUsername(), '@site' => $this->config('system.site')->get('name'))), 'Logged in using password reset link.'); + + // Ensure blocked and deleted accounts can't access the user.reset.login + // route. + $this->drupalLogout(); + $timestamp = REQUEST_TIME - 1; + $blocked_account = $this->drupalCreateUser()->block(); + $blocked_account->save(); + $this->drupalGet("user/reset/" . $blocked_account->id() . "/$timestamp/" . user_pass_rehash($blocked_account, $timestamp) . '/login'); + $this->assertResponse(403); + + $blocked_account->delete(); + $this->drupalGet("user/reset/" . $blocked_account->id() . "/$timestamp/" . user_pass_rehash($blocked_account, $timestamp) . '/login'); + $this->assertResponse(403); } /** @@ -195,6 +231,25 @@ public function getResetURL() { * Test user password reset while logged in. */ public function testUserPasswordResetLoggedIn() { + $another_account = $this->drupalCreateUser(); + $this->drupalLogin($another_account); + $this->drupalGet('user/password'); + $this->drupalPostForm(NULL, NULL, t('Submit')); + + // Click the reset URL while logged and change our password. + $resetURL = $this->getResetURL(); + // Log in as a different user. + $this->drupalLogin($this->account); + $this->drupalGet($resetURL); + $this->assertRaw(new FormattableMarkup( + 'Another user (%other_user) is already logged into the site on this computer, but you tried to use a one-time link for user %resetting_user. Please log out and try using the link again.', + ['%other_user' => $this->account->getUsername(), '%resetting_user' => $another_account->getUsername(), ':logout' => Url::fromRoute('user.logout')->toString()] + )); + + $another_account->delete(); + $this->drupalGet($resetURL); + $this->assertText('The one-time login link you clicked is invalid.'); + // Log in. $this->drupalLogin($this->account); @@ -212,6 +267,14 @@ public function testUserPasswordResetLoggedIn() { $edit = array('pass[pass1]' => $password, 'pass[pass2]' => $password); $this->drupalPostForm(NULL, $edit, t('Save')); $this->assertText(t('The changes have been saved.'), 'Password changed.'); + + // Logged in users should not be able to access the user.reset.login or the + // user.reset.form routes. + $timestamp = REQUEST_TIME - 1; + $this->drupalGet("user/reset/" . $this->account->id() . "/$timestamp/" . user_pass_rehash($this->account, $timestamp) . '/login'); + $this->assertResponse(403); + $this->drupalGet("user/reset/" . $this->account->id()); + $this->assertResponse(403); } /** @@ -265,9 +328,10 @@ function testResetImpersonation() { $reset_url = user_pass_reset_url($user1); $attack_reset_url = str_replace("user/reset/{$user1->id()}", "user/reset/{$user2->id()}", $reset_url); $this->drupalGet($attack_reset_url); + $this->drupalPostForm(NULL, NULL, t('Log in')); $this->assertNoText($user2->getUsername(), 'The invalid password reset page does not show the user name.'); $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.'); $this->assertText('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'); - } + } } diff --git a/core/modules/user/src/UserServiceProvider.php b/core/modules/user/src/UserServiceProvider.php index 79715df..49537e5 100644 --- a/core/modules/user/src/UserServiceProvider.php +++ b/core/modules/user/src/UserServiceProvider.php @@ -1,5 +1,8 @@ delete('config')->condition('name', 'user.mail')->execute(); $connection->insert('config') -->fields(array('collection', 'name', 'data')) -->values(array( + ->fields(array('collection', 'name', 'data')) + ->values(array( 'collection' => '', 'name' => 'user.mail', 'data' => "a:10:{s:14:\"cancel_confirm\";a:2:{s:4:\"body\";s:369:\"[user:name],\n\nA request to cancel your account has been made at [site:name].\n\nYou may now cancel your account on [site:url-brief] by clicking this link or copying and pasting it into your browser:\n\n[user:cancel-url]\n\nNOTE: The cancellation of your account is not reversible.\n\nThis link expires in one day and nothing will happen if it is not used.\n\n-- [site:name] team\";s:7:\"subject\";s:59:\"Account cancellation request for [user:name] at [site:name]\";}s:14:\"password_reset\";a:2:{s:4:\"body\";s:397:\"[user:name],\n\nA request to reset the password for your account has been made at [site:name].\n\nYou may now log in by clicking this link or copying and pasting it to your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password. It expires after one day and nothing will happen if it's not used.\n\n-- [site:name] team\";s:7:\"subject\";s:60:\"Replacement login information for [user:name] at [site:name]\";}s:22:\"register_admin_created\";a:2:{s:4:\"body\";s:463:\"[user:name],\n\nA site administrator at [site:name] has created an account for you. You may now log in by clicking this link or copying and pasting it to your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password.\n\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\n\nusername: [user:name]\npassword: Your password\n\n-- [site:name] team\";s:7:\"subject\";s:58:\"An administrator created an account for you at [site:name]\";}s:29:\"register_no_approval_required\";a:2:{s:4:\"body\";s:437:\"[user:name],\n\nThank you for registering at [site:name]. You may now log in by clicking this link or copying and pasting it to your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password.\n\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\n\nusername: [user:name]\npassword: Your password\n\n-- [site:name] team\";s:7:\"subject\";s:46:\"Account details for [user:name] at [site:name]\";}s:25:\"register_pending_approval\";a:2:{s:4:\"body\";s:281:\"[user:name],\n\nThank you for registering at [site:name]. Your application for an account is currently pending approval. Once it has been approved, you will receive another email containing information about how to log in, set your password, and other details.\n\n\n-- [site:name] team\";s:7:\"subject\";s:71:\"Account details for [user:name] at [site:name] (pending admin approval)\";}s:31:\"register_pending_approval_admin\";a:2:{s:4:\"body\";s:56:\"[user:name] has applied for an account.\n\n[user:edit-url]\";s:7:\"subject\";s:71:\"Account details for [user:name] at [site:name] (pending admin approval)\";}s:16:\"status_activated\";a:2:{s:4:\"body\";s:446:\"[user:name],\n\nYour account at [site:name] has been activated.\n\nYou may now log in by clicking this link or copying and pasting it into your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password.\n\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\n\nusername: [user:name]\npassword: Your password\n\n-- [site:name] team\";s:7:\"subject\";s:57:\"Account details for [user:name] at [site:name] (approved)\";}s:14:\"status_blocked\";a:2:{s:4:\"body\";s:89:\"[user:name],\n\nYour account on [site:account-name] has been blocked.\n\n-- [site:name] team\";s:7:\"subject\";s:56:\"Account details for [user:name] at [site:name] (blocked)\";}s:15:\"status_canceled\";a:2:{s:4:\"body\";s:82:\"[user:name],\n\nYour account on [site:name] has been canceled.\n\n-- [site:name] team\";s:7:\"subject\";s:57:\"Account details for [user:name] at [site:name] (canceled)\";}s:8:\"langcode\";s:2:\"en\";}" diff --git a/core/modules/user/tests/src/Unit/PermissionHandlerTest.php b/core/modules/user/tests/src/Unit/PermissionHandlerTest.php index 593d8c4..b0b91da 100644 --- a/core/modules/user/tests/src/Unit/PermissionHandlerTest.php +++ b/core/modules/user/tests/src/Unit/PermissionHandlerTest.php @@ -105,25 +105,25 @@ public function testBuildPermissionsYaml() { $url = vfsStream::url('modules'); mkdir($url . '/module_a'); - file_put_contents($url . '/module_a/module_a.permissions.yml', -"access_module_a: single_description" - ); + file_put_contents($url . '/module_a/module_a.permissions.yml', "access_module_a: single_description"); mkdir($url . '/module_b'); - file_put_contents($url . '/module_b/module_b.permissions.yml', -"'access module b': + file_put_contents($url . '/module_b/module_b.permissions.yml', << $this->mockModuleExtension('module_a', 'Module a'), @@ -187,9 +187,10 @@ public function testBuildPermissionsSortPerModule() { $url = vfsStream::url('modules'); mkdir($url . '/module_a'); - file_put_contents($url . '/module_a/module_a.permissions.yml', -"access_module_a2: single_description2 -access_module_a1: single_description1" + file_put_contents($url . '/module_a/module_a.permissions.yml', <<getDisplayName() + * @see \Drupal\Core\Session\AccountInterface::getDisplayName() */ function hook_user_format_name_alter(&$name, $account) { // Display the user's uid instead of name. diff --git a/core/modules/user/user.module b/core/modules/user/user.module index c530547..1e9f539 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -1334,7 +1334,7 @@ function user_toolbar() { $links_cache_contexts[] = 'user'; } else { - $links = array( + $links = array( 'login' => array( 'title' => t('Log in'), 'url' => Url::fromRoute('user.page'), diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml index caea979..319f219 100644 --- a/core/modules/user/user.routing.yml +++ b/core/modules/user/user.routing.yml @@ -168,6 +168,17 @@ user.cancel_confirm: _entity_access: 'user.delete' user: \d+ +user.reset.login: + path: '/user/reset/{uid}/{timestamp}/{hash}/login' + defaults: + _controller: '\Drupal\user\Controller\UserController::resetPassLogin' + _title: 'Reset password' + requirements: + _user_is_logged_in: 'FALSE' + options: + _maintenance_access: TRUE + no_cache: TRUE + user.reset: path: '/user/reset/{uid}/{timestamp}/{hash}' defaults: @@ -178,3 +189,14 @@ user.reset: options: _maintenance_access: TRUE no_cache: TRUE + +user.reset.form: + path: '/user/reset/{uid}' + defaults: + _controller: '\Drupal\user\Controller\UserController::getResetPassForm' + _title: 'Reset password' + requirements: + _user_is_logged_in: 'FALSE' + options: + _maintenance_access: TRUE + no_cache: TRUE diff --git a/core/modules/views/src/DisplayPluginCollection.php b/core/modules/views/src/DisplayPluginCollection.php index e94a6ac..0b692b1 100644 --- a/core/modules/views/src/DisplayPluginCollection.php +++ b/core/modules/views/src/DisplayPluginCollection.php @@ -26,7 +26,7 @@ class DisplayPluginCollection extends DefaultLazyPluginCollection { /** * Constructs a DisplayPluginCollection object. * - * @param \Drupal\views\ViewExecutable + * @param \Drupal\views\ViewExecutable $view * The view which has this displays attached. * @param \Drupal\Component\Plugin\PluginManagerInterface $manager * The manager to be used for instantiating plugins. diff --git a/core/modules/views/src/Plugin/views/access/AccessPluginBase.php b/core/modules/views/src/Plugin/views/access/AccessPluginBase.php index 7a9b27f..ee568e1 100644 --- a/core/modules/views/src/Plugin/views/access/AccessPluginBase.php +++ b/core/modules/views/src/Plugin/views/access/AccessPluginBase.php @@ -58,7 +58,7 @@ public function summaryTitle() { * @param \Drupal\Core\Session\AccountInterface $account * The user who wants to access this view. * - * @return TRUE + * @return bool * Returns whether the user has access to the view. */ abstract public function access(AccountInterface $account); diff --git a/core/modules/views/src/Plugin/views/area/Entity.php b/core/modules/views/src/Plugin/views/area/Entity.php index b1f5a1b..30c24ec 100644 --- a/core/modules/views/src/Plugin/views/area/Entity.php +++ b/core/modules/views/src/Plugin/views/area/Entity.php @@ -115,7 +115,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { $target = $target_entity->id(); } } - $form['target'] = [ + $form['target'] = [ '#title' => $this->t('@entity_type_label ID', ['@entity_type_label' => $label]), '#type' => 'textfield', '#default_value' => $target, diff --git a/core/modules/views/src/Plugin/views/area/View.php b/core/modules/views/src/Plugin/views/area/View.php index a624423..12f417c 100644 --- a/core/modules/views/src/Plugin/views/area/View.php +++ b/core/modules/views/src/Plugin/views/area/View.php @@ -28,37 +28,37 @@ class View extends AreaPluginBase { * * @var \Drupal\Core\Entity\EntityStorageInterface */ - protected $viewStorage; - - /** - * Constructs a View object. - * - * @param array $configuration - * A configuration array containing information about the plugin instance. - * @param string $plugin_id - * The plugin_id for the plugin instance. - * @param mixed $plugin_definition - * The plugin implementation definition. - * @param \Drupal\Core\Entity\EntityStorageInterface $view_storage - * The view storage. - */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $view_storage) { - parent::__construct($configuration, $plugin_id, $plugin_definition); - - $this->viewStorage = $view_storage; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('entity.manager')->getStorage('view') - ); - } + protected $viewStorage; + + /** + * Constructs a View object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Entity\EntityStorageInterface $view_storage + * The view storage. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $view_storage) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + + $this->viewStorage = $view_storage; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity.manager')->getStorage('view') + ); + } /** * {@inheritdoc} diff --git a/core/modules/views/src/Plugin/views/display/Block.php b/core/modules/views/src/Plugin/views/display/Block.php index fe37d91..3e35d7e 100644 --- a/core/modules/views/src/Plugin/views/display/Block.php +++ b/core/modules/views/src/Plugin/views/display/Block.php @@ -346,11 +346,11 @@ public function preBlockBuild(ViewsBlock $block) { * Block views use exposed widgets only if AJAX is set. */ public function usesExposed() { - if ($this->ajaxEnabled()) { - return parent::usesExposed(); - } - return FALSE; + if ($this->ajaxEnabled()) { + return parent::usesExposed(); } + return FALSE; + } /** * {@inheritdoc} diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php index 102718d..37b6a4c 100644 --- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php @@ -37,7 +37,7 @@ * * @var \Drupal\views\Plugin\views\ViewsHandlerInterface[] */ - public $handlers = []; + public $handlers = []; /** * An array of instantiated plugins used in this display. @@ -1051,9 +1051,9 @@ public function getArgumentsTokens() { if (!isset($tokens["%$count"])) { $tokens["%$count"] = ''; } - // Use strip tags as there should never be HTML in the path. - // However, we need to preserve special characters like " that - // were encoded by \Drupal\Component\Utility\Html::escape(). + // Use strip tags as there should never be HTML in the path. + // However, we need to preserve special characters like " that + // were encoded by \Drupal\Component\Utility\Html::escape(). $tokens["!$count"] = isset($this->view->args[$count - 1]) ? strip_tags(Html::decodeEntities($this->view->args[$count - 1])) : ''; } @@ -2236,7 +2236,7 @@ public function access(AccountInterface $account = NULL) { } $plugin = $this->getPlugin('access'); - /** @var \Drupal\views\Plugin\views\access\AccessPluginBase $plugin */ + /** @var \Drupal\views\Plugin\views\access\AccessPluginBase $plugin */ if ($plugin) { return $plugin->access($account); } diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php b/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php index 982b4ce..d3b4c79 100644 --- a/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php @@ -233,7 +233,7 @@ public function getPath(); * block display links to a page display, the page display will be returned * in both cases. * - * @return \Drupal\views\Plugin\views\display\DisplayRouterInterface|NULL + * @return \Drupal\views\Plugin\views\display\DisplayRouterInterface|null */ public function getRoutedDisplay(); diff --git a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php index e4189c5..5c2a8d8 100644 --- a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php +++ b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php @@ -540,7 +540,7 @@ public function buildExposeForm(&$form, FormStateInterface $form_state) { ); if (!empty($form['operator']['#type'])) { - // Increase the width of the left (operator) column. + // Increase the width of the left (operator) column. $form['operator']['#prefix'] = '
'; $form['operator']['#suffix'] = '
'; $form['value']['#prefix'] = '
'; @@ -1307,7 +1307,7 @@ public function convertExposedInput(&$input, $selected_group_id = NULL) { */ public function groupMultipleExposedInput(&$input) { if (!empty($input[$this->options['group_info']['identifier']])) { - return array_filter($input[$this->options['group_info']['identifier']]); + return array_filter($input[$this->options['group_info']['identifier']]); } return array(); } @@ -1491,9 +1491,9 @@ public function query() { * * @return bool */ - public function canGroup() { - return TRUE; - } + public function canGroup() { + return TRUE; + } /** * {@inheritdoc} diff --git a/core/modules/views/src/Plugin/views/filter/InOperator.php b/core/modules/views/src/Plugin/views/filter/InOperator.php index 593e97b..aa3e21f 100644 --- a/core/modules/views/src/Plugin/views/filter/InOperator.php +++ b/core/modules/views/src/Plugin/views/filter/InOperator.php @@ -54,7 +54,7 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o * This can use a guard to be used to reduce database hits as much as * possible. * - * @return array|NULL + * @return array|null * The stored values from $this->valueOptions. */ public function getValueOptions() { diff --git a/core/modules/views/src/Plugin/views/pager/Mini.php b/core/modules/views/src/Plugin/views/pager/Mini.php index 640010d..5f1750e 100644 --- a/core/modules/views/src/Plugin/views/pager/Mini.php +++ b/core/modules/views/src/Plugin/views/pager/Mini.php @@ -38,7 +38,7 @@ public function summaryTitle() { if (!empty($this->options['offset'])) { return $this->formatPlural($this->options['items_per_page'], 'Mini pager, @count item, skip @skip', 'Mini pager, @count items, skip @skip', array('@count' => $this->options['items_per_page'], '@skip' => $this->options['offset'])); } - return $this->formatPlural($this->options['items_per_page'], 'Mini pager, @count item', 'Mini pager, @count items', array('@count' => $this->options['items_per_page'])); + return $this->formatPlural($this->options['items_per_page'], 'Mini pager, @count item', 'Mini pager, @count items', array('@count' => $this->options['items_per_page'])); } /** diff --git a/core/modules/views/src/Plugin/views/pager/Some.php b/core/modules/views/src/Plugin/views/pager/Some.php index 2205f9d..ef7f65d 100644 --- a/core/modules/views/src/Plugin/views/pager/Some.php +++ b/core/modules/views/src/Plugin/views/pager/Some.php @@ -22,7 +22,7 @@ public function summaryTitle() { if (!empty($this->options['offset'])) { return $this->formatPlural($this->options['items_per_page'], '@count item, skip @skip', '@count items, skip @skip', array('@count' => $this->options['items_per_page'], '@skip' => $this->options['offset'])); } - return $this->formatPlural($this->options['items_per_page'], '@count item', '@count items', array('@count' => $this->options['items_per_page'])); + return $this->formatPlural($this->options['items_per_page'], '@count item', '@count items', array('@count' => $this->options['items_per_page'])); } protected function defineOptions() { diff --git a/core/modules/views/src/Plugin/views/sort/SortPluginBase.php b/core/modules/views/src/Plugin/views/sort/SortPluginBase.php index cd9555e..97122b1 100644 --- a/core/modules/views/src/Plugin/views/sort/SortPluginBase.php +++ b/core/modules/views/src/Plugin/views/sort/SortPluginBase.php @@ -64,11 +64,10 @@ public function adminSummary() { case 'asc': default: return $this->t('asc'); - break; + case 'DESC'; case 'desc'; return $this->t('desc'); - break; } } diff --git a/core/modules/views/src/Tests/Entity/FieldEntityTranslationTest.php b/core/modules/views/src/Tests/Entity/FieldEntityTranslationTest.php index eed7193..bd4e2ed 100644 --- a/core/modules/views/src/Tests/Entity/FieldEntityTranslationTest.php +++ b/core/modules/views/src/Tests/Entity/FieldEntityTranslationTest.php @@ -69,8 +69,7 @@ public function testTranslationRows() { $translation->save(); $this->drupalGet('test_entity_field_renderers/entity_translation'); - $this->assertRows( - [ + $this->assertRows([ [ 'title' => 'example EN', 'sticky' => 'Off', @@ -82,82 +81,76 @@ public function testTranslationRows() { ]); $this->drupalGet('test_entity_field_renderers/entity_default'); - $this->assertRows( + $this->assertRows([ [ - [ - 'title' => 'example EN', - 'sticky' => 'Off', - ], - [ - 'title' => 'example EN', - 'sticky' => 'Off', - ], - ]); + 'title' => 'example EN', + 'sticky' => 'Off', + ], + [ + 'title' => 'example EN', + 'sticky' => 'Off', + ], + ]); $this->drupalGet('test_entity_field_renderers/site_default'); - $this->assertRows( + $this->assertRows([ + [ + 'title' => 'example EN', + 'sticky' => 'Off', + ], [ - [ - 'title' => 'example EN', - 'sticky' => 'Off', - ], - [ - 'title' => 'example EN', - 'sticky' => 'Off', - ], - ]); + 'title' => 'example EN', + 'sticky' => 'Off', + ], + ]); $this->drupalGet('test_entity_field_renderers/language_interface'); - $this->assertRows( + $this->assertRows([ + [ + 'title' => 'example EN', + 'sticky' => 'Off', + ], [ - [ - 'title' => 'example EN', - 'sticky' => 'Off', - ], - [ - 'title' => 'example EN', - 'sticky' => 'Off', - ], - ]); + 'title' => 'example EN', + 'sticky' => 'Off', + ], + ]); $this->drupalGet('test_entity_field_renderers/language_interface', ['language' => new Language(['id' => 'es'])]); - $this->assertRows( + $this->assertRows([ [ - [ - 'title' => 'example ES', - 'sticky' => 'On', - ], - [ - 'title' => 'example ES', - 'sticky' => 'On', - ], - ]); + 'title' => 'example ES', + 'sticky' => 'On', + ], + [ + 'title' => 'example ES', + 'sticky' => 'On', + ], + ]); $this->drupalGet('test_entity_field_renderers/en'); - $this->assertRows( + $this->assertRows([ + [ + 'title' => 'example EN', + 'sticky' => 'Off', + ], [ - [ - 'title' => 'example EN', - 'sticky' => 'Off', - ], - [ - 'title' => 'example EN', - 'sticky' => 'Off', - ], - ]); + 'title' => 'example EN', + 'sticky' => 'Off', + ], + ]); $this->drupalGet('test_entity_field_renderers/es'); - $this->assertRows( + $this->assertRows([ + [ + 'title' => 'example ES', + 'sticky' => 'On', + ], [ - [ - 'title' => 'example ES', - 'sticky' => 'On', - ], - [ - 'title' => 'example ES', - 'sticky' => 'On', - ], - ]); + 'title' => 'example ES', + 'sticky' => 'On', + ], + ]); } /** diff --git a/core/modules/views/src/Tests/Handler/FieldWebTest.php b/core/modules/views/src/Tests/Handler/FieldWebTest.php index a6ff1b4..e852069 100644 --- a/core/modules/views/src/Tests/Handler/FieldWebTest.php +++ b/core/modules/views/src/Tests/Handler/FieldWebTest.php @@ -169,7 +169,7 @@ protected function parseContent($content) { * @param array $arguments * Some arguments for the xpath. * - * @return array|FALSE + * @return array|false * The return value of the xpath search. For details on the xpath string * format and return values see the SimpleXML documentation, * http://php.net/manual/function.simplexml-element-xpath.php. diff --git a/core/modules/views/src/Tests/Plugin/CacheTagTest.php b/core/modules/views/src/Tests/Plugin/CacheTagTest.php index 057110e..eaee656 100644 --- a/core/modules/views/src/Tests/Plugin/CacheTagTest.php +++ b/core/modules/views/src/Tests/Plugin/CacheTagTest.php @@ -97,7 +97,7 @@ protected function setUp() { * @param \Drupal\views\ViewExecutable $view * The view. * - * @return array|FALSE + * @return array|false * The render cache result or FALSE if not existent. */ protected function getRenderCache(ViewExecutable $view) { diff --git a/core/modules/views/src/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php index c8b0593..786b10f 100644 --- a/core/modules/views/src/ViewExecutable.php +++ b/core/modules/views/src/ViewExecutable.php @@ -1655,7 +1655,7 @@ public function preview($display_id = NULL, $args = array()) { /** * Runs attachments and lets the display do what it needs to before running. * - * @param array @args + * @param array $args * An array of arguments from the URL that can be used by the view. */ public function preExecute($args = array()) { diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_content_ajax.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_content_ajax.yml new file mode 100644 index 0000000..adc689e --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_content_ajax.yml @@ -0,0 +1,605 @@ +langcode: en +status: true +dependencies: + module: + - node + - user +id: test_content_ajax +label: Content +module: node +description: 'Find and manage content.' +tag: default +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_options: + use_ajax: true + access: + type: perm + options: + perm: 'access content overview' + cache: + type: tag + query: + type: views_query + exposed_form: + type: basic + options: + submit_button: Filter + reset_button: true + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: full + options: + items_per_page: 50 + tags: + previous: '‹ Previous' + next: 'Next ›' + first: '« First' + last: 'Last »' + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: true + caption: '' + summary: '' + description: '' + columns: + node_bulk_form: node_bulk_form + title: title + type: type + name: name + status: status + changed: changed + edit_node: edit_node + delete_node: delete_node + dropbutton: dropbutton + timestamp: title + info: + node_bulk_form: + align: '' + separator: '' + empty_column: false + responsive: '' + title: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + type: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + name: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-low + status: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + changed: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: priority-low + edit_node: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + delete_node: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + dropbutton: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + timestamp: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: changed + empty_table: true + row: + type: fields + fields: + node_bulk_form: + id: node_bulk_form + table: node + field: node_bulk_form + label: '' + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + plugin_id: node_bulk_form + entity_type: node + title: + id: title + table: node_field_data + field: title + label: Title + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + entity_type: node + entity_field: title + type: string + settings: + link_to_entity: true + plugin_id: field + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + label: 'Content type' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: entity_reference_label + settings: + link: false + group_column: target_id + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: node + entity_field: type + plugin_id: field + name: + id: name + table: users_field_data + field: name + relationship: uid + label: Author + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + plugin_id: field + type: user_name + entity_type: user + entity_field: name + status: + id: status + table: node_field_data + field: status + label: Status + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + type: boolean + settings: + format: custom + format_custom_true: Published + format_custom_false: Unpublished + plugin_id: field + entity_type: node + entity_field: status + changed: + id: changed + table: node_field_data + field: changed + label: Updated + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + type: timestamp + settings: + date_format: short + custom_date_format: '' + timezone: '' + plugin_id: field + entity_type: node + entity_field: changed + operations: + id: operations + table: node + field: operations + relationship: none + group_type: group + admin_label: '' + label: Operations + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + destination: true + plugin_id: entity_operations + filters: + status_extra: + id: status_extra + table: node_field_data + field: status_extra + operator: '=' + value: false + plugin_id: node_status + group: 1 + entity_type: node + status: + id: status + table: node_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: true + group: 1 + exposed: true + expose: + operator_id: '' + label: Status + description: '' + use_operator: false + operator: status_op + identifier: status + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: true + group_info: + label: 'Published status' + description: '' + identifier: status + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: + title: Published + operator: '=' + value: '1' + 2: + title: Unpublished + operator: '=' + value: '0' + plugin_id: boolean + entity_type: node + entity_field: status + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: type_op + label: 'Content type' + description: '' + use_operator: false + operator: type_op + identifier: type + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: bundle + entity_type: node + entity_field: type + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: title_op + label: Title + description: '' + use_operator: false + operator: title_op + identifier: title + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: string + entity_type: node + entity_field: title + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: langcode_op + label: Language + description: '' + use_operator: false + operator: langcode_op + identifier: langcode + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: language + entity_type: node + entity_field: langcode + sorts: { } + title: Content + empty: + area_text_custom: + id: area_text_custom + table: views + field: area_text_custom + empty: true + content: 'No content available.' + plugin_id: text_custom + arguments: { } + relationships: + uid: + id: uid + table: node_field_data + field: uid + admin_label: author + required: true + plugin_id: standard + show_admin_links: false + filter_groups: + operator: AND + groups: + 1: AND + display_extenders: { } + display_plugin: default + display_title: Master + id: default + position: 0 + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - 'user.node_grants:view' + - user.permissions + max-age: 0 + tags: { } + page_1: + display_options: + path: test-content-ajax + menu: + type: 'default tab' + title: Content + description: '' + menu_name: admin + weight: -10 + context: '' + tab_options: + type: normal + title: Content + description: 'Find and manage content' + menu_name: admin + weight: -10 + display_extenders: { } + display_plugin: page + display_title: Page + id: page_1 + position: 1 + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - 'user.node_grants:view' + - user.permissions + max-age: 0 + tags: { } diff --git a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/query/QueryTest.php b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/query/QueryTest.php index fbecf9f..1eb34c5 100644 --- a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/query/QueryTest.php +++ b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/query/QueryTest.php @@ -90,7 +90,7 @@ public function build(ViewExecutable $view) { // @todo You could add a string representation of the query. $this->view->build_info['query'] = ""; $this->view->build_info['count_query'] = ""; -} + } /** * {@inheritdoc} diff --git a/core/modules/views/tests/src/FunctionalJavascript/ClickSortingAJAXTest.php b/core/modules/views/tests/src/FunctionalJavascript/ClickSortingAJAXTest.php new file mode 100644 index 0000000..0e5095d --- /dev/null +++ b/core/modules/views/tests/src/FunctionalJavascript/ClickSortingAJAXTest.php @@ -0,0 +1,77 @@ +createContentType(['type' => 'page']); + $this->createNode(['title' => 'Page A', 'changed' => REQUEST_TIME]); + $this->createNode(['title' => 'Page B', 'changed' => REQUEST_TIME + 1000]); + + // Create a user privileged enough to view content. + $user = $this->drupalCreateUser([ + 'administer site configuration', + 'access content', + 'access content overview', + ]); + $this->drupalLogin($user); + } + + /** + * Tests if sorting via AJAX works for the "Content" View. + */ + public function testClickSorting() { + // Visit the content page. + $this->drupalGet('test-content-ajax'); + + $session_assert = $this->assertSession(); + + $page = $this->getSession()->getPage(); + + // Ensure that the Content we're testing for is in the right order, default + // sorting is by changed timestamp so the last created node should be first. + /** @var \Behat\Mink\Element\NodeElement[] $rows */ + $rows = $page->findAll('css', 'tbody tr'); + $this->assertCount(2, $rows); + $this->assertContains('Page B', $rows[0]->getHtml()); + $this->assertContains('Page A', $rows[1]->getHtml()); + + // Now sort by title and check if the order changed. + $page->clickLink('Title'); + $session_assert->assertWaitOnAjaxRequest(); + $rows = $page->findAll('css', 'tbody tr'); + $this->assertCount(2, $rows); + $this->assertContains('Page A', $rows[0]->getHtml()); + $this->assertContains('Page B', $rows[1]->getHtml()); + } + +} diff --git a/core/modules/views/tests/src/Kernel/Handler/FilterNumericTest.php b/core/modules/views/tests/src/Kernel/Handler/FilterNumericTest.php index c6f5af9..c4c978a 100644 --- a/core/modules/views/tests/src/Kernel/Handler/FilterNumericTest.php +++ b/core/modules/views/tests/src/Kernel/Handler/FilterNumericTest.php @@ -127,7 +127,7 @@ public function testFilterNumericBetween() { $view->destroy(); $view->setDisplay(); - // Change the filtering + // Change the filtering $view->displayHandlers->get('default')->overrideOption('filters', array( 'age' => array( 'id' => 'age', diff --git a/core/modules/views/tests/src/Kernel/Handler/FilterStringTest.php b/core/modules/views/tests/src/Kernel/Handler/FilterStringTest.php index e8c4977..5dd47ec 100644 --- a/core/modules/views/tests/src/Kernel/Handler/FilterStringTest.php +++ b/core/modules/views/tests/src/Kernel/Handler/FilterStringTest.php @@ -297,7 +297,7 @@ function testFilterStringWord() { function testFilterStringGroupedExposedWord() { - $filters = $this->getGroupedExposedFilters(); + $filters = $this->getGroupedExposedFilters(); $view = $this->getBasicPageView(); // Filter: Name, Operator: contains, Value: ing diff --git a/core/modules/views/tests/src/Kernel/Handler/SortDateTest.php b/core/modules/views/tests/src/Kernel/Handler/SortDateTest.php index 8c454b7..4a13754 100644 --- a/core/modules/views/tests/src/Kernel/Handler/SortDateTest.php +++ b/core/modules/views/tests/src/Kernel/Handler/SortDateTest.php @@ -24,61 +24,61 @@ protected function expectedResultSet($granularity, $reverse = TRUE) { $expected = array(); if (!$reverse) { switch ($granularity) { - case 'second': - $expected = array( - array('name' => 'John'), - array('name' => 'Paul'), - array('name' => 'Meredith'), - array('name' => 'Ringo'), - array('name' => 'George'), - ); - break; - case 'minute': - $expected = array( - array('name' => 'John'), - array('name' => 'Paul'), - array('name' => 'Ringo'), - array('name' => 'Meredith'), - array('name' => 'George'), - ); - break; - case 'hour': - $expected = array( - array('name' => 'John'), - array('name' => 'Ringo'), - array('name' => 'Paul'), - array('name' => 'Meredith'), - array('name' => 'George'), - ); - break; - case 'day': - $expected = array( - array('name' => 'John'), - array('name' => 'Ringo'), - array('name' => 'Paul'), - array('name' => 'Meredith'), - array('name' => 'George'), - ); - break; - case 'month': - $expected = array( - array('name' => 'John'), - array('name' => 'George'), - array('name' => 'Ringo'), - array('name' => 'Paul'), - array('name' => 'Meredith'), - ); - break; - case 'year': - $expected = array( - array('name' => 'John'), - array('name' => 'George'), - array('name' => 'Ringo'), - array('name' => 'Paul'), - array('name' => 'Meredith'), - ); - break; - } + case 'second': + $expected = array( + array('name' => 'John'), + array('name' => 'Paul'), + array('name' => 'Meredith'), + array('name' => 'Ringo'), + array('name' => 'George'), + ); + break; + case 'minute': + $expected = array( + array('name' => 'John'), + array('name' => 'Paul'), + array('name' => 'Ringo'), + array('name' => 'Meredith'), + array('name' => 'George'), + ); + break; + case 'hour': + $expected = array( + array('name' => 'John'), + array('name' => 'Ringo'), + array('name' => 'Paul'), + array('name' => 'Meredith'), + array('name' => 'George'), + ); + break; + case 'day': + $expected = array( + array('name' => 'John'), + array('name' => 'Ringo'), + array('name' => 'Paul'), + array('name' => 'Meredith'), + array('name' => 'George'), + ); + break; + case 'month': + $expected = array( + array('name' => 'John'), + array('name' => 'George'), + array('name' => 'Ringo'), + array('name' => 'Paul'), + array('name' => 'Meredith'), + ); + break; + case 'year': + $expected = array( + array('name' => 'John'), + array('name' => 'George'), + array('name' => 'Ringo'), + array('name' => 'Paul'), + array('name' => 'Meredith'), + ); + break; + } } else { switch ($granularity) { diff --git a/core/modules/views/tests/src/Kernel/ModuleTest.php b/core/modules/views/tests/src/Kernel/ModuleTest.php index ddf3347..204433d 100644 --- a/core/modules/views/tests/src/Kernel/ModuleTest.php +++ b/core/modules/views/tests/src/Kernel/ModuleTest.php @@ -199,7 +199,7 @@ public function testLoadFunctions() { $expected_opt_groups = array(); foreach ($all_views as $view) { foreach ($view->get('display') as $display) { - $expected_opt_groups[$view->id()][$view->id() . ':' . $display['id']] = (string) t('@view : @display', array('@view' => $view->id(), '@display' => $display['id'])); + $expected_opt_groups[$view->id()][$view->id() . ':' . $display['id']] = (string) t('@view : @display', array('@view' => $view->id(), '@display' => $display['id'])); } } $this->assertIdentical($expected_opt_groups, $this->castSafeStrings(Views::getViewsAsOptions(FALSE, 'all', NULL, TRUE)), 'Expected option array for an option group returned.'); diff --git a/core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php b/core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php index 332df31..d6b2a1e 100644 --- a/core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php +++ b/core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php @@ -1,6 +1,6 @@ viewsData->setEntityType($entity_type); - // Setup the table mapping. + // Setup the table mapping. $table_mapping = $this->getMockBuilder(DefaultTableMapping::class) ->disableOriginalConstructor() ->getMock(); @@ -717,7 +717,7 @@ public function testRevisionTableFields() { $this->viewsData->setEntityType($entity_type); - // Setup the table mapping. + // Setup the table mapping. $table_mapping = $this->getMockBuilder(DefaultTableMapping::class) ->disableOriginalConstructor() ->getMock(); @@ -1100,12 +1100,10 @@ public function setKey($key, $value) { } -} +namespace Drupal\entity_test\Entity; -namespace Drupal\entity_test\Entity { - if (!function_exists('t')) { - function t($string, array $args = []) { - return strtr($string, $args); - } +if (!function_exists('t')) { + function t($string, array $args = []) { + return strtr($string, $args); } } diff --git a/core/modules/views/tests/src/Unit/Plugin/Block/ViewsBlockTest.php b/core/modules/views/tests/src/Unit/Plugin/Block/ViewsBlockTest.php index 114638a..bf4b122 100644 --- a/core/modules/views/tests/src/Unit/Plugin/Block/ViewsBlockTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/Block/ViewsBlockTest.php @@ -1,6 +1,6 @@ name == 'my_special_view' && $account->hasPermission('my special permission') && $display_id == 'public_display') { + if ($view->id() == 'my_special_view' && $account->hasPermission('my special permission') && $display_id == 'public_display') { $args[0] = 'custom value'; } } @@ -744,7 +744,7 @@ function hook_views_post_build(ViewExecutable $view) { // assumptions about both exposed filter settings and the fields in the view. // Also note that this alter could be done at any point before the view being // rendered.) - if ($view->name == 'my_view' && isset($view->exposed_raw_input['type']) && $view->exposed_raw_input['type'] != 'All') { + if ($view->id() == 'my_view' && isset($view->exposed_raw_input['type']) && $view->exposed_raw_input['type'] != 'All') { // 'Type' should be interpreted as content type. if (isset($view->field['type'])) { $view->field['type']->options['exclude'] = TRUE; @@ -771,7 +771,7 @@ function hook_views_pre_execute(ViewExecutable $view) { $account = \Drupal::currentUser(); if (count($view->query->tables) > 2 && $account->hasPermission('administer views')) { - drupal_set_message(t('The view %view may be heavy to execute.', array('%view' => $view->name)), 'warning'); + drupal_set_message(t('The view %view may be heavy to execute.', array('%view' => $view->id())), 'warning'); } } diff --git a/core/modules/views_ui/src/Controller/ViewsUIController.php b/core/modules/views_ui/src/Controller/ViewsUIController.php index 4b0b44e..f36e912 100644 --- a/core/modules/views_ui/src/Controller/ViewsUIController.php +++ b/core/modules/views_ui/src/Controller/ViewsUIController.php @@ -31,7 +31,7 @@ class ViewsUIController extends ControllerBase { /** * Constructs a new \Drupal\views_ui\Controller\ViewsUIController object. * - * @param \Drupal\views\ViewsData views_data + * @param \Drupal\views\ViewsData $views_data * The Views data cache object. */ public function __construct(ViewsData $views_data) { diff --git a/core/modules/views_ui/src/Form/Ajax/RearrangeFilter.php b/core/modules/views_ui/src/Form/Ajax/RearrangeFilter.php index e858a84..e8eedd7 100644 --- a/core/modules/views_ui/src/Form/Ajax/RearrangeFilter.php +++ b/core/modules/views_ui/src/Form/Ajax/RearrangeFilter.php @@ -331,7 +331,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { * * For example array(0 => 'foo') would be array(1 => 'foo'). * - * @param array + * @param array $array * The array to increment keys on. * * @return array diff --git a/core/modules/views_ui/src/Tests/CustomBooleanTest.php b/core/modules/views_ui/src/Tests/CustomBooleanTest.php index 828b5b9..ecde493 100644 --- a/core/modules/views_ui/src/Tests/CustomBooleanTest.php +++ b/core/modules/views_ui/src/Tests/CustomBooleanTest.php @@ -115,7 +115,7 @@ public function testCustomOptionTemplate() { ->save(); $this->assertEqual($this->config('system.theme')->get('default'), 'views_test_theme'); - // Add the boolean field handler to the test view. + // Add the boolean field handler to the test view. $view = Views::getView('test_view'); $view->setDisplay(); diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index 6195c8c..cb222b0 100644 --- a/core/modules/views_ui/src/ViewUI.php +++ b/core/modules/views_ui/src/ViewUI.php @@ -1160,9 +1160,9 @@ public function uriRelationships() { /** * {@inheritdoc} */ - public function referencedEntities() { - return $this->storage->referencedEntities(); - } + public function referencedEntities() { + return $this->storage->referencedEntities(); + } /** * {@inheritdoc} diff --git a/core/phpcs.xml.dist b/core/phpcs.xml.dist index dae5b7a..ff05061 100644 --- a/core/phpcs.xml.dist +++ b/core/phpcs.xml.dist @@ -46,13 +46,11 @@ - - @@ -90,6 +88,7 @@ + diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh index a981c25..cd00d74 100755 --- a/core/scripts/run-tests.sh +++ b/core/scripts/run-tests.sh @@ -34,6 +34,11 @@ const SIMPLETEST_SCRIPT_EXIT_FAILURE = 1; const SIMPLETEST_SCRIPT_EXIT_EXCEPTION = 2; +if (!class_exists('\PHPUnit_Framework_TestCase')) { + echo "\nrun-tests.sh requires the PHPUnit testing framework. Please use 'composer install --dev' to ensure that it is present.\n\n"; + exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); +} + // Set defaults and get overrides. list($args, $count) = simpletest_script_parse_args(); diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php new file mode 100644 index 0000000..e02e0df --- /dev/null +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php @@ -0,0 +1,61 @@ +drupalCreateUser(); + $this->drupalLogin($account); + + $menu_link_content = MenuLinkContent::create([ + 'title' => 'Link to front page', + 'menu_name' => 'tools', + 'link' => ['uri' => 'route:'], + ]); + $menu_link_content->save(); + + $this->drupalPlaceBlock('system_menu_block:tools'); + } + + /** + * Tests that the session doesn't expire. + * + * Makes sure that drupal_valid_test_ua() works for multiple requests + * performed by the Mink browser. The SIMPLETEST_USER_AGENT cookie must always + * be valid. + */ + public function testSessionExpiration() { + // Visit the front page and click the link back to the front page a large + // number of times. + $this->drupalGet(''); + + $session_assert = $this->assertSession(); + + $page = $this->getSession()->getPage(); + + for ($i = 0; $i < 25; $i++) { + $page->clickLink('Link to front page'); + $session_assert->statusCodeEquals(200); + } + } + +} diff --git a/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php b/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php similarity index 97% rename from core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php rename to core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php index 5c1e3f5..e4979d3 100644 --- a/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php +++ b/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php @@ -1,6 +1,6 @@ storage = new StorageReplaceDataWrapper($this->container->get('config.storage')); + // ::listAll() verifications require other configuration data to exist. + $this->storage->write('system.performance', array()); + $this->storage->replaceData('system.performance', array('foo' => 'bar')); + } + + /** + * {@inheritdoc} + */ + protected function read($name) { + return $this->storage->read($name); + } + + /** + * {@inheritdoc} + */ + protected function insert($name, $data) { + $this->storage->write($name, $data); + } + + /** + * {@inheritdoc} + */ + protected function update($name, $data) { + $this->storage->write($name, $data); + } + + /** + * {@inheritdoc} + */ + protected function delete($name) { + $this->storage->delete($name); + } + + /** + * {@inheritdoc} + */ + public function testInvalidStorage() { + // No-op as this test does not make sense. + } + + /** + * Tests if new collections created correctly. + * + * @param string $collection + * The collection name. + * + * @dataProvider providerCollections + */ + public function testCreateCollection($collection) { + $initial_collection_name = $this->storage->getCollectionName(); + + // Create new storage with given collection and check it is set correctly. + $new_storage = $this->storage->createCollection($collection); + $this->assertSame($collection, $new_storage->getCollectionName()); + + // Check collection not changed in the current storage instance. + $this->assertSame($initial_collection_name, $this->storage->getCollectionName()); + } + + /** + * Data provider for testing different collections. + * + * @return array + * Returns an array of collection names. + */ + public function providerCollections() { + return [ + [StorageInterface::DEFAULT_COLLECTION], + ['foo.bar'], + ]; + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Database/DatabaseTestBase.php b/core/tests/Drupal/KernelTests/Core/Database/DatabaseTestBase.php index b38c5b6..f8c8c3d 100644 --- a/core/tests/Drupal/KernelTests/Core/Database/DatabaseTestBase.php +++ b/core/tests/Drupal/KernelTests/Core/Database/DatabaseTestBase.php @@ -36,20 +36,20 @@ protected function setUp() { */ function ensureSampleDataNull() { db_insert('test_null') - ->fields(array('name', 'age')) - ->values(array( + ->fields(array('name', 'age')) + ->values(array( 'name' => 'Kermit', 'age' => 25, )) - ->values(array( + ->values(array( 'name' => 'Fozzie', 'age' => NULL, )) - ->values(array( + ->values(array( 'name' => 'Gonzo', 'age' => 27, )) - ->execute(); + ->execute(); } /** diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityNonRevisionableTranslatableFieldTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityNonRevisionableTranslatableFieldTest.php new file mode 100644 index 0000000..da6ec66 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityNonRevisionableTranslatableFieldTest.php @@ -0,0 +1,47 @@ +installEntitySchema('entity_test_mulrev'); + $this->installEntitySchema('configurable_language'); + + ConfigurableLanguage::createFromLangcode('es')->save(); + } + + /** + * Tests translating a non-revisionable field. + */ + function testTranslatingNonRevisionableField() { + /** @var \Drupal\Core\Entity\ContentEntityBase $entity */ + $entity = EntityTestMulRev::create(); + $entity->set('non_rev_field', 'Hello'); + $entity->save(); + + $translation = $entity->addTranslation('es'); + $translation->set('non_rev_field', 'Hola'); + $translation->save(); + + $reloaded = EntityTestMulRev::load($entity->id()); + $this->assertEquals('Hello', $reloaded->getTranslation('en')->get('non_rev_field')->value); + + $this->assertEquals('Hola', $reloaded->getTranslation('es')->get('non_rev_field')->value); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php index eb0fbf6..8c79c5c 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php @@ -481,7 +481,7 @@ public function testCount() { ->exists("$field_name.color") ->count() ->execute(); - $this->assertFalse($count); + $this->assertFalse($count); } /** diff --git a/core/tests/Drupal/KernelTests/Core/Menu/MenuLinkTreeTest.php b/core/tests/Drupal/KernelTests/Core/Menu/MenuLinkTreeTest.php index 0cdc303..1efb14b 100644 --- a/core/tests/Drupal/KernelTests/Core/Menu/MenuLinkTreeTest.php +++ b/core/tests/Drupal/KernelTests/Core/Menu/MenuLinkTreeTest.php @@ -84,16 +84,16 @@ public function testDeleteLinksInMenu() { * Tests creating links with an expected tree structure. */ public function testCreateLinksInMenu() { - // This creates a tree with the following structure: - // - 1 - // - 2 - // - 3 - // - 4 - // - 5 - // - 7 - // - 6 - // - 8 - // With link 6 being the only external link. + // This creates a tree with the following structure: + // - 1 + // - 2 + // - 3 + // - 4 + // - 5 + // - 7 + // - 6 + // - 8 + // With link 6 being the only external link. $links = array( 1 => MenuLinkMock::create(array('id' => 'test.example1', 'route_name' => 'example1', 'title' => 'foo', 'parent' => '')), diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php index 4063d70..0146607 100644 --- a/core/tests/Drupal/KernelTests/KernelTestBase.php +++ b/core/tests/Drupal/KernelTests/KernelTestBase.php @@ -1104,7 +1104,7 @@ public function __get($name) { return Settings::get('file_public_path', \Drupal::service('site.path') . '/files'); case 'private_files_directory': - return $this->container->get('config.factory')->get('system.file')->get('path.private'); + return Settings::get('file_private_path'); case 'temp_files_directory': return file_directory_temp(); diff --git a/core/tests/Drupal/KernelTests/KernelTestBaseTest.php b/core/tests/Drupal/KernelTests/KernelTestBaseTest.php index 30f05b2..1605ad6 100644 --- a/core/tests/Drupal/KernelTests/KernelTestBaseTest.php +++ b/core/tests/Drupal/KernelTests/KernelTestBaseTest.php @@ -226,7 +226,7 @@ protected function tearDown() { ':pattern' => 'sqlite_%', ))->fetchAllKeyed(0, 0); - $this->assertTrue(empty($result), 'All test tables have been removed.'); + $this->assertTrue(empty($result), 'All test tables have been removed.'); } } diff --git a/core/tests/Drupal/Tests/BrowserTestBase.php b/core/tests/Drupal/Tests/BrowserTestBase.php index c8df47c..ea88906 100644 --- a/core/tests/Drupal/Tests/BrowserTestBase.php +++ b/core/tests/Drupal/Tests/BrowserTestBase.php @@ -385,7 +385,7 @@ protected function getDefaultDriverInstance() { } if (is_array($this->minkDefaultDriverArgs)) { - // Use ReflectionClass to instantiate class with received params. + // Use ReflectionClass to instantiate class with received params. $reflector = new \ReflectionClass($this->minkDefaultDriverClass); $driver = $reflector->newInstanceArgs($this->minkDefaultDriverArgs); } diff --git a/core/tests/Drupal/Tests/Component/ProxyBuilder/ProxyBuilderTest.php b/core/tests/Drupal/Tests/Component/ProxyBuilder/ProxyBuilderTest.php index 53a14f5..f81ca0d 100644 --- a/core/tests/Drupal/Tests/Component/ProxyBuilder/ProxyBuilderTest.php +++ b/core/tests/Drupal/Tests/Component/ProxyBuilder/ProxyBuilderTest.php @@ -230,7 +230,7 @@ public function testMethod($parameter) EOS; -$this->assertEquals($this->buildExpectedClass($class, $method_body), $result); + $this->assertEquals($this->buildExpectedClass($class, $method_body), $result); } /** diff --git a/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php b/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php index 0d6babc..878bbfd 100644 --- a/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php @@ -5,7 +5,7 @@ * Contains \Drupal\Tests\Component\Utility\ArgumentsResolverTest. */ -namespace Drupal\Tests\Component\Utility { +namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\ArgumentsResolver; use Drupal\Tests\UnitTestCase; @@ -187,7 +187,7 @@ public function providerTestHandleUnresolvedArgument() { $data = []; $data[] = [function($foo) {}]; $data[] = [[new TestClass(), 'access']]; - $data[] = ['test_access_arguments_resolver_access']; + $data[] = ['Drupal\Tests\Component\Utility\test_access_arguments_resolver_access']; return $data; } @@ -214,9 +214,5 @@ public function access($foo) { interface TestInterface2 { } -} - -namespace { - function test_access_arguments_resolver_access($foo) { - } +function test_access_arguments_resolver_access($foo) { } diff --git a/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php index b1a36b9..cbf86d2 100644 --- a/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php @@ -173,7 +173,7 @@ function providerFormat() { return $tests; } - /** + /** * Custom error handler that saves the last error. * * We need this custom error handler because we cannot rely on the error to diff --git a/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php index 4b78213..8602661 100644 --- a/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php @@ -1,41 +1,6 @@ moduleHandler->expects($this->atLeastOnce()) + $this->moduleHandler->expects($this->atLeastOnce()) ->method('moduleExists') ->with('dependencies') ->will($this->returnValue(TRUE)); diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheableMetadataTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheableMetadataTest.php index da6e994..5eecf1c 100644 --- a/core/tests/Drupal/Tests/Core/Cache/CacheableMetadataTest.php +++ b/core/tests/Drupal/Tests/Core/Cache/CacheableMetadataTest.php @@ -121,14 +121,14 @@ public function testSetCacheMaxAge($data, $expect_exception) { * Data provider for testSetCacheMaxAge. */ public function providerSetCacheMaxAge() { - return [ - [0 , FALSE], - ['http', TRUE], - ['0', TRUE], - [new \stdClass(), TRUE], - [300, FALSE], - [[], TRUE], - [8.0, TRUE] + return [ + [0 , FALSE], + ['http', TRUE], + ['0', TRUE], + [new \stdClass(), TRUE], + [300, FALSE], + [[], TRUE], + [8.0, TRUE] ]; } diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php index 2c4a01b..6f02023 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php @@ -1,6 +1,6 @@ fieldDefinitions = $this->mockFieldDefinitions(array('id')); $this->fieldDefinitions['id']->expects($this->any()) - ->method('getType') - ->will($this->returnValue('integer')); + ->method('getType') + ->will($this->returnValue('integer')); $this->setUpEntityStorage(); $this->entityType->expects($this->any()) - ->method('getKey') - ->will($this->returnValueMap(array( - array('id', 'id'), - ))); + ->method('getKey') + ->will($this->returnValueMap( + array(array('id', 'id')) + )); $method = new \ReflectionMethod($this->entityStorage, 'cleanIds'); $method->setAccessible(TRUE); diff --git a/core/tests/Drupal/Tests/Core/Form/FormStateDecoratorBaseTest.php b/core/tests/Drupal/Tests/Core/Form/FormStateDecoratorBaseTest.php index 0f1c27b..67e7a86 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormStateDecoratorBaseTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormStateDecoratorBaseTest.php @@ -271,7 +271,7 @@ public function testHasFileElement($has_file_element) { * * @dataProvider providerLimitValidationErrors * - * @param array{}|null $limit_validation_errors + * @param array[]|null $limit_validation_errors * Any valid value for * \Drupal\Core\Form\FormStateInterface::setLimitValidationErrors()'s * $limit_validation_errors argument; @@ -577,7 +577,7 @@ public function testSetStorage() { ]; $this->decoratedFormState->setStorage($storage) - ->shouldBeCalled(); + ->shouldBeCalled(); $this->assertSame($this->formStateDecoratorBase, $this->formStateDecoratorBase->setStorage($storage)); } diff --git a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php index fd88ea2..078d30e 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php +++ b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php @@ -1,6 +1,6 @@ moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); $this->formCache = $this->getMock('Drupal\Core\Form\FormCacheInterface'); @@ -311,35 +314,3 @@ public function getInfo($type) { } } - -} - -namespace { - - function test_form_id() { - $form['test'] = array( - '#type' => 'textfield', - '#title' => 'Test', - ); - $form['options'] = array( - '#type' => 'radios', - '#options' => array( - 'foo' => 'foo', - 'bar' => 'bar', - ), - ); - $form['value'] = array( - '#type' => 'value', - '#value' => 'bananas', - ); - $form['actions'] = array( - '#type' => 'actions', - ); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => 'Submit', - ); - return $form; - } - -} diff --git a/core/tests/Drupal/Tests/Core/Form/fixtures/form_base_test.inc b/core/tests/Drupal/Tests/Core/Form/fixtures/form_base_test.inc new file mode 100644 index 0000000..cd83394 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Form/fixtures/form_base_test.inc @@ -0,0 +1,38 @@ + 'textfield', + '#title' => 'Test', + ); + $form['options'] = array( + '#type' => 'radios', + '#options' => array( + 'foo' => 'foo', + 'bar' => 'bar', + ), + ); + $form['value'] = array( + '#type' => 'value', + '#value' => 'bananas', + ); + $form['actions'] = array( + '#type' => 'actions', + ); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => 'Submit', + ); + return $form; +} diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php index 21c8839..92ce48e 100644 --- a/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php @@ -162,9 +162,9 @@ public function testGetActionsForRoute($route_appears, array $plugin_definitions ->method('getTitle') ->will($this->returnValue($plugin_definition['title'])); $this->controllerResolver->expects($this->any()) - ->method('getArguments') - ->with($this->request, array($plugin, 'getTitle')) - ->will($this->returnValue(array())); + ->method('getArguments') + ->with($this->request, array($plugin, 'getTitle')) + ->will($this->returnValue(array())); $plugin->expects($this->any()) ->method('getWeight') diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php index 65fb417..5fc65ad 100644 --- a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php @@ -441,7 +441,7 @@ public function testGetTasksBuildWithCacheabilityMetadata() { // Ensure that all cacheability metadata is merged together. $this->assertEquals(['tag.example1', 'tag.example2'], $cacheability->getCacheTags()); $this->assertEquals(['context.example1', 'context.example2', 'route', 'user.permissions'], $cacheability->getCacheContexts()); - } + } protected function setupFactoryAndLocalTaskPlugins(array $definitions, $active_plugin_id) { $map = []; diff --git a/core/tests/Drupal/Tests/Core/Plugin/Discovery/HookDiscoveryTest.php b/core/tests/Drupal/Tests/Core/Plugin/Discovery/HookDiscoveryTest.php index c139b88..1472dc0 100644 --- a/core/tests/Drupal/Tests/Core/Plugin/Discovery/HookDiscoveryTest.php +++ b/core/tests/Drupal/Tests/Core/Plugin/Discovery/HookDiscoveryTest.php @@ -1,6 +1,6 @@ moduleHandler->expects($this->at(1)) ->method('invoke') ->with('hook_discovery_test', 'test_plugin') - ->will($this->returnValue(hook_discovery_test_test_plugin())); + ->will($this->returnValue($this->hookDiscoveryTestTestPlugin())); $this->moduleHandler->expects($this->at(2)) ->method('invoke') ->with('hook_discovery_test2', 'test_plugin') - ->will($this->returnValue(hook_discovery_test2_test_plugin())); + ->will($this->returnValue($this->hookDiscoveryTest2TestPlugin())); $definitions = $this->hookDiscovery->getDefinitions(); @@ -94,8 +94,8 @@ public function testGetDefinition() { $this->moduleHandler->expects($this->any()) ->method('invoke') ->will($this->returnValueMap(array( - array('hook_discovery_test', 'test_plugin', array(), hook_discovery_test_test_plugin()), - array('hook_discovery_test2', 'test_plugin', array(), hook_discovery_test2_test_plugin()), + array('hook_discovery_test', 'test_plugin', array(), $this->hookDiscoveryTestTestPlugin()), + array('hook_discovery_test2', 'test_plugin', array(), $this->hookDiscoveryTest2TestPlugin()), ) )); @@ -129,20 +129,16 @@ public function testGetDefinitionWithUnknownID() { $this->hookDiscovery->getDefinition('test_non_existant', TRUE); } -} - -} - -namespace { - function hook_discovery_test_test_plugin() { + protected function hookDiscoveryTestTestPlugin() { return array( 'test_id_1' => array('class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Apple'), 'test_id_2' => array('class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Orange'), ); } - function hook_discovery_test2_test_plugin() { + protected function hookDiscoveryTest2TestPlugin() { return array( 'test_id_3' => array('class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry'), ); } + } diff --git a/core/tests/Drupal/Tests/Core/Render/Element/MachineNameTest.php b/core/tests/Drupal/Tests/Core/Render/Element/MachineNameTest.php index fe4406d..d5340cb 100644 --- a/core/tests/Drupal/Tests/Core/Render/Element/MachineNameTest.php +++ b/core/tests/Drupal/Tests/Core/Render/Element/MachineNameTest.php @@ -1,11 +1,11 @@ [ '#access_callback' => function () use (&$current_user_role) { - // Only role C can access this subtree. - return $current_user_role === 'C'; - }, + // Only role C can access this subtree. + return $current_user_role === 'C'; + }, '#cache' => [ 'contexts' => ['bar'], 'tags' => ['d'], diff --git a/core/tests/Drupal/Tests/Core/Routing/RedirectDestinationTest.php b/core/tests/Drupal/Tests/Core/Routing/RedirectDestinationTest.php index 3b4a975..fd99e99 100644 --- a/core/tests/Drupal/Tests/Core/Routing/RedirectDestinationTest.php +++ b/core/tests/Drupal/Tests/Core/Routing/RedirectDestinationTest.php @@ -101,7 +101,7 @@ public function providerGet() { // A request with a destination query. $data[] = [$request, '/example']; - // A request without a destination query, + // A request without a destination query, $request = Request::create('/'); $data[] = [$request, '/current-path']; diff --git a/core/tests/Drupal/Tests/Core/Session/PermissionsHashGeneratorTest.php b/core/tests/Drupal/Tests/Core/Session/PermissionsHashGeneratorTest.php index 3bb4f3a..d5e8c32 100644 --- a/core/tests/Drupal/Tests/Core/Session/PermissionsHashGeneratorTest.php +++ b/core/tests/Drupal/Tests/Core/Session/PermissionsHashGeneratorTest.php @@ -1,6 +1,6 @@ '')), - array(array('hash_salt' => NULL)), - ); + return array( + array(array()), + array(array('hash_salt' => '')), + array(array('hash_salt' => NULL)), + ); } /** diff --git a/core/tests/Drupal/Tests/Core/StackMiddleware/ReverseProxyMiddlewareTest.php b/core/tests/Drupal/Tests/Core/StackMiddleware/ReverseProxyMiddlewareTest.php index 9bbf2e1..585c4b9 100644 --- a/core/tests/Drupal/Tests/Core/StackMiddleware/ReverseProxyMiddlewareTest.php +++ b/core/tests/Drupal/Tests/Core/StackMiddleware/ReverseProxyMiddlewareTest.php @@ -49,9 +49,9 @@ public function testNoProxy() { * @dataProvider reverseProxyEnabledProvider */ public function testReverseProxyEnabled($provided_settings) { - // Enable reverse proxy and add test values. - $settings = new Settings(array('reverse_proxy' => 1) + $provided_settings); - $this->trustedHeadersAreSet($settings); + // Enable reverse proxy and add test values. + $settings = new Settings(array('reverse_proxy' => 1) + $provided_settings); + $this->trustedHeadersAreSet($settings); } /** diff --git a/core/tests/Drupal/Tests/Core/StringTranslation/TranslatableMarkupTest.php b/core/tests/Drupal/Tests/Core/StringTranslation/TranslatableMarkupTest.php index 183f570..3ed2b3d 100644 --- a/core/tests/Drupal/Tests/Core/StringTranslation/TranslatableMarkupTest.php +++ b/core/tests/Drupal/Tests/Core/StringTranslation/TranslatableMarkupTest.php @@ -68,8 +68,8 @@ public function testToString() { ->method('translateString') ->with($text) ->willReturnCallback(function () { - throw new \Exception('Yes you may.'); - }); + throw new \Exception('Yes you may.'); + }); // We set a custom error handler because of https://github.com/sebastianbergmann/phpunit/issues/487 set_error_handler([$this, 'errorHandler']); diff --git a/core/tests/Drupal/Tests/Core/UrlTest.php b/core/tests/Drupal/Tests/Core/UrlTest.php index b562745..5324c26 100644 --- a/core/tests/Drupal/Tests/Core/UrlTest.php +++ b/core/tests/Drupal/Tests/Core/UrlTest.php @@ -154,7 +154,7 @@ public function testUrlFromRequest() { return $urls; } - /** + /** * This constraint checks whether a Request object has the right path. * * @param string $path diff --git a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php index a123993..f4dc2df 100644 --- a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php +++ b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php @@ -1,6 +1,6 @@ setupRequestStack(FALSE); - $this->assertEquals($expected, $this->unroutedUrlAssembler->assemble($uri, $options)); - $generated_url = $this->unroutedUrlAssembler->assemble($uri, $options, TRUE); - $this->assertEquals($expected, $generated_url->getGeneratedUrl()); - $this->assertInstanceOf('\Drupal\Core\Render\BubbleableMetadata', $generated_url); + $this->setupRequestStack(FALSE); + $this->assertEquals($expected, $this->unroutedUrlAssembler->assemble($uri, $options)); + $generated_url = $this->unroutedUrlAssembler->assemble($uri, $options, TRUE); + $this->assertEquals($expected, $generated_url->getGeneratedUrl()); + $this->assertInstanceOf('\Drupal\Core\Render\BubbleableMetadata', $generated_url); } /** diff --git a/core/themes/bartik/css/components/comments.css b/core/themes/bartik/css/components/comments.css index aec8540..a80e049 100644 --- a/core/themes/bartik/css/components/comments.css +++ b/core/themes/bartik/css/components/comments.css @@ -105,9 +105,6 @@ margin-bottom: 0.45em; font-size: 1.171em; } -.comment__content nav { - padding-top: 1px; -} .indented { margin-left: 40px; /* LTR */ } diff --git a/core/themes/bartik/templates/comment.html.twig b/core/themes/bartik/templates/comment.html.twig index 16cd5ba..d8f54cb 100644 --- a/core/themes/bartik/templates/comment.html.twig +++ b/core/themes/bartik/templates/comment.html.twig @@ -103,9 +103,6 @@ {{ title }} {{ title_suffix }} {% endif %} - {{ content|without('links') }} - {% if content.links %} - - {% endif %} + {{ content }}