diff --git a/core/modules/layout_builder/src/EventSubscriber/SetInlineBlockDependency.php b/core/modules/layout_builder/src/EventSubscriber/SetInlineBlockDependency.php index edc05f83ddf8798f89415aa46db6199dc24a8ae2..9f9ba7c2d6daac8afaaae2d7622a9eb187dfa9a8 100644 --- a/core/modules/layout_builder/src/EventSubscriber/SetInlineBlockDependency.php +++ b/core/modules/layout_builder/src/EventSubscriber/SetInlineBlockDependency.php @@ -10,6 +10,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\layout_builder\InlineBlockUsage; use Drupal\layout_builder\LayoutEntityHelperTrait; +use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -64,11 +65,14 @@ class SetInlineBlockDependency implements EventSubscriberInterface { * The database connection. * @param \Drupal\layout_builder\InlineBlockUsage $usage * The inline block usage service. + * @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager + * The section storage manager. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, InlineBlockUsage $usage) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, InlineBlockUsage $usage, SectionStorageManagerInterface $section_storage_manager) { $this->entityTypeManager = $entity_type_manager; $this->database = $database; $this->usage = $usage; + $this->sectionStorageManager = $section_storage_manager; } /** diff --git a/core/modules/layout_builder/src/InlineBlockEntityOperations.php b/core/modules/layout_builder/src/InlineBlockEntityOperations.php index 7e64b83cf1648622e06bae308e22ae67e594914e..55eab3b75e7d6f663feb30fa82c5560adcb9797e 100644 --- a/core/modules/layout_builder/src/InlineBlockEntityOperations.php +++ b/core/modules/layout_builder/src/InlineBlockEntityOperations.php @@ -8,6 +8,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\RevisionableInterface; use Drupal\layout_builder\Plugin\Block\InlineBlock; +use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -43,17 +44,36 @@ class InlineBlockEntityOperations implements ContainerInjectionInterface { /** * Constructs a new EntityOperations object. * + * @todo This constructor has one optional parameter, $section_storage_manager + * and one totally unused $database parameter. Deprecate the current + * constructor signature in https://www.drupal.org/node/3031492 after the + * general policy for constructor backwards compatibility is determined in + * https://www.drupal.org/node/3030640. + * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager * The entity type manager service. * @param \Drupal\layout_builder\InlineBlockUsage $usage * Inline block usage tracking service. * @param \Drupal\Core\Database\Connection $database * The database connection. + * @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager + * (optional) The section storage manager. + * + * @todo The current constructor signature is deprecated: + * - The $section_storage_manager parameter is optional, but should become + * required. + * - The $database parameter is unused and should be removed. + * Deprecate in https://www.drupal.org/node/3031492. */ - public function __construct(EntityTypeManagerInterface $entityTypeManager, InlineBlockUsage $usage, Connection $database) { + public function __construct(EntityTypeManagerInterface $entityTypeManager, InlineBlockUsage $usage, Connection $database, SectionStorageManagerInterface $section_storage_manager = NULL) { $this->entityTypeManager = $entityTypeManager; $this->blockContentStorage = $entityTypeManager->getStorage('block_content'); $this->usage = $usage; + if ($section_storage_manager === NULL) { + @trigger_error('The plugin.manager.layout_builder.section_storage service must be passed to \Drupal\layout_builder\InlineBlockEntityOperations::__construct(). It was added in Drupal 8.7.0 and will be required before Drupal 9.0.0.', E_USER_DEPRECATED); + $section_storage_manager = \Drupal::service('plugin.manager.layout_builder.section_storage'); + } + $this->sectionStorageManager = $section_storage_manager; } /** @@ -63,7 +83,8 @@ public static function create(ContainerInterface $container) { return new static( $container->get('entity_type.manager'), $container->get('inline_block.usage'), - $container->get('database') + $container->get('database'), + $container->get('plugin.manager.layout_builder.section_storage') ); } @@ -85,10 +106,10 @@ protected function removeUnusedForEntityOnSave(EntityInterface $entity) { if ($entity->isNew() || !isset($entity->original) || $entity instanceof RevisionableInterface) { return; } - $sections = $this->getEntitySections($entity); - // If this is a layout override and there are no sections then it is a new - // override. - if ($this->isEntityUsingFieldOverride($entity) && empty($sections)) { + // If the original entity used the default storage then we cannot remove + // unused inline blocks because they will still be referenced in the + // defaults. + if ($this->originalEntityUsesDefaultStorage($entity)) { return; } @@ -132,9 +153,9 @@ protected function getRemovedBlockIds(EntityInterface $entity) { * The parent entity. */ public function handleEntityDelete(EntityInterface $entity) { - if ($this->isLayoutCompatibleEntity($entity)) { - $this->usage->removeByLayoutEntity($entity); - } + // @todo In https://www.drupal.org/node/3008943 call + // \Drupal\layout_builder\LayoutEntityHelperTrait::isLayoutCompatibleEntity(). + $this->usage->removeByLayoutEntity($entity); } /** @@ -150,14 +171,10 @@ public function handlePreSave(EntityInterface $entity) { $duplicate_blocks = FALSE; if ($sections = $this->getEntitySections($entity)) { - if ($this->isEntityUsingFieldOverride($entity)) { - if (!$entity->isNew() && isset($entity->original)) { - if (empty($this->getEntitySections($entity->original))) { - // If there were no sections in the original entity then this is a - // new override from a default and the blocks need to be duplicated. - $duplicate_blocks = TRUE; - } - } + if ($this->originalEntityUsesDefaultStorage($entity)) { + // This is a new override from a default and the blocks need to be + // duplicated. + $duplicate_blocks = TRUE; } $new_revision = FALSE; if ($entity instanceof RevisionableInterface) { diff --git a/core/modules/layout_builder/src/LayoutBuilderServiceProvider.php b/core/modules/layout_builder/src/LayoutBuilderServiceProvider.php index 5070bf10c5fdeb93980f79f77b1d13f2aeb54043..c730b20b2d170b29aaa933ec8cb99d5afa0c1557 100644 --- a/core/modules/layout_builder/src/LayoutBuilderServiceProvider.php +++ b/core/modules/layout_builder/src/LayoutBuilderServiceProvider.php @@ -33,6 +33,7 @@ public function register(ContainerBuilder $container) { new Reference('entity_type.manager'), new Reference('database'), new Reference('inline_block.usage'), + new Reference('plugin.manager.layout_builder.section_storage'), ]); $definition->addTag('event_subscriber'); $container->setDefinition('layout_builder.get_block_dependency_subscriber', $definition); diff --git a/core/modules/layout_builder/src/LayoutEntityHelperTrait.php b/core/modules/layout_builder/src/LayoutEntityHelperTrait.php index fc22168a99e5fb0afb6824c048944b0edaf6b473..c85c16073bc77fd657cc76399bfbe8ba41ba8ba7 100644 --- a/core/modules/layout_builder/src/LayoutEntityHelperTrait.php +++ b/core/modules/layout_builder/src/LayoutEntityHelperTrait.php @@ -3,8 +3,13 @@ namespace Drupal\layout_builder; use Drupal\Component\Plugin\DerivativeInspectionInterface; +use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\Entity\Entity\EntityViewDisplay; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Plugin\Context\Context; +use Drupal\Core\Plugin\Context\ContextDefinition; +use Drupal\Core\Plugin\Context\EntityContext; use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface; use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage; @@ -15,6 +20,13 @@ */ trait LayoutEntityHelperTrait { + /** + * The section storage manager. + * + * @var \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface + */ + protected $sectionStorageManager; + /** * Determines if an entity can have a layout. * @@ -25,7 +37,7 @@ trait LayoutEntityHelperTrait { * TRUE if the entity can have a layout otherwise FALSE. */ protected function isLayoutCompatibleEntity(EntityInterface $entity) { - return $entity instanceof LayoutEntityDisplayInterface || $this->isEntityUsingFieldOverride($entity); + return $this->getSectionStorageForEntity($entity) !== NULL; } /** @@ -51,24 +63,15 @@ protected function getInlineBlockRevisionIdsInSections(array $sections) { /** * Gets the sections for an entity if any. * - * @todo Replace this method with calls to the SectionStorageManagerInterface - * method for getting sections from an entity in - * https://www.drupal.org/node/2986403. - * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity. * - * @return \Drupal\layout_builder\Section[]|null + * @return \Drupal\layout_builder\Section[] * The entity layout sections if available. */ protected function getEntitySections(EntityInterface $entity) { - if ($entity instanceof LayoutEntityDisplayInterface) { - return $entity->getSections(); - } - elseif ($this->isEntityUsingFieldOverride($entity)) { - return $entity->get(OverridesSectionStorage::FIELD_NAME)->getSections(); - } - return NULL; + $section_storage = $this->getSectionStorageForEntity($entity); + return $section_storage ? $section_storage->getSections() : []; } /** @@ -93,6 +96,35 @@ protected function getInlineBlockComponents(array $sections) { return $inline_block_components; } + /** + * Gets the section storage for an entity. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity. + * + * @return \Drupal\layout_builder\SectionStorageInterface|null + * The section storage if found otherwise NULL. + */ + protected function getSectionStorageForEntity(EntityInterface $entity) { + // @todo Take into account other view modes in + // https://www.drupal.org/node/3008924. + $view_mode = 'full'; + if ($entity instanceof LayoutEntityDisplayInterface) { + $contexts['display'] = EntityContext::fromEntity($entity); + } + else { + $contexts['entity'] = EntityContext::fromEntity($entity); + if ($entity instanceof FieldableEntityInterface) { + $display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode); + if ($display instanceof LayoutEntityDisplayInterface) { + $contexts['display'] = EntityContext::fromEntity($display); + } + $contexts['view_mode'] = new Context(new ContextDefinition('string'), $view_mode); + } + } + return $this->sectionStorageManager()->findByContext($contexts, new CacheableMetadata()); + } + /** * Determines if an entity is using a field for the layout override. * @@ -101,9 +133,50 @@ protected function getInlineBlockComponents(array $sections) { * * @return bool * TRUE if the entity is using a field for a layout override. + * + * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. + * To determine if an entity has a layout override, use + * \Drupal\layout_builder\LayoutEntityHelperTrait::getSectionStorageForEntity() + * and check whether the result is an instance of + * \Drupal\layout_builder\DefaultsSectionStorageInterface. + * + * @see https://www.drupal.org/node/3030609 */ protected function isEntityUsingFieldOverride(EntityInterface $entity) { + @trigger_error('\Drupal\layout_builder\LayoutEntityHelperTrait::isEntityUsingFieldOverride() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Internal storage of overrides may change so the existence of the field does not necessarily guarantee an overridable entity. See https://www.drupal.org/node/3030609.', E_USER_DEPRECATED); return $entity instanceof FieldableEntityInterface && $entity->hasField(OverridesSectionStorage::FIELD_NAME); } + /** + * Determines if the original entity used the default section storage. + * + * This method can be used during the entity save process to determine whether + * $entity->original is set and used the default section storage plugin as + * determined by ::getSectionStorageForEntity(). + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity. + * + * @return bool + * TRUE if the original entity used the default storage. + */ + protected function originalEntityUsesDefaultStorage(EntityInterface $entity) { + $section_storage = $this->getSectionStorageForEntity($entity); + if ($section_storage instanceof OverridesSectionStorageInterface && !$entity->isNew() && isset($entity->original)) { + $original_section_storage = $this->getSectionStorageForEntity($entity->original); + return $original_section_storage instanceof DefaultsSectionStorageInterface; + } + return FALSE; + } + + /** + * Gets the section storage manager. + * + * @return \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface + * The section storage manager. + */ + private function sectionStorageManager() { + return $this->sectionStorageManager ?: \Drupal::service('plugin.manager.layout_builder.section_storage'); + } + } diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutEntityHelperTraitTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutEntityHelperTraitTest.php new file mode 100644 index 0000000000000000000000000000000000000000..54cc87d5b1f76a7cfd77d39fdffa1078b320e00c --- /dev/null +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutEntityHelperTraitTest.php @@ -0,0 +1,260 @@ +<?php + +namespace Drupal\Tests\layout_builder\Kernel; + +use Drupal\Core\Entity\Display\EntityViewDisplayInterface; +use Drupal\Core\Entity\Entity\EntityViewDisplay; +use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Plugin\Context\Context; +use Drupal\Core\Plugin\Context\EntityContext; +use Drupal\entity_test\Entity\EntityTest; +use Drupal\KernelTests\KernelTestBase; +use Drupal\layout_builder\DefaultsSectionStorageInterface; +use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay; +use Drupal\layout_builder\LayoutEntityHelperTrait; +use Drupal\layout_builder\OverridesSectionStorageInterface; +use Drupal\layout_builder\Section; +use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface; +use Drupal\layout_builder\SectionStorageInterface; +use Prophecy\Argument; + +/** + * @coversDefaultClass \Drupal\layout_builder\LayoutEntityHelperTrait + * + * @group layout_builder + */ +class LayoutEntityHelperTraitTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'layout_builder', + 'entity_test', + 'system', + 'user', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->installSchema('system', ['key_value_expire']); + $this->installEntitySchema('user'); + $this->installEntitySchema('entity_test'); + } + + /** + * Dataprovider for testGetSectionStorageForEntity(). + */ + public function providerTestGetSectionStorageForEntity() { + $data = []; + $data['entity_view_display'] = [ + 'entity_view_display', + [ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'default', + 'status' => TRUE, + 'third_party_settings' => [ + 'layout_builder' => [ + 'enabled' => TRUE, + ], + ], + ], + ['display'], + ]; + $data['fieldable entity'] = [ + 'entity_test', + [], + ['entity', 'display', 'view_mode'], + ]; + return $data; + } + + /** + * @covers ::getSectionStorageForEntity + * + * @dataProvider providerTestGetSectionStorageForEntity + */ + public function testGetSectionStorageForEntity($entity_type_id, $values, $expected_context_keys) { + $section_storage_manager = $this->prophesize(SectionStorageManagerInterface::class); + $section_storage_manager->load('')->willReturn(NULL); + $section_storage_manager->findByContext(Argument::cetera())->will(function ($arguments) { + return $arguments[0]; + }); + $this->container->set('plugin.manager.layout_builder.section_storage', $section_storage_manager->reveal()); + $entity = $this->container->get('entity_type.manager')->getStorage($entity_type_id)->create($values); + $entity->save(); + $class = new TestLayoutEntityHelperTrait(); + $result = $class->getSectionStorageForEntity($entity); + $this->assertEquals($expected_context_keys, array_keys($result)); + if ($entity instanceof EntityViewDisplayInterface) { + $this->assertEquals(EntityContext::fromEntity($entity), $result['display']); + } + elseif ($entity instanceof FieldableEntityInterface) { + $this->assertEquals(EntityContext::fromEntity($entity), $result['entity']); + $this->assertInstanceOf(Context::class, $result['view_mode']); + $this->assertEquals('full', $result['view_mode']->getContextData()->getValue()); + + $expected_display = EntityViewDisplay::collectRenderDisplay($entity, 'full'); + $this->assertInstanceOf(EntityContext::class, $result['display']); + /** @var \Drupal\Core\Plugin\Context\EntityContext $display_entity_context */ + $display_entity_context = $result['display']; + + /** @var \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay $display_entity */ + $display_entity = $display_entity_context->getContextData()->getValue(); + $this->assertInstanceOf(LayoutBuilderEntityViewDisplay::class, $display_entity); + + $this->assertEquals('full', $display_entity->getMode()); + $this->assertEquals($expected_display->getEntityTypeId(), $display_entity->getEntityTypeId()); + $this->assertEquals($expected_display->getComponents(), $display_entity->getComponents()); + $this->assertEquals($expected_display->getThirdPartySettings('layout_builder'), $display_entity->getThirdPartySettings('layout_builder')); + } + else { + throw new \UnexpectedValueException("Unexpected entity type."); + } + + } + + /** + * Dataprovider for testOriginalEntityUsesDefaultStorage(). + */ + public function providerTestOriginalEntityUsesDefaultStorage() { + return [ + 'original uses default' => [ + [ + 'updated' => 'override', + 'original' => 'default', + ], + FALSE, + TRUE, + TRUE, + ], + 'original uses override' => [ + [ + 'updated' => 'override', + 'original' => 'override', + ], + FALSE, + TRUE, + FALSE, + ], + 'no original use override' => [ + [ + 'updated' => 'override', + ], + FALSE, + FALSE, + FALSE, + ], + 'no original uses default' => [ + [ + 'updated' => 'default', + ], + FALSE, + FALSE, + FALSE, + ], + 'is new use override' => [ + [ + 'updated' => 'override', + ], + TRUE, + FALSE, + FALSE, + ], + 'is new use default' => [ + [ + 'updated' => 'default', + ], + TRUE, + FALSE, + FALSE, + ], + + ]; + } + + /** + * @covers ::originalEntityUsesDefaultStorage + * + * @dataProvider providerTestOriginalEntityUsesDefaultStorage + */ + public function testOriginalEntityUsesDefaultStorage($entity_storages, $is_new, $has_original, $expected) { + $this->assertFalse($is_new && $has_original); + $entity = EntityTest::create(['name' => 'updated']); + if (!$is_new) { + $entity->save(); + if ($has_original) { + $original_entity = EntityTest::create(['name' => 'original']); + $entity->original = $original_entity; + } + + } + + $section_storage_manager = $this->prophesize(SectionStorageManagerInterface::class); + $section_storage_manager->load('')->willReturn(NULL); + $storages = [ + 'default' => $this->prophesize(DefaultsSectionStorageInterface::class)->reveal(), + 'override' => $this->prophesize(OverridesSectionStorageInterface::class)->reveal(), + ]; + + $section_storage_manager->findByContext(Argument::cetera())->will(function ($arguments) use ($storages, $entity_storages) { + $contexts = $arguments[0]; + if (isset($contexts['entity'])) { + /** @var \Drupal\entity_test\Entity\EntityTest $entity */ + $entity = $contexts['entity']->getContextData()->getValue(); + return $storages[$entity_storages[$entity->getName()]]; + } + }); + + $this->container->set('plugin.manager.layout_builder.section_storage', $section_storage_manager->reveal()); + $class = new TestLayoutEntityHelperTrait(); + $this->assertSame($expected, $class->originalEntityUsesDefaultStorage($entity)); + } + + /** + * @covers ::getEntitySections + */ + public function testGetEntitySections() { + $entity = EntityTest::create(['name' => 'updated']); + $section_storage_manager = $this->prophesize(SectionStorageManagerInterface::class); + $section_storage_manager->load('')->willReturn(NULL); + $section_storage = $this->prophesize(SectionStorageInterface::class); + $sections = [ + new Section('layout_onecol'), + ]; + $this->assertCount(1, $sections); + $section_storage->getSections()->willReturn($sections); + $section_storage->count()->willReturn(1); + + $section_storage_manager->findByContext(Argument::cetera())->willReturn($section_storage->reveal()); + $this->container->set('plugin.manager.layout_builder.section_storage', $section_storage_manager->reveal()); + $class = new TestLayoutEntityHelperTrait(); + // Ensure that if the entity has a section storage the sections will be + // returned. + $this->assertSame($sections, $class->getEntitySections($entity)); + + $section_storage_manager->findByContext(Argument::cetera())->willReturn(NULL); + $this->container->set('plugin.manager.layout_builder.section_storage', $section_storage_manager->reveal()); + // Ensure that if the entity has no section storage an empty array will be + // returned. + $this->assertSame([], $class->getEntitySections($entity)); + } + +} + +/** + * Test class using the trait. + */ +class TestLayoutEntityHelperTrait { + use LayoutEntityHelperTrait { + getSectionStorageForEntity as public; + originalEntityUsesDefaultStorage as public; + getEntitySections as public; + } + +} diff --git a/core/modules/layout_builder/tests/src/Unit/LayoutEntityHelperTraitTest.php b/core/modules/layout_builder/tests/src/Unit/LayoutEntityHelperTraitTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3f7e97b03a7d2ee7a137c3d932db4b1ff2a957fa --- /dev/null +++ b/core/modules/layout_builder/tests/src/Unit/LayoutEntityHelperTraitTest.php @@ -0,0 +1,160 @@ +<?php + +namespace Drupal\Tests\layout_builder\Unit; + +use Drupal\Component\Plugin\ConfigurablePluginInterface; +use Drupal\Component\Plugin\DerivativeInspectionInterface; +use Drupal\Component\Plugin\PluginInspectionInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\layout_builder\LayoutEntityHelperTrait; +use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage; +use Drupal\layout_builder\Section; +use Drupal\layout_builder\SectionComponent; +use Drupal\Tests\UnitTestCase; + +/** + * @coversDefaultClass \Drupal\layout_builder\LayoutEntityHelperTrait + * + * @group layout_builder + */ +class LayoutEntityHelperTraitTest extends UnitTestCase { + + /** + * @covers ::isEntityUsingFieldOverride + * + * @dataProvider providerTestIsEntityUsingFieldOverride + * + * @expectedDeprecation \Drupal\layout_builder\LayoutEntityHelperTrait::isEntityUsingFieldOverride() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Internal storage of overrides may change so the existence of the field does not necessarily guarantee an overridable entity. See https://www.drupal.org/node/3030609. + * + * @group legacy + */ + public function testIsEntityUsingFieldOverride(EntityInterface $entity, $expected) { + $test_class = new TestClass(); + $this->assertSame($expected, $test_class->isEntityUsingFieldOverride($entity)); + } + + /** + * Dataprovider for testIsEntityUsingFieldOverride(). + */ + public function providerTestIsEntityUsingFieldOverride() { + $data['non fieldable entity'] = [ + $this->prophesize(EntityInterface::class)->reveal(), + FALSE, + ]; + $fieldable_entity = $this->prophesize(FieldableEntityInterface::class); + $fieldable_entity->hasField(OverridesSectionStorage::FIELD_NAME)->willReturn(FALSE); + $data['fieldable entity without layout field'] = [ + $fieldable_entity->reveal(), + FALSE, + ]; + $entity_using_field = $this->prophesize(FieldableEntityInterface::class); + $entity_using_field->hasField(OverridesSectionStorage::FIELD_NAME)->willReturn(TRUE); + $data['fieldable entity with layout field'] = [ + $entity_using_field->reveal(), + TRUE, + ]; + return $data; + } + + /** + * Dataprovider method for tests that need sections with inline blocks. + */ + public function providerSectionsWithInlineComponents() { + $components = []; + + // Ensure a non-derivative component is not returned. + $non_derivative_component = $this->prophesize(SectionComponent::class); + $non_derivative_component->getPlugin()->willReturn($this->prophesize(PluginInspectionInterface::class)->reveal()); + $components[] = $non_derivative_component->reveal(); + + // Ensure a derivative component with a different base Id is not returned. + $derivative_non_inline_component = $this->prophesize(SectionComponent::class); + $plugin = $this->prophesize(DerivativeInspectionInterface::class); + $plugin->getBaseId()->willReturn('some_other_base_id_which_we_do_not_care_about_but_it_is_nothing_personal'); + $derivative_non_inline_component->getPlugin()->willReturn($plugin); + $components[] = $derivative_non_inline_component->reveal(); + + // Ensure that inline block component is returned. + $inline_component = $this->prophesize(SectionComponent::class); + $inline_plugin = $this->prophesize(DerivativeInspectionInterface::class)->willImplement(ConfigurablePluginInterface::class); + $inline_plugin->getBaseId()->willReturn('inline_block'); + $inline_plugin->getConfiguration()->willReturn(['block_revision_id' => 'the_revision_id']); + $inline_component->getPlugin()->willReturn($inline_plugin->reveal()); + $inline_component = $inline_component->reveal(); + $components[] = $inline_component; + + // Ensure that inline block component without revision is returned. + $inline_component_without_revision_id = $this->prophesize(SectionComponent::class); + $inline_plugin_without_revision_id = $this->prophesize(DerivativeInspectionInterface::class)->willImplement(ConfigurablePluginInterface::class); + $inline_plugin_without_revision_id->getBaseId()->willReturn('inline_block'); + $inline_plugin_without_revision_id->getConfiguration()->willReturn(['other_key' => 'other_value']); + $inline_component_without_revision_id->getPlugin()->willReturn($inline_plugin_without_revision_id->reveal()); + $inline_component_without_revision_id = $inline_component_without_revision_id->reveal(); + $components[] = $inline_component_without_revision_id; + + $section = $this->prophesize(Section::class); + $section->getComponents()->willReturn($components); + + $components = []; + // Ensure that inline block components in all sections are returned. + $inline_component2 = $this->prophesize(SectionComponent::class); + $inline_plugin2 = $this->prophesize(DerivativeInspectionInterface::class)->willImplement(ConfigurablePluginInterface::class); + $inline_plugin2->getBaseId()->willReturn('inline_block'); + $inline_plugin2->getConfiguration()->willReturn(['block_revision_id' => 'the_other_revision_id']); + $inline_component2->getPlugin()->willReturn($inline_plugin2->reveal()); + $inline_component2 = $inline_component2->reveal(); + $components[] = $inline_component2; + + $section2 = $this->prophesize(Section::class); + $section2->getComponents()->willReturn($components); + + return [ + [ + [$section->reveal(), $section2->reveal()], + // getInlineBlockComponents() should return inline blocks even if they + // have no revision Id. + [ + $inline_component, + $inline_component_without_revision_id, + $inline_component2, + ], + // getInlineBlockRevisionIdsInSections should just the revision Ids. + ['the_revision_id', 'the_other_revision_id'], + ], + ]; + } + + /** + * @covers ::getInlineBlockComponents + * + * @dataProvider providerSectionsWithInlineComponents + */ + public function testGetInlineBlockComponents($sections, $expected_components) { + $test_class = new TestClass(); + $this->assertSame($expected_components, $test_class->getInlineBlockComponents($sections)); + } + + /** + * @covers ::getInlineBlockRevisionIdsInSections + * + * @dataProvider providerSectionsWithInlineComponents + */ + public function testGetInlineBlockRevisionIdsInSections($sections, $components, $expected_revision_ids) { + $test_class = new TestClass(); + $this->assertSame($expected_revision_ids, $test_class->getInlineBlockRevisionIdsInSections($sections)); + } + +} + +/** + * Test class using the trait. + */ +class TestClass { + use LayoutEntityHelperTrait { + isEntityUsingFieldOverride as public; + getInlineBlockComponents as public; + getInlineBlockRevisionIdsInSections as public; + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Asset/ResolvedLibraryDefinitionsFilesMatchTest.php b/core/tests/Drupal/KernelTests/Core/Asset/ResolvedLibraryDefinitionsFilesMatchTest.php index df57e1213e35d1a904a329bd457b5c6740cb35f5..321443d4db16805b2d3240f589adc07d591b831b 100644 --- a/core/tests/Drupal/KernelTests/Core/Asset/ResolvedLibraryDefinitionsFilesMatchTest.php +++ b/core/tests/Drupal/KernelTests/Core/Asset/ResolvedLibraryDefinitionsFilesMatchTest.php @@ -108,6 +108,15 @@ protected function setUp() { } return TRUE; }); + + // Install the 'user' entity schema because the workspaces module's install + // hook creates a workspace with default uid of 1. Then the layout_builder + // module's implementation of hook_entity_presave will cause + // \Drupal\Core\TypedData\Validation\RecursiveValidator::validate() to run + // on the workspace which will fail because the user table is not present. + // @todo Remove this in https://www.drupal.org/node/3039217. + $this->installEntitySchema('user'); + // Remove demo_umami_content module as its install hook creates content // that relies on the presence of entity tables and various other elements // not present in a kernel test.