From 09b113657a036a7c9176841fafe48d16795deb0f Mon Sep 17 00:00:00 2001 From: Pantheon Automation Date: Wed, 7 Sep 2016 13:26:21 -0700 Subject: [PATCH] Update to Drupal 8.1.9. For more information, see https://www.drupal.org/project/drupal/releases/8.1.9 --- core/INSTALL.txt | 13 ++ core/MAINTAINERS.txt | 10 +- core/core.api.php | 33 ++-- core/includes/file.inc | 4 +- core/includes/install.core.inc | 4 +- core/includes/update.inc | 2 +- core/lib/Drupal.php | 2 +- .../Core/Database/Driver/pgsql/Schema.php | 2 +- .../Core/Datetime/FormattedDateDiff.php | 15 +- .../Entity/Element/EntityAutocomplete.php | 2 +- .../Drupal/Core/Entity/EntityAccessCheck.php | 17 +- .../Core/Entity/Query/QueryInterface.php | 1 - .../Entity/Sql/SqlContentEntityStorage.php | 9 + core/lib/Drupal/Core/Entity/entity.api.php | 20 +- core/lib/Drupal/Core/File/file.api.php | 6 - core/lib/Drupal/Core/Form/ConfigFormBase.php | 2 +- core/lib/Drupal/Core/Utility/ProjectInfo.php | 2 +- core/misc/dialog/dialog.ajax.js | 16 +- core/modules/action/src/ActionListBuilder.php | 4 +- .../action/src/Tests/ActionListTest.php | 37 ++++ .../block_content/src/Entity/BlockContent.php | 18 +- .../src/Plugin/Derivative/BlockContent.php | 2 + .../src/Kernel/BlockContentDeletionTest.php | 63 ++++++ .../d6/CommentVariablePerCommentType.php | 2 +- .../Kernel/Migrate/d7/MigrateCommentTest.php | 2 +- .../d6/CommentVariablePerCommentTypeTest.php | 4 +- .../config/src/StorageReplaceDataWrapper.php | 9 +- .../Tests/ConfigSingleImportExportTest.php | 5 +- .../src/Plugin/migrate/process/FieldType.php | 11 +- .../EntityReferenceAdminTest.php | 10 + .../field/tests/src/Kernel/BulkDeleteTest.php | 92 +++++++++ .../Views/EntityReferenceRelationshipTest.php | 53 +++++- .../Migrate/d7/RollbackFieldInstanceTest.php | 80 ++++++++ .../Kernel/Migrate/d7/RollbackFieldTest.php | 77 ++++++++ .../Migrate/d7/RollbackViewModesTest.php | 52 +++++ .../Field/FieldType/FileFieldItemList.php | 6 +- .../file/tests/src/Kernel/UsageTest.php | 60 ++++++ .../migration_templates/d6_filter_format.yml | 9 +- .../migration_templates/d7_filter_format.yml | 11 +- .../src/Plugin/migrate/process/FilterID.php | 90 +++++++++ .../Migrate/d6/MigrateFilterFormatTest.php | 19 +- .../Migrate/d7/MigrateFilterFormatTest.php | 20 +- .../Plugin/migrate/process/FilterIdTest.php | 119 ++++++++++++ .../ImageStyleDownloadController.php | 11 +- .../image/src/ImageStyleListBuilder.php | 41 +--- .../src/Tests/ImageStylesPathAndUrlTest.php | 11 ++ .../src/Plugin/Field/FieldType/LinkItem.php | 2 +- .../link/tests/src/Kernel/LinkItemTest.php | 6 + .../migrate/src/Event/MigrateEvents.php | 2 +- .../modules/migrate/src/MigrateExecutable.php | 6 + .../src/Plugin/MigrateIdMapInterface.php | 8 + .../src/Plugin/MigratePluginManager.php | 4 +- .../Plugin/MigratePluginManagerInterface.php | 29 +++ core/modules/migrate/src/Plugin/Migration.php | 8 +- .../destination/EntityFieldInstance.php | 8 + .../destination/EntityFieldStorageConfig.php | 8 + .../migrate/destination/EntityViewMode.php | 8 + .../migrate/src/Plugin/migrate/id_map/Sql.php | 20 +- .../src/Plugin/migrate/process/Callback.php | 2 + .../src/Plugin/migrate/process/Concat.php | 2 + .../src/Plugin/migrate/process/DedupeBase.php | 2 + .../Plugin/migrate/process/DedupeEntity.php | 2 + .../Plugin/migrate/process/DefaultValue.php | 2 + .../src/Plugin/migrate/process/Explode.php | 2 + .../src/Plugin/migrate/process/Extract.php | 2 +- .../src/Plugin/migrate/process/Flatten.php | 2 +- .../src/Plugin/migrate/process/Get.php | 2 + .../src/Plugin/migrate/process/Iterator.php | 2 +- .../Plugin/migrate/process/MachineName.php | 2 + .../src/Plugin/migrate/process/Migration.php | 6 +- .../src/Plugin/migrate/process/Route.php | 5 +- .../Plugin/migrate/process/SkipOnEmpty.php | 2 + .../migrate/process/SkipRowIfNotSet.php | 2 + .../src/Plugin/migrate/process/StaticMap.php | 2 +- .../src/Plugin/migrate/process/Substr.php | 2 + .../tests/src/Kernel/MigrateRollbackTest.php | 3 + .../tests/src/Unit/MigrateSqlIdMapTest.php | 29 +++ .../Plugin/MigrateCckFieldPluginManager.php | 6 +- .../MigrateCckFieldPluginManagerInterface.php | 28 +++ .../src/Plugin/migrate/CckMigration.php | 27 ++- .../migrate_drupal/tests/fixtures/drupal7.php | 180 +++++++++++++++++- .../MigrateCckFieldPluginManagerTest.php | 23 +-- .../src/Tests/d7/MigrateUpgrade7Test.php | 4 +- core/modules/node/node.module | 2 +- .../node/src/Controller/NodeController.php | 2 +- .../src/Controller/NodeViewController.php | 73 ++++++- .../node/src/Plugin/migrate/D6NodeDeriver.php | 16 +- .../node/src/Plugin/migrate/D7NodeDeriver.php | 16 +- .../Tests/NodeAccessRebuildNodeGrantsTest.php | 58 +++++- .../node/src/Tests/NodeAccessRebuildTest.php | 36 ---- .../node/src/Tests/NodeCacheTagsTest.php | 10 + core/modules/node/src/Tests/NodeViewTest.php | 35 ++++ .../src/Kernel/Migrate/d7/MigrateNodeTest.php | 2 +- core/modules/simpletest/simpletest.api.php | 2 +- .../src/Functional/BrowserTestBaseTest.php | 5 + .../Entity/EntityWithUriCacheTagsTestBase.php | 12 +- ...ConfigDirectorySetNoDirectoryErrorTest.php | 62 ++++++ ...allerConfigDirectorySetNoDirectoryTest.php | 49 +++++ core/modules/system/system.install | 12 +- ...entity_reference_entity_test_view_long.yml | 121 ++++++++++++ .../Migrate/d7/MigrateTrackerNodeTest.php | 2 +- .../Migrate/d7/MigrateTrackerUserTest.php | 2 +- core/modules/update/update.api.php | 2 +- .../user/src/Controller/UserController.php | 176 +++++++++++++---- .../user/src/Form/UserPasswordResetForm.php | 61 +----- .../Plugin/migrate/destination/EntityUser.php | 2 - .../user/src/Tests/UserMailNotifyTest.php | 76 ++++++++ .../user/src/Tests/UserPasswordResetTest.php | 64 +++++++ core/modules/user/src/UserServiceProvider.php | 5 +- .../Kernel/Migrate/d6/MigrateUserRoleTest.php | 28 +-- core/modules/user/user.api.php | 2 +- core/modules/user/user.module | 4 +- core/modules/user/user.routing.yml | 22 +++ .../views/src/Tests/FieldApiDataTest.php | 2 + core/modules/views/views.api.php | 6 +- core/modules/views/views.views.inc | 4 + core/phpcs.xml.dist | 2 + core/phpunit.xml.dist | 18 +- .../Storage/StorageReplaceDataWrapperTest.php | 93 +++++++++ .../Core/Theme/StableTemplateOverrideTest.php | 6 +- core/tests/Drupal/Tests/BrowserTestBase.php | 2 +- .../Drupal/Tests/Core/Datetime/DateTest.php | 33 ++++ core/tests/Drupal/Tests/UnitTestCase.php | 4 +- core/tests/README.md | 33 ++++ core/tests/bootstrap.php | 2 + 125 files changed, 2307 insertions(+), 385 deletions(-) create mode 100644 core/modules/action/src/Tests/ActionListTest.php create mode 100644 core/modules/block_content/tests/src/Kernel/BlockContentDeletionTest.php create mode 100644 core/modules/field/tests/src/Kernel/Migrate/d7/RollbackFieldInstanceTest.php create mode 100644 core/modules/field/tests/src/Kernel/Migrate/d7/RollbackFieldTest.php create mode 100644 core/modules/field/tests/src/Kernel/Migrate/d7/RollbackViewModesTest.php create mode 100644 core/modules/filter/src/Plugin/migrate/process/FilterID.php create mode 100644 core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterIdTest.php create mode 100644 core/modules/migrate/src/Plugin/MigratePluginManagerInterface.php create mode 100644 core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManagerInterface.php delete mode 100644 core/modules/node/src/Tests/NodeAccessRebuildTest.php create mode 100644 core/modules/system/src/Tests/Installer/InstallerConfigDirectorySetNoDirectoryErrorTest.php create mode 100644 core/modules/system/src/Tests/Installer/InstallerConfigDirectorySetNoDirectoryTest.php create mode 100644 core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_entity_test_view_long.yml create mode 100644 core/modules/user/src/Tests/UserMailNotifyTest.php create mode 100644 core/tests/Drupal/KernelTests/Core/Config/Storage/StorageReplaceDataWrapperTest.php diff --git a/core/INSTALL.txt b/core/INSTALL.txt index 8c5ee8723..c83976126 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 60aefd152..e884c10c5 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 @@ -472,11 +473,13 @@ Views module - Tim Plunkett 'tim.plunkett' https://www.drupal.org/u/tim.plunkett - Damian Lee 'damiankloip' https://www.drupal.org/u/damiankloip - Jess Myrbo 'xjm' https://www.drupal.org/u/xjm +- Len Swaneveld 'Lendude' https://www.drupal.org/u/lendude Views UI module - Daniel Wehner 'dawehner' https://www.drupal.org/u/dawehner - Tim Plunkett 'tim.plunkett' https://www.drupal.org/u/tim.plunkett - Damian Lee 'damiankloip' https://www.drupal.org/u/damiankloip +- Len Swaneveld 'Lendude' https://www.drupal.org/u/lendude Theme maintainers @@ -523,6 +526,9 @@ Multi-lingual Web services - Larry Garfield 'Crell' https://www.drupal.org/u/crell +Workflow Initiative +- Dick Olsson 'dixon_' https://www.drupal.org/u/dixon_ + Provisional membership: None at this time. diff --git a/core/core.api.php b/core/core.api.php index 4b2fe62fd..7b90ae21f 100644 --- a/core/core.api.php +++ b/core/core.api.php @@ -1894,21 +1894,32 @@ function hook_cron() { // Short-running operation example, not using a queue: // Delete all expired records since the last cron run. - $expires = \Drupal::state()->get('mymodule.cron_last_run', REQUEST_TIME); - db_delete('mymodule_table') + $expires = \Drupal::state()->get('mymodule.last_check', 0); + \Drupal::database()->delete('mymodule_table') ->condition('expires', $expires, '>=') ->execute(); - \Drupal::state()->set('mymodule.cron_last_run', REQUEST_TIME); + \Drupal::state()->set('mymodule.last_check', REQUEST_TIME); // Long-running operation example, leveraging a queue: - // Fetch feeds from other sites. - $result = db_query('SELECT * FROM {aggregator_feed} WHERE checked + refresh < :time AND refresh <> :never', array( - ':time' => REQUEST_TIME, - ':never' => AGGREGATOR_CLEAR_NEVER, - )); + // Queue news feeds for updates once their refresh interval has elapsed. $queue = \Drupal::queue('aggregator_feeds'); - foreach ($result as $feed) { - $queue->createItem($feed); + $ids = \Drupal::entityManager()->getStorage('aggregator_feed')->getFeedIdsToRefresh(); + foreach (Feed::loadMultiple($ids) as $feed) { + if ($queue->createItem($feed)) { + // Add timestamp to avoid queueing item more than once. + $feed->setQueuedTime(REQUEST_TIME); + $feed->save(); + } + } + $ids = \Drupal::entityQuery('aggregator_feed') + ->condition('queued', REQUEST_TIME - (3600 * 6), '<') + ->execute(); + if ($ids) { + $feeds = Feed::loadMultiple($ids); + foreach ($feeds as $feed) { + $feed->setQueuedTime(0); + $feed->save(); + } } } @@ -2036,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/file.inc b/core/includes/file.inc index 017a3d650..a58e6dcd7 100644 --- a/core/includes/file.inc +++ b/core/includes/file.inc @@ -1220,7 +1220,9 @@ function file_directory_os_temp() { foreach ($directories as $directory) { if (is_dir($directory) && is_writable($directory)) { - return $directory; + // Both sys_get_temp_dir() and ini_get('upload_tmp_dir') can return paths + // with a trailing directory separator. + return rtrim($directory, DIRECTORY_SEPARATOR); } } return FALSE; diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index d75ed9f40..11bfbb3e6 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -369,7 +369,9 @@ function install_begin_request($class_loader, &$install_state) { $install_state['config_verified'] = FALSE; } $install_state['database_verified'] = install_verify_database_settings($site_path); - $install_state['settings_verified'] = $install_state['config_verified'] && $install_state['database_verified']; + // A valid settings.php has database settings and a hash_salt value. Other + // settings like config_directories will be checked by system_requirements(). + $install_state['settings_verified'] = $install_state['database_verified'] && (bool) Settings::get('hash_salt', FALSE); // Install factory tables only after checking the database. if ($install_state['database_verified'] && $install_state['database_ready']) { diff --git a/core/includes/update.inc b/core/includes/update.inc index 2c020db6b..ea126f864 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() { diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index 0e82afad8..6539eb6d9 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -81,7 +81,7 @@ class Drupal { /** * The current system version. */ - const VERSION = '8.1.8'; + const VERSION = '8.1.9'; /** * Core API compatibility. diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php index 4cd660b4b..972a6e4cf 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/Datetime/FormattedDateDiff.php b/core/lib/Drupal/Core/Datetime/FormattedDateDiff.php index 07bb83638..266022be8 100644 --- a/core/lib/Drupal/Core/Datetime/FormattedDateDiff.php +++ b/core/lib/Drupal/Core/Datetime/FormattedDateDiff.php @@ -51,10 +51,23 @@ class FormattedDateDiff implements RenderableInterface, CacheableDependencyInter } /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return $this->maxAge; + } + + /** + * The maximum age for which this object may be cached. + * * @return int + * The maximum time in seconds that this object may be cached. + * + * @deprecated in Drupal 8.1.9 and will be removed before Drupal 9.0.0. Use + * \Drupal\Core\Datetime\FormattedDateDiff::getCacheMaxAge() instead. */ public function getMaxAge() { - return $this->maxAge; + return $this->getCacheMaxAge(); } /** diff --git a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php index 23fd0b50a..51adf91b7 100644 --- a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php +++ b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php @@ -297,7 +297,7 @@ class EntityAutocomplete extends Textfield { $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/EntityAccessCheck.php b/core/lib/Drupal/Core/Entity/EntityAccessCheck.php index 0de9b0141..bc8bd5973 100644 --- a/core/lib/Drupal/Core/Entity/EntityAccessCheck.php +++ b/core/lib/Drupal/Core/Entity/EntityAccessCheck.php @@ -17,13 +17,26 @@ class EntityAccessCheck implements AccessInterface { * Checks access to the entity operation on the given route. * * The value of the '_entity_access' key must be in the pattern - * 'entity_type.operation.' The entity type must match the {entity_type} - * parameter in the route pattern. This will check a node for 'update' access: + * 'entity_slug_name.operation.' For example, this will check a node for + * 'update' access: * @code * pattern: '/foo/{node}/bar' * requirements: * _entity_access: 'node.update' * @endcode + * And this will check a dynamic entity type: + * @code + * example.route: + * path: foo/{entity_type}/{example} + * requirements: + * _entity_access: example.update + * options: + * parameters: + * example: + * type: entity:{entity_type} + * @endcode + * @see \Drupal\Core\ParamConverter\EntityConverter + * * Available operations are 'view', 'update', 'create', and 'delete'. * * @param \Symfony\Component\Routing\Route $route diff --git a/core/lib/Drupal/Core/Entity/Query/QueryInterface.php b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php index 17b266ffc..630d17673 100644 --- a/core/lib/Drupal/Core/Entity/Query/QueryInterface.php +++ b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php @@ -31,7 +31,6 @@ interface QueryInterface extends AlterableInterface { * ->condition('greetings', 'merhaba', '=', 'tr') * ->condition('greetings.value', 'siema', '=', 'pl') * ->execute(); - * $entity_ids = $query->execute(); * @endcode * * @param $field diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php index e115bd211..b2be1c0c4 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php @@ -1543,6 +1543,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt $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 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt $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 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt * 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 c61c2368e..36873845c 100644 --- a/core/lib/Drupal/Core/Entity/entity.api.php +++ b/core/lib/Drupal/Core/Entity/entity.api.php @@ -1252,21 +1252,6 @@ function hook_ENTITY_TYPE_revision_delete(Drupal\Core\Entity\EntityInterface $en } } -/** - * Alter or execute an Drupal\Core\Entity\Query\EntityQueryInterface. - * - * @param \Drupal\Core\Entity\Query\QueryInterface $query - * Note the $query->altered attribute which is TRUE in case the query has - * already been altered once. This happens with cloned queries. - * If there is a pager, then such a cloned query will be executed to count - * all elements. This query can be detected by checking for - * ($query->pager && $query->count), allowing the driver to return 0 from - * the count query and disable the pager. - */ -function hook_entity_query_alter(\Drupal\Core\Entity\Query\QueryInterface $query) { - // @todo: code example. -} - /** * Act on entities being assembled before rendering. * @@ -1860,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 @@ -1871,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/File/file.api.php b/core/lib/Drupal/Core/File/file.api.php index 43913fbd0..c26ac2be3 100644 --- a/core/lib/Drupal/Core/File/file.api.php +++ b/core/lib/Drupal/Core/File/file.api.php @@ -157,12 +157,6 @@ function hook_archiver_info_alter(&$info) { * will always be passed the full path to the root of the site that should * be used to restrict where file transfer operations can occur (the $jail) * and an array of settings values returned by the settings form. - * - 'file': Required. The include file containing the FileTransfer class. - * This should be a separate .inc file, not just the .module file, so that - * the minimum possible code is loaded when authorize.php is running. - * - 'file path': Optional. The directory (relative to the Drupal root) - * where the include file lives. If not defined, defaults to the base - * directory of the module implementing the hook. * - 'weight': Optional. Integer weight used for sorting connection types on * the authorize.php form. * diff --git a/core/lib/Drupal/Core/Form/ConfigFormBase.php b/core/lib/Drupal/Core/Form/ConfigFormBase.php index c2f83fadb..fb508f854 100644 --- a/core/lib/Drupal/Core/Form/ConfigFormBase.php +++ b/core/lib/Drupal/Core/Form/ConfigFormBase.php @@ -41,7 +41,7 @@ abstract class ConfigFormBase extends FormBase { '#button_type' => 'primary', ); - // By default, render the form using theme_system_config_form(). + // By default, render the form using system-config-form.html.twig. $form['#theme'] = 'system_config_form'; return $form; diff --git a/core/lib/Drupal/Core/Utility/ProjectInfo.php b/core/lib/Drupal/Core/Utility/ProjectInfo.php index 1eed1fe9a..24043d3b4 100644 --- a/core/lib/Drupal/Core/Utility/ProjectInfo.php +++ b/core/lib/Drupal/Core/Utility/ProjectInfo.php @@ -172,7 +172,7 @@ class ProjectInfo { * @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/misc/dialog/dialog.ajax.js b/core/misc/dialog/dialog.ajax.js index 16f72f00a..3f1b0c2ef 100644 --- a/core/misc/dialog/dialog.ajax.js +++ b/core/misc/dialog/dialog.ajax.js @@ -62,7 +62,7 @@ */ prepareDialogButtons: function ($dialog) { var buttons = []; - var $buttons = $dialog.find('.form-actions input[type=submit]'); + var $buttons = $dialog.find('.form-actions input[type=submit], .form-actions a.button'); $buttons.each(function () { // Hidden form buttons need special attention. For browser consistency, // the button needs to be "visible" in order to have the enter key fire @@ -74,14 +74,22 @@ width: 0, height: 0, padding: 0, - border: 0 + border: 0, + overflow: 'hidden' }); buttons.push({ text: $originalButton.html() || $originalButton.attr('value'), class: $originalButton.attr('class'), click: function (e) { - $originalButton.trigger('mousedown').trigger('mouseup').trigger('click'); - e.preventDefault(); + // If the original button is an anchor tag, triggering the "click" + // event will not simulate a click. Use the click method instead. + if ($originalButton.is('a')) { + $originalButton[0].click(); + } + else { + $originalButton.trigger('mousedown').trigger('mouseup').trigger('click'); + e.preventDefault(); + } } }); }); diff --git a/core/modules/action/src/ActionListBuilder.php b/core/modules/action/src/ActionListBuilder.php index 09024037b..0a54cd652 100644 --- a/core/modules/action/src/ActionListBuilder.php +++ b/core/modules/action/src/ActionListBuilder.php @@ -108,10 +108,10 @@ class ActionListBuilder extends ConfigEntityListBuilder { * {@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/Tests/ActionListTest.php b/core/modules/action/src/Tests/ActionListTest.php new file mode 100644 index 000000000..f533cdf1b --- /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/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php index 958fc7ac8..425117d83 100644 --- a/core/modules/block_content/src/Entity/BlockContent.php +++ b/core/modules/block_content/src/Entity/BlockContent.php @@ -104,9 +104,15 @@ class BlockContent extends ContentEntityBase implements BlockContentInterface { */ 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(); } /** @@ -237,4 +243,12 @@ class BlockContent extends ContentEntityBase implements BlockContentInterface { 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 63b68c07d..ab564451c 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 @@ class BlockContent extends DeriverBase implements ContainerDeriverInterface { */ 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 000000000..e54b2d3d4 --- /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/comment/src/Plugin/migrate/source/d6/CommentVariablePerCommentType.php b/core/modules/comment/src/Plugin/migrate/source/d6/CommentVariablePerCommentType.php index ea987be92..5d16ed029 100644 --- a/core/modules/comment/src/Plugin/migrate/source/d6/CommentVariablePerCommentType.php +++ b/core/modules/comment/src/Plugin/migrate/source/d6/CommentVariablePerCommentType.php @@ -20,7 +20,7 @@ class CommentVariablePerCommentType extends CommentVariable { $return = array(); foreach ($node_types as $node_type => $data) { // Only 2 comment types depending on subject field visibility. - if (empty($data['comment_subject_field'])) { + if (!empty($data['comment_subject_field'])) { // Default label and description should be set in migration. $return['comment'] = array( 'comment_type' => 'comment', 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 ed1917838..4d4df032e 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 @@ class MigrateCommentTest extends MigrateDrupal7TestBase { ), )); $this->executeMigrations([ - 'd7_node:test_content_type', + 'd7_node', 'd7_comment_type', 'd7_comment', ]); diff --git a/core/modules/comment/tests/src/Unit/Migrate/d6/CommentVariablePerCommentTypeTest.php b/core/modules/comment/tests/src/Unit/Migrate/d6/CommentVariablePerCommentTypeTest.php index 0055a2ca8..75d0932a9 100644 --- a/core/modules/comment/tests/src/Unit/Migrate/d6/CommentVariablePerCommentTypeTest.php +++ b/core/modules/comment/tests/src/Unit/Migrate/d6/CommentVariablePerCommentTypeTest.php @@ -24,10 +24,10 @@ class CommentVariablePerCommentTypeTest extends MigrateSqlSourceTestCase { // Each result will also include a label and description, but those are // static values set by the source plugin and don't need to be asserted. array( - 'comment_type' => 'comment_no_subject', + 'comment_type' => 'comment', ), array( - 'comment_type' => 'comment', + 'comment_type' => 'comment_no_subject', ), ); diff --git a/core/modules/config/src/StorageReplaceDataWrapper.php b/core/modules/config/src/StorageReplaceDataWrapper.php index 58b3b492b..91c3a7cfc 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 @@ class StorageReplaceDataWrapper implements StorageInterface { $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 @@ class StorageReplaceDataWrapper implements StorageInterface { * {@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 876cbfdd4..7c4bc84cc 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/field/src/Plugin/migrate/process/FieldType.php b/core/modules/field/src/Plugin/migrate/process/FieldType.php index 129694006..7874f9c3b 100644 --- a/core/modules/field/src/Plugin/migrate/process/FieldType.php +++ b/core/modules/field/src/Plugin/migrate/process/FieldType.php @@ -3,12 +3,12 @@ namespace Drupal\field\Plugin\migrate\process; use Drupal\Component\Plugin\Exception\PluginNotFoundException; -use Drupal\Component\Plugin\PluginManagerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\migrate\MigrateExecutableInterface; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\Plugin\migrate\process\StaticMap; use Drupal\migrate\Row; +use Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -21,7 +21,7 @@ class FieldType extends StaticMap implements ContainerFactoryPluginInterface { /** * The cckfield plugin manager. * - * @var \Drupal\Component\Plugin\PluginManagerInterface + * @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface */ protected $cckPluginManager; @@ -41,12 +41,12 @@ class FieldType extends StaticMap implements ContainerFactoryPluginInterface { * The plugin ID. * @param mixed $plugin_definition * The plugin definition. - * @param \Drupal\Component\Plugin\PluginManagerInterface $cck_plugin_manager + * @param \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface $cck_plugin_manager * The cckfield plugin manager. * @param \Drupal\migrate\Plugin\MigrationInterface $migration * The migration being run. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, PluginManagerInterface $cck_plugin_manager, MigrationInterface $migration = NULL) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrateCckFieldPluginManagerInterface $cck_plugin_manager, MigrationInterface $migration = NULL) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->cckPluginManager = $cck_plugin_manager; $this->migration = $migration; @@ -72,7 +72,8 @@ class FieldType extends StaticMap implements ContainerFactoryPluginInterface { $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/Tests/EntityReference/EntityReferenceAdminTest.php b/core/modules/field/src/Tests/EntityReference/EntityReferenceAdminTest.php index d781084b1..4e8be4b34 100644 --- a/core/modules/field/src/Tests/EntityReference/EntityReferenceAdminTest.php +++ b/core/modules/field/src/Tests/EntityReference/EntityReferenceAdminTest.php @@ -208,6 +208,7 @@ class EntityReferenceAdminTest extends WebTestBase { '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 @@ class EntityReferenceAdminTest extends WebTestBase { '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 @@ class EntityReferenceAdminTest extends WebTestBase { $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/tests/src/Kernel/BulkDeleteTest.php b/core/modules/field/tests/src/Kernel/BulkDeleteTest.php index 41fedfc7f..ae3d4e1c3 100644 --- a/core/modules/field/tests/src/Kernel/BulkDeleteTest.php +++ b/core/modules/field/tests/src/Kernel/BulkDeleteTest.php @@ -210,6 +210,98 @@ class BulkDeleteTest extends FieldKernelTestBase { $this->assertFalse(array_diff($found, array_keys($this->entities))); } + /** + * 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/EntityReference/Views/EntityReferenceRelationshipTest.php b/core/modules/field/tests/src/Kernel/EntityReference/Views/EntityReferenceRelationshipTest.php index 47822d2d3..5dc096f65 100644 --- a/core/modules/field/tests/src/Kernel/EntityReference/Views/EntityReferenceRelationshipTest.php +++ b/core/modules/field/tests/src/Kernel/EntityReference/Views/EntityReferenceRelationshipTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\field\Kernel\EntityReference\Views; +use Drupal\entity_test\Entity\EntityTestMulChanged; use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait; use Drupal\entity_test\Entity\EntityTest; use Drupal\entity_test\Entity\EntityTestMul; @@ -27,6 +28,7 @@ class EntityReferenceRelationshipTest extends ViewsKernelTestBase { */ public static $testViews = array( 'test_entity_reference_entity_test_view', + 'test_entity_reference_entity_test_view_long', 'test_entity_reference_reverse_entity_test_view', 'test_entity_reference_entity_test_mul_view', 'test_entity_reference_reverse_entity_test_mul_view', @@ -55,6 +57,7 @@ class EntityReferenceRelationshipTest extends ViewsKernelTestBase { $this->installEntitySchema('user'); $this->installEntitySchema('entity_test'); $this->installEntitySchema('entity_test_mul'); + $this->installEntitySchema('entity_test_mul_changed'); // Create reference from entity_test to entity_test_mul. $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_data', 'field_test_data', 'entity_test_mul'); @@ -62,6 +65,12 @@ class EntityReferenceRelationshipTest extends ViewsKernelTestBase { // Create reference from entity_test_mul to entity_test. $this->createEntityReferenceField('entity_test_mul', 'entity_test_mul', 'field_data_test', 'field_data_test', 'entity_test'); + // Create another field for testing with a long name. So it's storage name + // will become hashed. Use entity_test_mul_changed, so the resulting field + // tables created will be greater than 48 chars long. + // @see \Drupal\Core\Entity\Sql\DefaultTableMapping::generateFieldTableName() + $this->createEntityReferenceField('entity_test_mul_changed', 'entity_test_mul_changed', 'field_test_data_with_a_long_name', 'field_test_data_with_a_long_name', 'entity_test'); + ViewTestData::createTestViews(get_class($this), array('entity_reference_test_views')); } @@ -124,7 +133,6 @@ class EntityReferenceRelationshipTest extends ViewsKernelTestBase { // Test that the correct relationship entity is on the row. $this->assertEqual($row->_relationship_entities['field_test_data']->id(), 1); $this->assertEqual($row->_relationship_entities['field_test_data']->bundle(), 'entity_test_mul'); - } // Check the backwards reference view. @@ -225,4 +233,47 @@ class EntityReferenceRelationshipTest extends ViewsKernelTestBase { } } + /** + * Tests views data generated for relationship. + * + * @see entity_reference_field_views_data() + */ + public function testDataTableRelationshipWithLongFieldName() { + // Create some test entities which link each other. + $referenced_entity = EntityTest::create(); + $referenced_entity->save(); + + $entity = EntityTestMulChanged::create(); + $entity->field_test_data_with_a_long_name->target_id = $referenced_entity->id(); + $entity->save(); + $this->entities[] = $entity; + + $entity = EntityTestMulChanged::create(); + $entity->field_test_data_with_a_long_name->target_id = $referenced_entity->id(); + $entity->save(); + $this->entities[] = $entity; + + Views::viewsData()->clear(); + + // Check an actual test view. + $view = Views::getView('test_entity_reference_entity_test_view_long'); + $this->executeView($view); + /** @var \Drupal\views\ResultRow $row */ + foreach ($view->result as $index => $row) { + // Check that the actual ID of the entity is the expected one. + $this->assertEqual($row->id, $this->entities[$index]->id()); + + // Also check that we have the correct result entity. + $this->assertEqual($row->_entity->id(), $this->entities[$index]->id()); + + // Test the forward relationship. + //$this->assertEqual($row->entity_test_entity_test_mul__field_data_test_id, 1); + + // Test that the correct relationship entity is on the row. + $this->assertEqual($row->_relationship_entities['field_test_data_with_a_long_name']->id(), 1); + $this->assertEqual($row->_relationship_entities['field_test_data_with_a_long_name']->bundle(), 'entity_test'); + + } + } + } 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 000000000..e71ac3ce9 --- /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 000000000..7c5c6fd41 --- /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 000000000..b069387cf --- /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/file/src/Plugin/Field/FieldType/FileFieldItemList.php b/core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php index 3d064afcf..20f479750 100644 --- a/core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php +++ b/core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php @@ -81,9 +81,11 @@ class FileFieldItemList extends EntityReferenceFieldItemList { parent::delete(); $entity = $this->getEntity(); - // Delete all file usages within this entity. + // If a translation is deleted only decrement the file usage by one. If the + // default translation is deleted remove all file usages within this entity. + $count = $entity->isDefaultTranslation() ? 0 : 1; foreach ($this->referencedEntities() as $file) { - \Drupal::service('file.usage')->delete($file, 'file', $entity->getEntityTypeId(), $entity->id(), 0); + \Drupal::service('file.usage')->delete($file, 'file', $entity->getEntityTypeId(), $entity->id(), $count); } } diff --git a/core/modules/file/tests/src/Kernel/UsageTest.php b/core/modules/file/tests/src/Kernel/UsageTest.php index fdf12311b..8e2601510 100644 --- a/core/modules/file/tests/src/Kernel/UsageTest.php +++ b/core/modules/file/tests/src/Kernel/UsageTest.php @@ -2,6 +2,13 @@ namespace Drupal\Tests\file\Kernel; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\language\Entity\ConfigurableLanguage; +use Drupal\language\Entity\ContentLanguageSettings; +use Drupal\node\Entity\Node; +use Drupal\node\Entity\NodeType; + /** * Tests file usage functions. * @@ -203,4 +210,57 @@ class UsageTest extends FileManagedUnitTestBase { $this->assertTrue(file_exists($perm_new->getFileUri()), 'New permanent file was correctly ignored.'); } + /** + * Tests file usage with translated entities. + */ + public function testFileUsageWithEntityTranslation() { + /** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */ + $file_usage = $this->container->get('file.usage'); + + $this->enableModules(['node', 'language']); + $this->installEntitySchema('node'); + $this->installSchema('node', ['node_access']); + + // Activate English and Romanian languages. + ConfigurableLanguage::create(['id' => 'en'])->save(); + ConfigurableLanguage::create(['id' => 'ro'])->save(); + + NodeType::create(['type' => 'page'])->save(); + ContentLanguageSettings::loadByEntityTypeBundle('node', 'page') + ->setLanguageAlterable(FALSE) + ->setDefaultLangcode('en') + ->save(); + // Create a file field attached to 'page' node-type. + FieldStorageConfig::create([ + 'type' => 'file', + 'entity_type' => 'node', + 'field_name' => 'file', + ])->save(); + FieldConfig::create([ + 'entity_type' => 'node', + 'bundle' => 'page', + 'field_name' => 'file', + 'label' => 'File', + ])->save(); + + // Create a node, attach a file and add a Romanian translation. + $node = Node::create(['type' => 'page', 'title' => 'Page']); + $node + ->set('file', $file = $this->createFile()) + ->addTranslation('ro', $node->getTranslation('en')->toArray()) + ->save(); + + // Check that the file is used twice. + $usage = $file_usage->listUsage($file); + $this->assertEquals(2, $usage['file']['node'][$node->id()]); + + // Remove the Romanian translation. + $node->removeTranslation('ro'); + $node->save(); + + // Check that one usage has been removed and is used only once now. + $usage = $file_usage->listUsage($file); + $this->assertEquals(1, $usage['file']['node'][$node->id()]); + } + } diff --git a/core/modules/filter/migration_templates/d6_filter_format.yml b/core/modules/filter/migration_templates/d6_filter_format.yml index 6b767f208..a5f438862 100644 --- a/core/modules/filter/migration_templates/d6_filter_format.yml +++ b/core/modules/filter/migration_templates/d6_filter_format.yml @@ -16,11 +16,16 @@ process: key: '@id' process: id: - plugin: static_map - default_value: filter_null + # If the filter ID cannot be mapped, it will be passed through + # unchanged because the bypass flag is set. The filter_id plugin + # will flatten the input value and default it to filter_null (the + # fallback filter plugin ID) if the flattened input value is not + # a valid plugin ID. + plugin: filter_id source: - module - delta + bypass: true map: filter: - filter_html diff --git a/core/modules/filter/migration_templates/d7_filter_format.yml b/core/modules/filter/migration_templates/d7_filter_format.yml index 2b44a80c0..c0710b5f0 100644 --- a/core/modules/filter/migration_templates/d7_filter_format.yml +++ b/core/modules/filter/migration_templates/d7_filter_format.yml @@ -15,11 +15,16 @@ process: key: '@id' process: id: - plugin: static_map + # If the filter ID cannot be mapped, it will pass through unmodified + # because the bypass flag is set. When the user actually tries to + # view text through an invalid filter plugin, the filter system will + # fall back to filter_null and display a helpful error message. + plugin: filter_id bypass: true source: name - map: - php_code: filter_null + # No need to map anything -- filter plugin IDs haven't changed since + # Drupal 7. + map: { } settings: plugin: filter_settings source: settings diff --git a/core/modules/filter/src/Plugin/migrate/process/FilterID.php b/core/modules/filter/src/Plugin/migrate/process/FilterID.php new file mode 100644 index 000000000..26569860f --- /dev/null +++ b/core/modules/filter/src/Plugin/migrate/process/FilterID.php @@ -0,0 +1,90 @@ +filterManager = $filter_manager; + $this->stringTranslation = $translator; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('plugin.manager.filter'), + $container->get('string_translation') + ); + } + + /** + * {@inheritdoc} + */ + public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { + $plugin_id = parent::transform($value, $migrate_executable, $row, $destination_property); + + // If the static map is bypassed on failure, the returned plugin ID will be + // an array if $value was. Plugin IDs cannot be arrays, so flatten it before + // passing it into the filter manager. + if (is_array($plugin_id)) { + $plugin_id = implode(':', $plugin_id); + } + + if ($this->filterManager->hasDefinition($plugin_id)) { + return $plugin_id; + } + else { + $fallback = $this->filterManager->getFallbackPluginId($plugin_id); + + $message = $this->t('Filter @plugin_id could not be mapped to an existing filter plugin; defaulting to @fallback.', [ + '@plugin_id' => $plugin_id, + '@fallback' => $fallback, + ]); + $migrate_executable->saveMessage((string) $message, MigrationInterface::MESSAGE_WARNING); + + return $fallback; + } + } + +} diff --git a/core/modules/filter/tests/src/Kernel/Migrate/d6/MigrateFilterFormatTest.php b/core/modules/filter/tests/src/Kernel/Migrate/d6/MigrateFilterFormatTest.php index ed5f0fbfe..81faf3e51 100644 --- a/core/modules/filter/tests/src/Kernel/Migrate/d6/MigrateFilterFormatTest.php +++ b/core/modules/filter/tests/src/Kernel/Migrate/d6/MigrateFilterFormatTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\filter\Kernel\Migrate\d6; use Drupal\filter\Entity\FilterFormat; +use Drupal\filter\FilterFormatInterface; use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase; /** @@ -39,14 +40,18 @@ class MigrateFilterFormatTest extends MigrateDrupal6TestBase { $this->assertFalse(isset($filters['filter_html_image_secure'])); // Check variables migrated into filter. - $this->assertIdentical('