diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index 4dd5231..7e5c3a4 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -342,7 +342,7 @@ public function preSave(EntityStorageInterface $storage) { throw new ConfigDuplicateUUIDException("Attempt to save a configuration entity '{$this->id()}' with UUID '{$this->uuid()}' when this entity already exists with UUID '{$original->uuid()}'"); } } - if (!$this->isSyncing() && !$this->trustedData) { + if (!$this->isSyncing()) { // Ensure the correct dependencies are present. If the configuration is // being written during a configuration synchronization then there is no // need to recalculate the dependencies. diff --git a/core/lib/Drupal/Core/Field/FieldItemBase.php b/core/lib/Drupal/Core/Field/FieldItemBase.php index 8e50c6a..c73c7fd 100644 --- a/core/lib/Drupal/Core/Field/FieldItemBase.php +++ b/core/lib/Drupal/Core/Field/FieldItemBase.php @@ -270,6 +270,13 @@ public static function calculateDependencies(FieldDefinitionInterface $field_def /** * {@inheritdoc} */ + public static function calculateStorageDependencies(FieldStorageDefinitionInterface $field_definition) { + return []; + } + + /** + * {@inheritdoc} + */ public static function onDependencyRemoval(FieldDefinitionInterface $field_definition, array $dependencies) { return FALSE; } diff --git a/core/lib/Drupal/Core/Field/FieldItemInterface.php b/core/lib/Drupal/Core/Field/FieldItemInterface.php index 23e3757..f6685b1 100644 --- a/core/lib/Drupal/Core/Field/FieldItemInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemInterface.php @@ -419,6 +419,39 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state); public static function calculateDependencies(FieldDefinitionInterface $field_definition); /** + * Calculates dependencies for field items on the storage level. + * + * Dependencies are saved in the field storage configuration entity and are + * used to determine configuration synchronization order. For example, if the + * field type storage depends on a particular entity type, this method should + * return an array of dependencies listing the module that provides the entity + * type. + * + * Dependencies added here affect the storage and therefore are always + * considered hard dependencies. If the dependency is removed the field + * storage must be deleted. + * + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $field_storage_definition + * The field storage definition. + * + * @return array + * An array of dependencies grouped by type (config, content, module, + * theme). For example: + * @code + * array( + * 'config' => array('user.role.anonymous', 'user.role.authenticated'), + * 'content' => array('node:article:f0a189e6-55fb-47fb-8005-5bef81c44d6d'), + * 'module' => array('node', 'user'), + * 'theme' => array('seven'), + * ); + * @endcode + * + * @see \Drupal\Core\Config\Entity\ConfigDependencyManager + * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::getConfigDependencyName() + */ + public static function calculateStorageDependencies(FieldStorageDefinitionInterface $field_storage_definition); + + /** * Informs the plugin that a dependency of the field will be deleted. * * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition 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 66c0b00..9e76afb 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php @@ -448,6 +448,16 @@ public static function calculateDependencies(FieldDefinitionInterface $field_def /** * {@inheritdoc} */ + public static function calculateStorageDependencies(FieldStorageDefinitionInterface $field_definition) { + $dependencies = parent::calculateStorageDependencies($field_definition); + $target_entity_type = \Drupal::entityManager()->getDefinition($field_definition->getSetting('target_type')); + $dependencies['module'][] = $target_entity_type->getProvider(); + return $dependencies; + } + + /** + * {@inheritdoc} + */ public static function onDependencyRemoval(FieldDefinitionInterface $field_definition, array $dependencies) { $changed = parent::onDependencyRemoval($field_definition, $dependencies); $entity_manager = \Drupal::entityManager(); diff --git a/core/modules/book/config/install/core.entity_view_mode.node.print.yml b/core/modules/book/config/install/core.entity_view_mode.node.print.yml index d695ac5..d615b03 100644 --- a/core/modules/book/config/install/core.entity_view_mode.node.print.yml +++ b/core/modules/book/config/install/core.entity_view_mode.node.print.yml @@ -1,11 +1,11 @@ langcode: en status: false dependencies: - module: - - node enforced: module: - book + module: + - node id: node.print label: Print targetEntityType: node diff --git a/core/modules/config/src/Tests/ConfigInstallTest.php b/core/modules/config/src/Tests/ConfigInstallTest.php index 2d09548..7358e65 100644 --- a/core/modules/config/src/Tests/ConfigInstallTest.php +++ b/core/modules/config/src/Tests/ConfigInstallTest.php @@ -211,7 +211,11 @@ public function testDependencyChecking() { } $this->installModules(['config_other_module_config_test']); $this->installModules(['config_install_dependency_test']); - $this->assertTrue(entity_load('config_test', 'other_module_test_with_dependency'), 'The config_test.dynamic.other_module_test_with_dependency configuration has been created during install.'); + $entity = \Drupal::entityManager()->getStorage('config_test')->load('other_module_test_with_dependency'); + $this->assertTrue($entity, 'The config_test.dynamic.other_module_test_with_dependency configuration has been created during install.'); + // Ensure that dependencies can be added during module installation by + // hooks. + $this->assertIdentical('config_install_dependency_test', $entity->getDependencies()['module'][0]); } /** diff --git a/core/modules/config/tests/config_install_dependency_test/config_install_dependency_test.module b/core/modules/config/tests/config_install_dependency_test/config_install_dependency_test.module new file mode 100644 index 0000000..08a1697 --- /dev/null +++ b/core/modules/config/tests/config_install_dependency_test/config_install_dependency_test.module @@ -0,0 +1,15 @@ +setEnforcedDependencies(['module' => ['config_install_dependency_test']]); +} diff --git a/core/modules/field/src/Entity/FieldStorageConfig.php b/core/modules/field/src/Entity/FieldStorageConfig.php index 594fb3b..d4c2922 100644 --- a/core/modules/field/src/Entity/FieldStorageConfig.php +++ b/core/modules/field/src/Entity/FieldStorageConfig.php @@ -343,6 +343,11 @@ public function calculateDependencies() { parent::calculateDependencies(); // Ensure the field is dependent on the providing module. $this->addDependency('module', $this->getTypeProvider()); + // Ask the field type for any additional storage dependencies. + // @see \Drupal\Core\Field\FieldItemInterface::calculateStorageDependencies() + $definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->getType(), FALSE); + $this->addDependencies($definition['class']::calculateStorageDependencies($this)); + // Ensure the field is dependent on the provider of the entity type. $entity_type = \Drupal::entityManager()->getDefinition($this->entity_type); $this->addDependency('module', $entity_type->getProvider()); diff --git a/core/modules/field/tests/src/Unit/FieldStorageConfigEntityUnitTest.php b/core/modules/field/tests/src/Unit/FieldStorageConfigEntityUnitTest.php index ab5fa75..daaf995 100644 --- a/core/modules/field/tests/src/Unit/FieldStorageConfigEntityUnitTest.php +++ b/core/modules/field/tests/src/Unit/FieldStorageConfigEntityUnitTest.php @@ -8,6 +8,9 @@ namespace Drupal\Tests\field\Unit; use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\Field\FieldItemBase; +use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\field\Entity\FieldStorageConfig; use Drupal\Tests\UnitTestCase; @@ -40,15 +43,24 @@ class FieldStorageConfigEntityUnitTest extends UnitTestCase { protected $uuid; /** + * The field type manager. + * + * @var \Drupal\Core\Field\FieldTypePluginManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $fieldTypeManager; + + /** * {@inheritdoc} */ protected function setUp() { $this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface'); $this->uuid = $this->getMock('\Drupal\Component\Uuid\UuidInterface'); + $this->fieldTypeManager = $this->getMock(FieldTypePluginManagerInterface::class); $container = new ContainerBuilder(); $container->set('entity.manager', $this->entityManager); $container->set('uuid', $this->uuid); + $container->set('plugin.manager.field.field_type', $this->fieldTypeManager); \Drupal::setContainer($container); } @@ -73,18 +85,19 @@ public function testCalculateDependencies() { // ConfigEntityBase::addDependency() to get the provider of the field config // entity type and once in FieldStorageConfig::calculateDependencies() to // get the provider of the entity type that field is attached to. - $this->entityManager->expects($this->at(0)) - ->method('getDefinition') - ->with('field_storage_config') - ->will($this->returnValue($fieldStorageConfigentityType)); - $this->entityManager->expects($this->at(1)) + $this->entityManager->expects($this->any()) ->method('getDefinition') - ->with($attached_entity_type_id) - ->will($this->returnValue($attached_entity_type)); - $this->entityManager->expects($this->at(2)) + ->willReturnMap([ + ['field_storage_config', TRUE, $fieldStorageConfigentityType], + [$attached_entity_type_id, TRUE, $attached_entity_type], + ]); + + $this->fieldTypeManager->expects($this->atLeastOnce()) ->method('getDefinition') - ->with('field_storage_config') - ->will($this->returnValue($fieldStorageConfigentityType)); + ->with('test_field_type', FALSE) + ->willReturn([ + 'class' => TestFieldType::class, + ]); $field_storage = new FieldStorageConfig(array( 'entity_type' => $attached_entity_type_id, @@ -94,8 +107,28 @@ public function testCalculateDependencies() { )); $dependencies = $field_storage->calculateDependencies()->getDependencies(); - $this->assertContains('test_module', $dependencies['module']); - $this->assertContains('entity_provider_module', $dependencies['module']); + $this->assertEquals(['entity_provider_module', 'entity_test', 'test_module'], $dependencies['module']); + $this->assertEquals(['stark'], $dependencies['theme']); + } + +} + +/** + * A test class to test field storage dependencies. + * + * @see \Drupal\Core\Field\FieldItemInterface::calculateStorageDependencies() + */ +class TestFieldType { + + /** + * {@inheritdoc} + */ + public static function calculateStorageDependencies(FieldStorageDefinitionInterface $field_definition) { + $dependencies = []; + $dependencies['module'] = ['entity_test']; + $dependencies['theme'] = ['stark']; + + return $dependencies; } } diff --git a/core/modules/forum/config/install/core.entity_view_display.node.forum.default.yml b/core/modules/forum/config/install/core.entity_view_display.node.forum.default.yml index 69fdb79..39389a5 100644 --- a/core/modules/forum/config/install/core.entity_view_display.node.forum.default.yml +++ b/core/modules/forum/config/install/core.entity_view_display.node.forum.default.yml @@ -8,7 +8,6 @@ dependencies: - node.type.forum module: - comment - - taxonomy - text - user id: node.forum.default diff --git a/core/modules/forum/config/install/core.entity_view_display.node.forum.teaser.yml b/core/modules/forum/config/install/core.entity_view_display.node.forum.teaser.yml index 6eb1a0c..4405e71 100644 --- a/core/modules/forum/config/install/core.entity_view_display.node.forum.teaser.yml +++ b/core/modules/forum/config/install/core.entity_view_display.node.forum.teaser.yml @@ -8,7 +8,6 @@ dependencies: - field.field.node.forum.taxonomy_forums - node.type.forum module: - - taxonomy - text - user id: node.forum.teaser diff --git a/core/modules/forum/config/install/field.field.node.forum.taxonomy_forums.yml b/core/modules/forum/config/install/field.field.node.forum.taxonomy_forums.yml index 1f7bc5c..f2a30f2 100644 --- a/core/modules/forum/config/install/field.field.node.forum.taxonomy_forums.yml +++ b/core/modules/forum/config/install/field.field.node.forum.taxonomy_forums.yml @@ -4,8 +4,7 @@ dependencies: config: - field.storage.node.taxonomy_forums - node.type.forum - module: - - taxonomy + - taxonomy.vocabulary.forums id: node.forum.taxonomy_forums field_name: taxonomy_forums entity_type: node diff --git a/core/modules/system/src/Tests/Entity/EntityReferenceFieldTest.php b/core/modules/system/src/Tests/Entity/EntityReferenceFieldTest.php index b78dff29..d372902 100644 --- a/core/modules/system/src/Tests/Entity/EntityReferenceFieldTest.php +++ b/core/modules/system/src/Tests/Entity/EntityReferenceFieldTest.php @@ -14,6 +14,7 @@ use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait; use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; use Drupal\user\Entity\Role; use Drupal\user\Entity\User; use Drupal\user\RoleInterface; @@ -406,4 +407,35 @@ public function testTargetEntityNoLoad() { } } + /** + * Tests the dependencies entity reference fields are created with. + */ + public function testEntityReferenceFieldDependencies() { + $field_name = 'user_reference_field'; + $entity_type = 'entity_test'; + + $field_storage = FieldStorageConfig::create(array( + 'field_name' => $field_name, + 'type' => 'entity_reference', + 'entity_type' => $entity_type, + 'settings' => array( + 'target_type' => 'user', + ), + )); + $field_storage->save(); + $this->assertEqual(['module' => ['entity_test', 'user']], $field_storage->getDependencies()); + + $field = FieldConfig::create(array( + 'field_name' => $field_name, + 'entity_type' => $entity_type, + 'bundle' => 'entity_test', + 'label' => $field_name, + 'settings' => array( + 'handler' => 'default', + ), + )); + $field->save(); + $this->assertEqual(['config' => ['field.storage.entity_test.user_reference_field'], 'module' => ['entity_test']], $field->getDependencies()); + } + } diff --git a/core/modules/system/src/Tests/Update/RecalculatedDependencyTest.php b/core/modules/system/src/Tests/Update/RecalculatedDependencyTest.php new file mode 100644 index 0000000..53a832a --- /dev/null +++ b/core/modules/system/src/Tests/Update/RecalculatedDependencyTest.php @@ -0,0 +1,50 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../tests/fixtures/update/drupal-8.bare.standard.php.gz', + ]; + } + + /** + * Ensures that the entities are resaved so they have the new dependency. + */ + public function testUpdate() { + $this->runUpdates(); + + $data = \Drupal::config('field.field.node.article.field_tags')->get(); + $this->assertFalse(isset($data['dependencies']['module'])); + $this->assertEqual([ + 'field.storage.node.field_tags', + 'node.type.article', + 'taxonomy.vocabulary.tags' + ], $data['dependencies']['config']); + + $data = \Drupal::config('field.storage.node.field_tags')->get(); + $this->assertEqual(['node', 'taxonomy'], $data['dependencies']['module']); + + $data = \Drupal::config('field.field.user.user.user_picture')->get(); + $this->assertEqual(['image', 'user'], $data['dependencies']['module']); + + $data = \Drupal::config('field.storage.node.field_image')->get(); + $this->assertEqual(['file', 'image', 'node'], $data['dependencies']['module']); + } + +} diff --git a/core/modules/system/src/Tests/Update/UpdatePostUpdateTest.php b/core/modules/system/src/Tests/Update/UpdatePostUpdateTest.php index 1d6375e..e031df3 100644 --- a/core/modules/system/src/Tests/Update/UpdatePostUpdateTest.php +++ b/core/modules/system/src/Tests/Update/UpdatePostUpdateTest.php @@ -64,6 +64,7 @@ public function testPostUpdate() { 'field_post_update_save_custom_storage_property', 'field_post_update_entity_reference_handler_setting', 'system_post_update_fix_enforced_dependencies', + 'system_post_update_recalculate_dependencies_for_installed_config_entities', 'views_post_update_update_cacheability_metadata', ], $updates); $this->assertEqual($updates, $key_value->get('existing_updates')); diff --git a/core/modules/system/system.post_update.php b/core/modules/system/system.post_update.php index 3a141ce..46cfda2 100644 --- a/core/modules/system/system.post_update.php +++ b/core/modules/system/system.post_update.php @@ -4,6 +4,9 @@ * @file * Post update functions for System. */ +use Drupal\Core\Config\FileStorage; +use Drupal\Core\Config\InstallStorage; +use Drupal\Core\Config\StorageInterface; /** * @addtogroup updates-8.0.0-beta @@ -31,5 +34,30 @@ function system_post_update_fix_enforced_dependencies() { } /** + * Re-save all config entities that got installed at some point. + * + * Drupal forgot to recalculate the dependencies for them, + * see https://www.drupal.org/node/2520526, so we resave to recalculate them. + */ +function system_post_update_recalculate_dependencies_for_installed_config_entities() { + /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */ + $config_manager = \Drupal::service('config.manager'); + + // Loads scan all module install folders and resave the entities in there. + $modules = array_keys(\Drupal::moduleHandler()->getModuleList()); + foreach ($modules as $module) { + $default_install_path = drupal_get_path('module', $module) . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY; + $storage = new FileStorage($default_install_path, StorageInterface::DEFAULT_COLLECTION); + $config_names = $storage->listAll(); + + foreach ($config_names as $config_name) { + if ($entity = $config_manager->loadConfigEntityByName($config_name)) { + $entity->save(); + } + } + } +} + +/** * @} End of "addtogroup updates-8.0.0-beta". */ diff --git a/core/profiles/standard/config/install/field.field.node.article.field_tags.yml b/core/profiles/standard/config/install/field.field.node.article.field_tags.yml index c49c64f..1b9c4cc 100644 --- a/core/profiles/standard/config/install/field.field.node.article.field_tags.yml +++ b/core/profiles/standard/config/install/field.field.node.article.field_tags.yml @@ -4,6 +4,7 @@ dependencies: config: - field.storage.node.field_tags - node.type.article + - taxonomy.vocabulary.tags id: node.article.field_tags field_name: field_tags entity_type: node diff --git a/core/profiles/standard/config/install/field.storage.node.field_image.yml b/core/profiles/standard/config/install/field.storage.node.field_image.yml index 8c2f981..e4da708 100644 --- a/core/profiles/standard/config/install/field.storage.node.field_image.yml +++ b/core/profiles/standard/config/install/field.storage.node.field_image.yml @@ -2,8 +2,9 @@ langcode: en status: true dependencies: module: - - node + - file - image + - node id: node.field_image field_name: field_image entity_type: node diff --git a/core/profiles/standard/config/install/field.storage.user.user_picture.yml b/core/profiles/standard/config/install/field.storage.user.user_picture.yml index 2d47c28..8253628 100644 --- a/core/profiles/standard/config/install/field.storage.user.user_picture.yml +++ b/core/profiles/standard/config/install/field.storage.user.user_picture.yml @@ -2,6 +2,7 @@ langcode: en status: true dependencies: module: + - file - image - user id: user.user_picture