diff --git a/core/modules/layout_builder/src/Element/LayoutBuilder.php b/core/modules/layout_builder/src/Element/LayoutBuilder.php index 7cc979d342..df34477ab1 100644 --- a/core/modules/layout_builder/src/Element/LayoutBuilder.php +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php @@ -142,20 +142,17 @@ protected function prepareLayout(SectionStorageInterface $section_storage) { } // If the layout is an override that has not yet been overridden, copy the // sections from the corresponding default. - elseif ($section_storage instanceof OverridesSectionStorageInterface) { - $source_sections = []; - if ($section_storage->isTranslatable() && !$section_storage->isOverridden() && !$section_storage->isDefaultTranslation()) { - $default_translation_section_storage = $section_storage->getDefaultTranslationSectionStorage(); - $source_sections = $default_translation_section_storage->getSections(); + elseif ($section_storage instanceof OverridesSectionStorageInterface && !$section_storage->isOverridden()) { + if ($section_storage->isTranslatable() && !$section_storage->isDefaultTranslation()) { + $source_storage = $section_storage->getDefaultTranslationSectionStorage(); } - elseif (!$section_storage->isOverridden()) { - $source_sections = $section_storage->getDefaultSectionStorage()->getSections(); + else { + $source_storage = $section_storage->getDefaultSectionStorage(); } - foreach ($source_sections as $section) { + foreach ($source_storage->getSections() as $section) { $section_storage->appendSection($section); } $this->layoutTempstoreRepository->set($section_storage); - } } diff --git a/core/modules/layout_builder/src/OverridesSectionStorageInterface.php b/core/modules/layout_builder/src/OverridesSectionStorageInterface.php index f4de1f2bbb..b00d04d488 100644 --- a/core/modules/layout_builder/src/OverridesSectionStorageInterface.php +++ b/core/modules/layout_builder/src/OverridesSectionStorageInterface.php @@ -31,28 +31,4 @@ public function getDefaultSectionStorage(); */ public function isOverridden(); - /** - * Indicates if the layout is translatable. - * - * @return bool - * TRUE if the layout is translatable, otherwise FALSE. - */ - public function isTranslatable(); - - /** - * Indicates if the layout is default translation layout. - * - * @return bool - * TRUE if the layout is the default translation layout, otherwise FALSE. - */ - public function isDefaultTranslation(); - - /** - * Gets the default translation section storage. - * - * @return \Drupal\layout_builder\SectionStorageInterface - * The section storage used by the default translation. - */ - public function getDefaultTranslationSectionStorage(); - } diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php index 843e8220b7..a563d0b59e 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php @@ -22,6 +22,7 @@ use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay; use Drupal\layout_builder\OverridesSectionStorageInterface; use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface; +use Drupal\layout_builder\TranslatableOverridesSectionStorageInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Routing\RouteCollection; @@ -48,7 +49,7 @@ * experimental modules and development releases of contributed modules. * See https://www.drupal.org/core/experimental for more information. */ -class OverridesSectionStorage extends SectionStorageBase implements ContainerFactoryPluginInterface, OverridesSectionStorageInterface, SectionStorageLocalTaskProviderInterface { +class OverridesSectionStorage extends SectionStorageBase implements ContainerFactoryPluginInterface, TranslatableOverridesSectionStorageInterface, SectionStorageLocalTaskProviderInterface { /** * The field name used by this storage. @@ -175,7 +176,7 @@ public function extractIdFromRoute($value, $definition, $name, array $defaults) public function getSectionListFromId($id) { @trigger_error('\Drupal\layout_builder\SectionStorageInterface::getSectionListFromId() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. The section list should be derived from context. See https://www.drupal.org/node/3016262.', E_USER_DEPRECATED); if (strpos($id, '.') !== FALSE) { - list($entity_type_id, $entity_id) = explode('.', $id, 2); + list($entity_type_id, $entity_id) = explode('.', $id); $entity = $this->entityRepository->getActive($entity_type_id, $entity_id); if ($entity instanceof FieldableEntityInterface && $entity->hasField(static::FIELD_NAME)) { return $entity->get(static::FIELD_NAME); @@ -363,6 +364,11 @@ public function save() { public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) { $default_section_storage = $this->getDefaultSectionStorage(); $result = AccessResult::allowedIf($default_section_storage->isLayoutBuilderEnabled())->addCacheableDependency($default_section_storage); + $entity = $this->getEntity(); + $result->addCacheableDependency($entity); + if ($entity instanceof TranslatableInterface && !$entity->isDefaultTranslation()) { + $result = $result->andIf(AccessResult::allowedIf($this->isTranslatable())); + } return $return_as_object ? $result : $result->isAllowed(); } @@ -433,8 +439,14 @@ public function getDefaultTranslationSectionStorage() { /** @var \Drupal\Core\Entity\TranslatableInterface $entity */ $entity = $this->getEntity(); $untranslated_entity = $entity->getUntranslated(); + // @todo Expand to work for all view modes in + // https://www.drupal.org/node/2907413. + $view_mode = 'full'; + // Retrieve the actual view mode from the returned view display as the + // requested view mode may not exist and a fallback will be used. + $view_mode = LayoutBuilderEntityViewDisplay::collectRenderDisplay($entity, $view_mode)->getMode(); $contexts = [ - 'view_mode' => new Context(ContextDefinition::create('string'), 'full'), + 'view_mode' => new Context(ContextDefinition::create('string'), $view_mode), 'entity' => EntityContext::fromEntity($untranslated_entity), 'display' => EntityContext::fromEntity(EntityViewDisplay::collectRenderDisplay($untranslated_entity, 'full')), ]; diff --git a/core/modules/layout_builder/src/OverridesSectionStorageInterface.php b/core/modules/layout_builder/src/TranslatableOverridesSectionStorageInterface.php similarity index 56% copy from core/modules/layout_builder/src/OverridesSectionStorageInterface.php copy to core/modules/layout_builder/src/TranslatableOverridesSectionStorageInterface.php index f4de1f2bbb..d20cd7c042 100644 --- a/core/modules/layout_builder/src/OverridesSectionStorageInterface.php +++ b/core/modules/layout_builder/src/TranslatableOverridesSectionStorageInterface.php @@ -3,33 +3,14 @@ namespace Drupal\layout_builder; /** - * Defines an interface for an object that stores layout sections for overrides. + * Defines an interface for translatable section overrides. * * @internal * Layout Builder is currently experimental and should only be leveraged by * experimental modules and development releases of contributed modules. * See https://www.drupal.org/core/experimental for more information. */ -interface OverridesSectionStorageInterface extends SectionStorageInterface { - - /** - * Returns the corresponding defaults section storage for this override. - * - * @return \Drupal\layout_builder\DefaultsSectionStorageInterface - * The defaults section storage. - * - * @todo Determine if this method needs a parameter in - * https://www.drupal.org/project/drupal/issues/2936507. - */ - public function getDefaultSectionStorage(); - - /** - * Indicates if overrides are in use. - * - * @return bool - * TRUE if this overrides section storage is in use, otherwise FALSE. - */ - public function isOverridden(); +interface TranslatableOverridesSectionStorageInterface extends OverridesSectionStorageInterface { /** * Indicates if the layout is translatable. diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTranslationTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTranslationTest.php index fd3098d5a7..ebf2c83e8f 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTranslationTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTranslationTest.php @@ -2,8 +2,6 @@ namespace Drupal\Tests\layout_builder\Functional; -use Drupal\Core\Entity\Entity\EntityViewDisplay; -use Drupal\Core\Url; use Drupal\Tests\content_translation\Functional\ContentTranslationTestBase; /** @@ -13,12 +11,7 @@ */ class LayoutBuilderTranslationTest extends ContentTranslationTestBase { - /** - * The entity used for testing. - * - * @var \Drupal\Core\Entity\EntityInterface - */ - protected $entity; + use TranslationTestTrait; /** * {@inheritdoc} @@ -35,70 +28,8 @@ class LayoutBuilderTranslationTest extends ContentTranslationTestBase { */ protected function setUp() { parent::setUp(); - - $this->drupalLogin($this->administrator); - - $field_ui_prefix = 'entity_test_mul/structure/entity_test_mul'; - // Allow overrides for the layout. - $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save'); - $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save'); - - // @todo The Layout Builder UI relies on local tasks; fix in - // https://www.drupal.org/project/drupal/issues/2917777. - $this->drupalPlaceBlock('local_tasks_block'); - - // Create a test entity. - $id = $this->createEntity([ - $this->fieldName => [['value' => 'The untranslated field value']], - ], $this->langcodes[0]); - $storage = $this->container->get('entity_type.manager')->getStorage($this->entityTypeId); - $storage->resetCache([$id]); - $this->entity = $storage->load($id); - - // Create a translation. - $this->drupalLogin($this->translator); - $add_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_add", [ - $this->entityTypeId => $this->entity->id(), - 'source' => $this->langcodes[0], - 'target' => $this->langcodes[2], - ]); - $this->drupalPostForm($add_translation_url, [ - "{$this->fieldName}[0][value]" => 'The translated field value', - ], 'Save'); - } - - /** - * {@inheritdoc} - */ - protected function setupTestFields() { - parent::setupTestFields(); - - EntityViewDisplay::create([ - 'targetEntityType' => $this->entityTypeId, - 'bundle' => $this->bundle, - 'mode' => 'default', - 'status' => TRUE, - ])->setComponent($this->fieldName, ['type' => 'string'])->save(); - } - - /** - * {@inheritdoc} - */ - protected function getAdministratorPermissions() { - $permissions = parent::getAdministratorPermissions(); - $permissions[] = 'administer entity_test_mul display'; - return $permissions; - } - - /** - * {@inheritdoc} - */ - protected function getTranslatorPermissions() { - $permissions = parent::getTranslatorPermissions(); - $permissions[] = 'view test entity translations'; - $permissions[] = 'view test entity'; - $permissions[] = 'configure any layout'; - return $permissions; + $this->setUpViewDisplay(); + $this->setUpEntities(); } /** @@ -247,4 +178,5 @@ public function testLayoutTranslationFromDefault() { $assert_session->pageTextNotContains('The translated field value'); $assert_session->pageTextNotContains('Powered by Drupal'); } + } diff --git a/core/modules/layout_builder/tests/src/Functional/TranslationTestTrait.php b/core/modules/layout_builder/tests/src/Functional/TranslationTestTrait.php new file mode 100644 index 0000000000..a7d33d2a1a --- /dev/null +++ b/core/modules/layout_builder/tests/src/Functional/TranslationTestTrait.php @@ -0,0 +1,88 @@ +drupalLogin($this->administrator); + + $field_ui_prefix = 'entity_test_mul/structure/entity_test_mul'; + // Allow overrides for the layout. + $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save'); + $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save'); + + // @todo The Layout Builder UI relies on local tasks; fix in + // https://www.drupal.org/project/drupal/issues/2917777. + $this->drupalPlaceBlock('local_tasks_block'); + + // Create a test entity. + $id = $this->createEntity([ + $this->fieldName => [['value' => 'The untranslated field value']], + ], $this->langcodes[0]); + $storage = $this->container->get('entity_type.manager') + ->getStorage($this->entityTypeId); + $storage->resetCache([$id]); + $this->entity = $storage->load($id); + + // Create a translation. + $this->drupalLogin($this->translator); + $add_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_add", [ + $this->entityTypeId => $this->entity->id(), + 'source' => $this->langcodes[0], + 'target' => $this->langcodes[2], + ]); + $this->drupalPostForm($add_translation_url, [ + "{$this->fieldName}[0][value]" => 'The translated field value', + ], 'Save'); + } + + /** + * Set up the View Display. + */ + protected function setUpViewDisplay() { + EntityViewDisplay::create([ + 'targetEntityType' => $this->entityTypeId, + 'bundle' => $this->bundle, + 'mode' => 'default', + 'status' => TRUE, + ])->setComponent($this->fieldName, ['type' => 'string'])->save(); + } + +} diff --git a/core/modules/layout_builder/tests/src/Functional/UntranslatableLayoutTest.php b/core/modules/layout_builder/tests/src/Functional/UntranslatableLayoutTest.php new file mode 100644 index 0000000000..daf54b7de5 --- /dev/null +++ b/core/modules/layout_builder/tests/src/Functional/UntranslatableLayoutTest.php @@ -0,0 +1,70 @@ +setUpViewDisplay(); + $this->setUpEntities(); + + $field_config = FieldConfig::loadByName($this->entityTypeId, $this->bundle, OverridesSectionStorage::FIELD_NAME); + $field_config->setTranslatable(FALSE); + $field_config->save(); + } + + /** + * Tests that layout translations are not available. + */ + public function testLayoutTranslationDenied() { + $assert_session = $this->assertSession(); + + $entity_url = $this->entity->toUrl('canonical')->toString(); + $language = \Drupal::languageManager()->getLanguage($this->langcodes[2]); + $translated_entity_url = $this->entity->toUrl('canonical', ['language' => $language])->toString(); + $layout_url = $entity_url . '/layout'; + $translated_layout_url = $translated_entity_url . '/layout'; + + $this->drupalGet($entity_url); + $assert_session->pageTextNotContains('The translated field value'); + $assert_session->pageTextContains('The untranslated field value'); + $assert_session->linkExists('Layout'); + + $this->drupalGet($translated_entity_url); + $assert_session->pageTextNotContains('The untranslated field value'); + $assert_session->pageTextContains('The translated field value'); + $assert_session->linkNotExists('Layout'); + + $this->drupalGet($layout_url); + $assert_session->pageTextNotContains('Access denied'); + + $this->drupalGet($translated_layout_url); + $assert_session->pageTextContains('Access denied'); + } + +}