diff --git a/core/modules/layout_builder/layout_builder.module b/core/modules/layout_builder/layout_builder.module index f03e73afd0..b05382b94d 100644 --- a/core/modules/layout_builder/layout_builder.module +++ b/core/modules/layout_builder/layout_builder.module @@ -405,3 +405,15 @@ function layout_builder_preprocess_language_content_settings_table(&$variables) } } } + +/** + * Implements hook_theme_suggestions_HOOK_alter(). + */ +function layout_builder_theme_suggestions_field_alter(&$suggestions, array $variables) { + $element = $variables['element']; + if (isset($element['#third_party_settings']['layout_builder']['view_mode'])) { + // See system_theme_suggestions_field(). + $suggestions[] = 'field__' . $element['#entity_type'] . '__' . $element['#field_name'] . '__' . $element['#bundle'] . '__' . $element['#third_party_settings']['layout_builder']['view_mode']; + } + return $suggestions; +} diff --git a/core/modules/layout_builder/src/LayoutEntityHelperTrait.php b/core/modules/layout_builder/src/LayoutEntityHelperTrait.php index e1c4216ce7..2c0d9d7547 100644 --- a/core/modules/layout_builder/src/LayoutEntityHelperTrait.php +++ b/core/modules/layout_builder/src/LayoutEntityHelperTrait.php @@ -109,6 +109,7 @@ protected function getSectionStorageForEntity(EntityInterface $entity) { $view_mode = 'full'; if ($entity instanceof LayoutEntityDisplayInterface) { $contexts['display'] = EntityContext::fromEntity($entity); + $contexts['view_mode'] = new Context(new ContextDefinition('string'), $entity->getMode()); } else { $contexts['entity'] = EntityContext::fromEntity($entity); diff --git a/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php b/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php index e8a21d12dc..493ed5dee5 100644 --- a/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php +++ b/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php @@ -156,6 +156,7 @@ protected function getEntity() { */ public function build() { $display_settings = $this->getConfiguration()['formatter']; + $display_settings['third_party_settings']['layout_builder']['view_mode'] = $this->getContextValue('view_mode'); $entity = $this->getEntity(); try { $build = $entity->get($this->fieldName)->view($display_settings); diff --git a/core/modules/layout_builder/src/Plugin/Derivative/FieldBlockDeriver.php b/core/modules/layout_builder/src/Plugin/Derivative/FieldBlockDeriver.php index 97ac811409..bc2b32cc17 100644 --- a/core/modules/layout_builder/src/Plugin/Derivative/FieldBlockDeriver.php +++ b/core/modules/layout_builder/src/Plugin/Derivative/FieldBlockDeriver.php @@ -9,6 +9,7 @@ use Drupal\Core\Field\FieldConfigInterface; use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\Field\FormatterPluginManager; +use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Plugin\Context\EntityContextDefinition; use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; @@ -123,6 +124,7 @@ public function getDerivativeDefinitions($base_plugin_definition) { $context_definition->addConstraint('Bundle', [$bundle]); $derivative['context_definitions'] = [ 'entity' => $context_definition, + 'view_mode' => (new ContextDefinition('string'))->setDefaultValue('default'), ]; $derivative_id = $entity_type_id . PluginBase::DERIVATIVE_SEPARATOR . $bundle . PluginBase::DERIVATIVE_SEPARATOR . $field_name; diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php index 8c6e8afb28..de8f1bfc45 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php @@ -2,6 +2,7 @@ namespace Drupal\layout_builder\Plugin\SectionStorage; +use Drupal\Component\Plugin\Context\ContextInterface as ComponentContextInterface; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Access\AccessResult; use Drupal\Core\Cache\RefinableCacheableDependencyInterface; @@ -33,6 +34,7 @@ * weight = 20, * context_definitions = { * "display" = @ContextDefinition("entity:entity_view_display"), + * "view_mode" = @ContextDefinition("string", default_value = "default"), * }, * ) * @@ -432,4 +434,15 @@ public function isApplicable(RefinableCacheableDependencyInterface $cacheability return $this->isLayoutBuilderEnabled(); } + /** + * {@inheritdoc} + */ + public function setContext($name, ComponentContextInterface $context) { + // Set the view mode context based on the display context. + if ($name === 'display') { + $this->setContextValue('view_mode', $context->getContextValue()->getMode()); + } + parent::setContext($name, $context); + } + } diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php index cde6f497c5..eb3e12ef9b 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php @@ -39,7 +39,7 @@ * "entity" = @ContextDefinition("entity", constraints = { * "EntityHasField" = \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage::FIELD_NAME, * }), - * "view_mode" = @ContextDefinition("string"), + * "view_mode" = @ContextDefinition("string", default_value = "default"), * } * ) * diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php b/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php index 8c6d506c1d..0f8543336f 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php @@ -2,6 +2,8 @@ namespace Drupal\layout_builder\Plugin\SectionStorage; +use Drupal\Core\Plugin\Context\Context; +use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Plugin\ContextAwarePluginBase; use Drupal\layout_builder\Routing\LayoutBuilderRoutesTrait; use Drupal\layout_builder\Section; @@ -109,7 +111,16 @@ public function removeAllSections($set_blank = FALSE) { * {@inheritdoc} */ public function getContextsDuringPreview() { - return $this->getContexts(); + $contexts = $this->getContexts(); + + // view_mode is a required context, but SectionStorage plugins are not + // required to return it (for example, the layout_library plugin provided + // in the Layout Library module. In these instances, explicitly create a + // view_mode context with the value "default". + if (!isset($contexts['view_mode']) || $contexts['view_mode']->validate()->count() || !$contexts['view_mode']->getContextValue()) { + $contexts['view_mode'] = new Context(new ContextDefinition('string'), 'default'); + } + return $contexts; } /** diff --git a/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/layout_builder_field_block_theme_suggestions_test.info.yml b/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/layout_builder_field_block_theme_suggestions_test.info.yml new file mode 100644 index 0000000000..589c256103 --- /dev/null +++ b/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/layout_builder_field_block_theme_suggestions_test.info.yml @@ -0,0 +1,6 @@ +name: 'Layout Builder Field Block Theme Suggestions Test' +type: module +description: 'Support module for testing.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/layout_builder_field_block_theme_suggestions_test.module b/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/layout_builder_field_block_theme_suggestions_test.module new file mode 100644 index 0000000000..729f3a3331 --- /dev/null +++ b/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/layout_builder_field_block_theme_suggestions_test.module @@ -0,0 +1,19 @@ + [ + 'base hook' => 'field', + ], + ]; +} diff --git a/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/templates/field--node--body--bundle-with-section-field--default.html.twig b/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/templates/field--node--body--bundle-with-section-field--default.html.twig new file mode 100644 index 0000000000..d48ff34267 --- /dev/null +++ b/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/templates/field--node--body--bundle-with-section-field--default.html.twig @@ -0,0 +1 @@ +

I am a field template for a specific view mode!

diff --git a/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/layout_builder_fieldblock_test.services.yml b/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/layout_builder_fieldblock_test.services.yml new file mode 100644 index 0000000000..ed7b1f92cf --- /dev/null +++ b/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/layout_builder_fieldblock_test.services.yml @@ -0,0 +1,5 @@ +services: + layout_builder_fieldblock_test.fake_view_mode_context: + class: Drupal\layout_builder_fieldblock_test\ContextProvider\FakeViewModeContext + tags: + - { name: 'context_provider' } diff --git a/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/src/ContextProvider/FakeViewModeContext.php b/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/src/ContextProvider/FakeViewModeContext.php new file mode 100644 index 0000000000..1dfbf8f296 --- /dev/null +++ b/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/src/ContextProvider/FakeViewModeContext.php @@ -0,0 +1,30 @@ + new Context(new ContextDefinition('string'), 'default')]; + } + + /** + * {@inheritdoc} + */ + public function getAvailableContexts() { + return $this->getRuntimeContexts([]); + } + +} diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderFieldBlockThemeSuggestionsTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderFieldBlockThemeSuggestionsTest.php new file mode 100644 index 0000000000..4d47586dff --- /dev/null +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderFieldBlockThemeSuggestionsTest.php @@ -0,0 +1,68 @@ +createContentType([ + 'type' => 'bundle_with_section_field', + 'name' => 'Bundle with section field', + ]); + $this->createNode([ + 'type' => 'bundle_with_section_field', + 'title' => 'A node title', + 'body' => [ + [ + 'value' => 'This is content that the template should not render', + ], + ], + ]); + + $this->drupalLogin($this->drupalCreateUser([ + 'configure any layout', + 'administer node display', + ])); + + $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default'); + $this->drupalPostForm(NULL, ['layout[enabled]' => TRUE], 'Save'); + } + + /** + * Tests that of view mode specific field templates are suggested. + */ + public function testFieldBlockViewModeTemplates() { + $assert_session = $this->assertSession(); + + $this->drupalGet('node/1'); + // Confirm that content is displayed by layout builder. + $assert_session->elementExists('css', '.block-layout-builder'); + // Text that only appears in the view mode specific template. + $assert_session->pageTextContains('I am a field template for a specific view mode!'); + // The content of the body field should not be visible because it is + // displayed via a template that does not render it. + $assert_session->pageTextNotContains('This is content that the template should not render'); + } + +} diff --git a/core/modules/layout_builder/tests/src/Kernel/DefaultsSectionStorageTest.php b/core/modules/layout_builder/tests/src/Kernel/DefaultsSectionStorageTest.php index 637da29947..1d854972f5 100644 --- a/core/modules/layout_builder/tests/src/Kernel/DefaultsSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/DefaultsSectionStorageTest.php @@ -2,7 +2,10 @@ namespace Drupal\Tests\layout_builder\Kernel; +use Drupal\Core\Plugin\Context\Context; +use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Plugin\Context\EntityContext; +use Drupal\Core\Plugin\Context\EntityContextDefinition; use Drupal\entity_test\Entity\EntityTest; use Drupal\KernelTests\KernelTestBase; use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay; @@ -51,7 +54,10 @@ protected function setUp() { $this->installEntitySchema('user'); $this->installConfig(['layout_builder_defaults_test']); - $this->plugin = DefaultsSectionStorage::create($this->container, [], 'defaults', new SectionStorageDefinition()); + $definition = (new SectionStorageDefinition()) + ->addContextDefinition('display', EntityContextDefinition::fromEntityTypeId('entity_view_display')) + ->addContextDefinition('view_mode', new ContextDefinition('string')); + $this->plugin = DefaultsSectionStorage::create($this->container, [], 'defaults', $definition); } /** @@ -141,8 +147,9 @@ public function testGetContexts() { $context = EntityContext::fromEntity($display); $this->plugin->setContext('display', $context); - $expected = ['display' => $context]; - $this->assertSame($expected, $this->plugin->getContexts()); + $result = $this->plugin->getContexts(); + $this->assertSame(['view_mode', 'display'], array_keys($result)); + $this->assertSame($context, $result['display']); } /** @@ -161,7 +168,7 @@ public function testGetContextsDuringPreview() { $this->plugin->setContext('display', $context); $result = $this->plugin->getContextsDuringPreview(); - $this->assertEquals(['display', 'layout_builder.entity'], array_keys($result)); + $this->assertSame(['view_mode', 'display', 'layout_builder.entity'], array_keys($result)); $this->assertSame($context, $result['display']); @@ -169,6 +176,10 @@ public function testGetContextsDuringPreview() { $result_value = $result['layout_builder.entity']->getContextValue(); $this->assertInstanceOf(EntityTest::class, $result_value); $this->assertSame('entity_test', $result_value->bundle()); + + $this->assertInstanceOf(Context::class, $result['view_mode']); + $result_value = $result['view_mode']->getContextValue(); + $this->assertSame('default', $result_value); } /** @@ -202,4 +213,24 @@ public function testGetTempstoreKey() { $this->assertSame('entity_test.entity_test.default', $result); } + /** + * Tests loading given a display. + */ + public function testLoadFromDisplay() { + $display = LayoutBuilderEntityViewDisplay::create([ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'default', + 'status' => TRUE, + ]); + $display->save(); + $contexts = [ + 'display' => EntityContext::fromEntity($display), + ]; + + $section_storage_manager = $this->container->get('plugin.manager.layout_builder.section_storage'); + $section_storage = $section_storage_manager->load('defaults', $contexts); + $this->assertInstanceOf(DefaultsSectionStorage::class, $section_storage); + } + } diff --git a/core/modules/layout_builder/tests/src/Kernel/FieldBlockTest.php b/core/modules/layout_builder/tests/src/Kernel/FieldBlockTest.php index 4675046a89..d08a65bad9 100644 --- a/core/modules/layout_builder/tests/src/Kernel/FieldBlockTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/FieldBlockTest.php @@ -10,6 +10,7 @@ use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FormatterPluginManager; +use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Form\EnforcedResponseException; use Drupal\Core\Plugin\Context\EntityContextDefinition; use Drupal\Core\Session\AccountInterface; @@ -210,6 +211,7 @@ protected function getTestBlock(ProphecyInterface $entity_prophecy, array $confi 'bundles' => ['entity_test'], 'context_definitions' => [ 'entity' => EntityContextDefinition::fromEntityTypeId('entity_test')->setLabel('Test'), + 'view_mode' => new ContextDefinition('string'), ], ]; $formatter_manager = $this->prophesize(FormatterPluginManager::class); @@ -225,6 +227,7 @@ protected function getTestBlock(ProphecyInterface $entity_prophecy, array $confi $this->logger->reveal() ); $block->setContextValue('entity', $entity_prophecy->reveal()); + $block->setContextValue('view_mode', 'default'); return $block; } diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutEntityHelperTraitTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutEntityHelperTraitTest.php index 54cc87d5b1..dc9c412ddb 100644 --- a/core/modules/layout_builder/tests/src/Kernel/LayoutEntityHelperTraitTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutEntityHelperTraitTest.php @@ -64,7 +64,7 @@ public function providerTestGetSectionStorageForEntity() { ], ], ], - ['display'], + ['display', 'view_mode'], ]; $data['fieldable entity'] = [ 'entity_test', diff --git a/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php b/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php index f36b8fbc94..9d20508dee 100644 --- a/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php @@ -2,13 +2,17 @@ namespace Drupal\Tests\layout_builder\Unit; +use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityType; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Plugin\Context\ContextInterface; +use Drupal\Core\Plugin\Context\EntityContextDefinition; +use Drupal\Core\TypedData\TypedDataManagerInterface; use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface; use Drupal\layout_builder\Entity\SampleEntityGeneratorInterface; use Drupal\layout_builder\Plugin\SectionStorage\DefaultsSectionStorage; @@ -67,6 +71,17 @@ protected function setUp() { * @covers ::setThirdPartySetting */ public function testThirdPartySettings() { + $this->entityTypeManager->getDefinition('entity_view_display')->willReturn(new EntityType(['id' => 'entity_view_display'])); + + $container = new ContainerBuilder(); + $container->set('typed_data_manager', $this->prophesize(TypedDataManagerInterface::class)->reveal()); + $container->set('entity_type.manager', $this->entityTypeManager->reveal()); + \Drupal::setContainer($container); + + $this->plugin->getPluginDefinition() + ->addContextDefinition('display', EntityContextDefinition::fromEntityTypeId('entity_view_display')) + ->addContextDefinition('view_mode', new ContextDefinition('string')); + // Set an initial value on the section list. $section_list = $this->prophesize(LayoutEntityDisplayInterface::class);