diff --git a/core/modules/layout_builder/layout_builder.routing.yml b/core/modules/layout_builder/layout_builder.routing.yml
index f034a5a1b2..1cfb25e3fd 100644
--- a/core/modules/layout_builder/layout_builder.routing.yml
+++ b/core/modules/layout_builder/layout_builder.routing.yml
@@ -6,7 +6,7 @@ layout_builder.choose_section:
requirements:
_permission: 'configure any layout'
_layout_builder_access: 'view'
- _layout_builder_translation_access: 'view'
+ _layout_builder_translation_access: 'untranslated'
options:
_admin_route: TRUE
parameters:
@@ -20,7 +20,7 @@ layout_builder.add_section:
requirements:
_permission: 'configure any layout'
_layout_builder_access: 'view'
- _layout_builder_translation_access: 'view'
+ _layout_builder_translation_access: 'untranslated'
options:
_admin_route: TRUE
parameters:
@@ -38,7 +38,7 @@ layout_builder.configure_section:
requirements:
_permission: 'configure any layout'
_layout_builder_access: 'view'
- _layout_builder_translation_access: 'view'
+ _layout_builder_translation_access: 'untranslated'
options:
_admin_route: TRUE
parameters:
@@ -52,7 +52,7 @@ layout_builder.remove_section:
requirements:
_permission: 'configure any layout'
_layout_builder_access: 'view'
- _layout_builder_translation_access: 'view'
+ _layout_builder_translation_access: 'untranslated'
options:
_admin_route: TRUE
parameters:
@@ -67,7 +67,7 @@ layout_builder.choose_block:
requirements:
_permission: 'configure any layout'
_layout_builder_access: 'view'
- _layout_builder_translation_access: 'view'
+ _layout_builder_translation_access: 'untranslated'
options:
_admin_route: TRUE
parameters:
@@ -82,7 +82,7 @@ layout_builder.add_block:
requirements:
_permission: 'configure any layout'
_layout_builder_access: 'view'
- _layout_builder_translation_access: 'view'
+ _layout_builder_translation_access: 'untranslated'
options:
_admin_route: TRUE
parameters:
@@ -96,7 +96,7 @@ layout_builder.choose_inline_block:
_title: 'Add a new Inline Block'
requirements:
_permission: 'configure any layout'
- _layout_builder_translation_access: 'view'
+ _layout_builder_translation_access: 'untranslated'
options:
_admin_route: TRUE
parameters:
@@ -111,7 +111,7 @@ layout_builder.update_block:
requirements:
_permission: 'configure any layout'
_layout_builder_access: 'view'
- _layout_builder_translation_access: 'view'
+ _layout_builder_translation_access: 'untranslated'
options:
_admin_route: TRUE
parameters:
@@ -126,6 +126,7 @@ layout_builder.translate_block:
requirements:
_permission: 'configure any layout'
_layout_builder_access: 'view'
+ _layout_builder_translation_access: 'translated'
options:
_admin_route: TRUE
parameters:
@@ -139,7 +140,7 @@ layout_builder.remove_block:
requirements:
_permission: 'configure any layout'
_layout_builder_access: 'view'
- _layout_builder_translation_access: 'view'
+ _layout_builder_translation_access: 'untranslated'
options:
_admin_route: TRUE
parameters:
@@ -159,7 +160,7 @@ layout_builder.move_block:
requirements:
_permission: 'configure any layout'
_layout_builder_access: 'view'
- _layout_builder_translation_access: 'view'
+ _layout_builder_translation_access: 'untranslated'
options:
_admin_route: TRUE
parameters:
diff --git a/core/modules/layout_builder/src/Access/LayoutBuilderTranslationAccessCheck.php b/core/modules/layout_builder/src/Access/LayoutBuilderTranslationAccessCheck.php
index f3df40112a..a4f016a9aa 100644
--- a/core/modules/layout_builder/src/Access/LayoutBuilderTranslationAccessCheck.php
+++ b/core/modules/layout_builder/src/Access/LayoutBuilderTranslationAccessCheck.php
@@ -7,6 +7,7 @@
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\layout_builder\SectionStorageInterface;
use Drupal\layout_builder\TranslatableSectionStorageInterface;
+use Symfony\Component\Routing\Route;
/**
* Provides an access check for the Layout Builder translations.
@@ -23,15 +24,24 @@ class LayoutBuilderTranslationAccessCheck implements AccessInterface {
*
* @param \Drupal\layout_builder\SectionStorageInterface $section_storage
* The section storage.
+ * @param \Symfony\Component\Routing\Route $route
+ * The route to check against.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
- public function access(SectionStorageInterface $section_storage) {
- $access = AccessResult::allowedIf(!($section_storage instanceof TranslatableSectionStorageInterface && !$section_storage->isDefaultTranslation()));
+ public function access(SectionStorageInterface $section_storage, Route $route) {
+ $translation_type = $route->getRequirement('_layout_builder_translation_access');
+ if ($translation_type === 'untranslated') {
+ $access = AccessResult::allowedIf(!$section_storage instanceof TranslatableSectionStorageInterface || $section_storage->isDefaultTranslation());
+ }
+ elseif ($translation_type === 'translated') {
+ $access = AccessResult::allowedIf($section_storage instanceof TranslatableSectionStorageInterface && !$section_storage->isDefaultTranslation());
+ }
if ($access instanceof RefinableCacheableDependencyInterface) {
$access->addCacheableDependency($section_storage);
}
return $access;
}
+
}
diff --git a/core/modules/layout_builder/src/Element/LayoutBuilder.php b/core/modules/layout_builder/src/Element/LayoutBuilder.php
index 9b05fe9d26..2e3b021f62 100644
--- a/core/modules/layout_builder/src/Element/LayoutBuilder.php
+++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
@@ -108,7 +108,7 @@ public function preRender($element) {
*/
protected function layout(SectionStorageInterface $section_storage) {
$this->prepareLayout($section_storage);
- $is_translation = $section_storage instanceof TranslatableSectionStorageInterface && !$section_storage->isDefaultTranslation();
+ $sections_editable = !($section_storage instanceof TranslatableSectionStorageInterface && !$section_storage->isDefaultTranslation());
$output = [];
if ($this->isAjax()) {
@@ -118,14 +118,14 @@ protected function layout(SectionStorageInterface $section_storage) {
}
$count = 0;
for ($i = 0; $i < $section_storage->count(); $i++) {
- if (!$is_translation) {
+ if ($sections_editable) {
$output[] = $this->buildAddSectionLink($section_storage, $count);
}
$output[] = $this->buildAdministrativeSection($section_storage, $count);
$count++;
}
- if (!$is_translation) {
+ if ($sections_editable) {
$output[] = $this->buildAddSectionLink($section_storage, $count);
}
@@ -158,7 +158,7 @@ protected function prepareLayout(SectionStorageInterface $section_storage) {
// changed. This should avoid data loss if the layout has been
// updated since this layout override has started. This probably also
// needs to be done on save to avoid overriding the layout if it was
- // save since the last time this page was opened.
+ // saved since the last time this page was opened.
}
}
// If the layout is an override that has not yet been overridden, copy the
@@ -253,8 +253,7 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s
$storage_type = $section_storage->getStorageType();
$storage_id = $section_storage->getStorageId();
$section = $section_storage->getSection($delta);
- $is_translation = $section_storage instanceof TranslatableSectionStorageInterface && !$section_storage->isDefaultTranslation();
- $contextual_link_group = $is_translation ? 'layout_builder_block_translation' : 'layout_builder_block';
+ $sections_editable = !($section_storage instanceof TranslatableSectionStorageInterface && !$section_storage->isDefaultTranslation());
$layout = $section->getLayout();
$build = $section->toRenderArray($this->getAvailableContexts($section_storage), TRUE);
$layout_definition = $layout->getPluginDefinition();
@@ -263,56 +262,58 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s
foreach ($layout_definition->getRegions() as $region => $info) {
if (!empty($build[$region])) {
foreach (Element::children($build[$region]) as $uuid) {
- if (!$is_translation) {
+ if ($sections_editable) {
$build[$region][$uuid]['#attributes']['class'][] = 'draggable';
}
- $component = $section->getComponent($uuid);
-
- $needs_translation_link = $this->defaultComponentHasTranslatableSettings($section_storage, $component);
$build[$region][$uuid]['#attributes']['data-layout-block-uuid'] = $uuid;
- if (!$is_translation || $needs_translation_link) {
+ $contextual_link_settings = [
+ 'route_parameters' => [
+ 'section_storage_type' => $storage_type,
+ 'section_storage' => $storage_id,
+ 'delta' => $delta,
+ 'region' => $region,
+ 'uuid' => $uuid,
+ ],
+ ];
+ if ($sections_editable) {
$build[$region][$uuid]['#contextual_links'] = [
- $contextual_link_group => [
- 'route_parameters' => [
- 'section_storage_type' => $storage_type,
- 'section_storage' => $storage_id,
- 'delta' => $delta,
- 'region' => $region,
- 'uuid' => $uuid,
- ],
- ],
+ 'layout_builder_block' => $contextual_link_settings,
+ ];
+ }
+ elseif ($this->defaultComponentHasTranslatableSettings($section_storage, $component = $section->getComponent($uuid))) {
+ $build[$region][$uuid]['#contextual_links'] = [
+ 'layout_builder_block_translation' => $contextual_link_settings,
];
}
}
}
- if (!$is_translation) {
- $build[$region]['layout_builder_add_block']['link'] = [
- '#type' => 'link',
- // Add one to the current delta since it is zero-indexed.
- '#title' => $this->t('Add Block in section @section, @region region', ['@section' => $delta + 1, '@region' => $region_labels[$region]]),
- '#url' => Url::fromRoute('layout_builder.choose_block',
- [
- 'section_storage_type' => $storage_type,
- 'section_storage' => $storage_id,
- 'delta' => $delta,
- 'region' => $region,
- ],
- [
- 'attributes' => [
- 'class' => [
- 'use-ajax',
- 'layout-builder__link',
- 'layout-builder__link--add',
- ],
- 'data-dialog-type' => 'dialog',
- 'data-dialog-renderer' => 'off_canvas',
+ $build[$region]['layout_builder_add_block']['link'] = [
+ '#type' => 'link',
+ '#access' => $sections_editable,
+ // Add one to the current delta since it is zero-indexed.
+ '#title' => $this->t('Add Block in section @section, @region region', ['@section' => $delta + 1, '@region' => $region_labels[$region]]),
+ '#url' => Url::fromRoute('layout_builder.choose_block',
+ [
+ 'section_storage_type' => $storage_type,
+ 'section_storage' => $storage_id,
+ 'delta' => $delta,
+ 'region' => $region,
+ ],
+ [
+ 'attributes' => [
+ 'class' => [
+ 'use-ajax',
+ 'layout-builder__link',
+ 'layout-builder__link--add',
],
- ]
- ),
- ];
- }
+ 'data-dialog-type' => 'dialog',
+ 'data-dialog-renderer' => 'off_canvas',
+ ],
+ ]
+ ),
+ ];
$build[$region]['layout_builder_add_block']['#type'] = 'container';
$build[$region]['layout_builder_add_block']['#attributes'] = ['class' => ['layout-builder__add-block']];
$build[$region]['layout_builder_add_block']['#weight'] = 1000;
@@ -327,60 +328,59 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s
$build['#attributes']['data-layout-delta'] = $delta;
$build['#attributes']['class'][] = 'layout-builder__layout';
- $section_container = [
+ return [
'#type' => 'container',
'#attributes' => [
'class' => ['layout-builder__section'],
],
- 'layout-builder__section' => $build,
- ];
- if (!$is_translation) {
- $section_container += [
- 'remove' => [
- '#type' => 'link',
- '#title' => $this->t('Remove section @section', ['@section' => $delta + 1]),
- '#url' => Url::fromRoute('layout_builder.remove_section', [
- 'section_storage_type' => $storage_type,
- 'section_storage' => $storage_id,
- 'delta' => $delta,
- ]),
- '#attributes' => [
- 'class' => [
- 'use-ajax',
- 'layout-builder__link',
- 'layout-builder__link--remove',
- ],
- 'data-dialog-type' => 'dialog',
- 'data-dialog-renderer' => 'off_canvas',
+ 'remove' => [
+ '#type' => 'link',
+ '#access' => $sections_editable,
+ '#title' => $this->t('Remove section @section', ['@section' => $delta + 1]),
+ '#url' => Url::fromRoute('layout_builder.remove_section', [
+ 'section_storage_type' => $storage_type,
+ 'section_storage' => $storage_id,
+ 'delta' => $delta,
+ ]),
+ '#attributes' => [
+ 'class' => [
+ 'use-ajax',
+ 'layout-builder__link',
+ 'layout-builder__link--remove',
],
+ 'data-dialog-type' => 'dialog',
+ 'data-dialog-renderer' => 'off_canvas',
],
- 'configure' => [
- '#type' => 'link',
- '#title' => $this->t('Configure section'),
- '#access' => $layout instanceof PluginFormInterface,
- '#url' => Url::fromRoute('layout_builder.configure_section', [
- 'section_storage_type' => $storage_type,
- 'section_storage' => $storage_id,
- 'delta' => $delta,
- ]),
- '#attributes' => [
- 'class' => [
- 'use-ajax',
- 'layout-builder__link',
- 'layout-builder__link--configure',
- ],
- 'data-dialog-type' => 'dialog',
- 'data-dialog-renderer' => 'off_canvas',
+ ],
+ 'configure' => [
+ '#type' => 'link',
+ '#title' => $this->t('Configure section'),
+ '#access' => $layout instanceof PluginFormInterface && $sections_editable,
+ '#url' => Url::fromRoute('layout_builder.configure_section', [
+ 'section_storage_type' => $storage_type,
+ 'section_storage' => $storage_id,
+ 'delta' => $delta,
+ ]),
+ '#attributes' => [
+ 'class' => [
+ 'use-ajax',
+ 'layout-builder__link',
+ 'layout-builder__link--configure',
],
+ 'data-dialog-type' => 'dialog',
+ 'data-dialog-renderer' => 'off_canvas',
],
- ];
- }
- return $section_container;
+ ],
+ 'layout-builder__section' => $build,
+ ];
}
/**
* Determines if the component in the default translation is translatable.
*
+ * @todo determine how handle other settings that need to be translated
+ * such as the inline blocks use case.
+ *
* @param \Drupal\layout_builder\SectionStorageInterface $section_storage
* The section storage.
* @param \Drupal\layout_builder\SectionComponent $component
diff --git a/core/modules/layout_builder/src/EventSubscriber/ComponentPluginLabelTranslate.php b/core/modules/layout_builder/src/EventSubscriber/ComponentPluginLabelTranslate.php
index 88fcf6e124..fd489458ef 100644
--- a/core/modules/layout_builder/src/EventSubscriber/ComponentPluginLabelTranslate.php
+++ b/core/modules/layout_builder/src/EventSubscriber/ComponentPluginLabelTranslate.php
@@ -29,13 +29,13 @@ public static function getSubscribedEvents() {
public function onBuildRender(SectionComponentBuildRenderArrayEvent $event) {
$plugin = $event->getPlugin();
$contexts = $event->getContexts();
- if (!$plugin instanceof ConfigurableInterface && !isset($contexts['@language.current_language_context:language_interface'])) {
+ if (!$plugin instanceof ConfigurableInterface && !isset($contexts['layout_builder.entity'])) {
return;
}
- /** @var \Drupal\Core\Language\Language $language */
- $language = $contexts['@language.current_language_context:language_interface']->getContextValue();
- $langcode = $language->getId();
+ /** @var \Drupal\Core\Entity\EntityInterface $entity */
+ $entity = $contexts['layout_builder.entity']->getContextValue();
+ $langcode = $entity->language()->getId();
$configuration = $plugin->getConfiguration();
if (isset($configuration['label']) && isset($configuration['layout_builder_translations'][$langcode]['label'])) {
$configuration['label'] = $configuration['layout_builder_translations'][$langcode]['label'];
diff --git a/core/modules/layout_builder/src/Form/BlockPluginTranslationForm.php b/core/modules/layout_builder/src/Form/BlockPluginTranslationForm.php
index 93ba86a782..af701ada2d 100644
--- a/core/modules/layout_builder/src/Form/BlockPluginTranslationForm.php
+++ b/core/modules/layout_builder/src/Form/BlockPluginTranslationForm.php
@@ -6,7 +6,6 @@
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormBase;
-use Drupal\Core\Render\Element;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -17,15 +16,21 @@ class BlockPluginTranslationForm extends PluginFormBase implements ContainerInje
use StringTranslationTrait;
- protected $current_language;
+ /**
+ * The current language code.
+ *
+ * @var string
+ */
+ protected $currentLangcode;
/**
* BlockPluginTranslationForm constructor.
*
- * @param $current_language
+ * @param string $current_langcode
+ * The current language code.
*/
- public function __construct($current_language) {
- $this->current_language = $current_language;
+ public function __construct($current_langcode) {
+ $this->currentLangcode = $current_langcode;
}
@@ -44,12 +49,12 @@ public static function create(ContainerInterface $container) {
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
if ($this->plugin instanceof ConfigurableInterface) {
$configuration = $this->plugin->getConfiguration();
- $form['translated_label'] = [
- '#title' => $this->t('Translated label'),
- '#type' => 'textfield',
- '#default_value' => isset($configuration['layout_builder_translations'][$this->current_language]['label']) ? $configuration['layout_builder_translations'][$this->current_language]['label'] : $configuration['label'],
- '#required' => TRUE,
- ];
+ $form['translated_label'] = [
+ '#title' => $this->t('Label'),
+ '#type' => 'textfield',
+ '#default_value' => isset($configuration['layout_builder_translations'][$this->currentLangcode]['label']) ? $configuration['layout_builder_translations'][$this->currentLangcode]['label'] : $configuration['label'],
+ '#required' => TRUE,
+ ];
}
return $form;
}
@@ -66,19 +71,9 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
if ($this->plugin instanceof ConfigurableInterface) {
$configuration = $this->plugin->getConfiguration();
- $configuration['layout_builder_translations'][$this->current_language]['label'] = $form_state->getValue('translated_label');
+ $configuration['layout_builder_translations'][$this->currentLangcode]['label'] = $form_state->getValue('translated_label');
$this->plugin->setConfiguration($configuration);
}
}
- /**
- *
- * @return \Drupal\Core\Plugin\PluginFormInterface
- */
- protected function getConfigureForm() {
- /** @var \Drupal\Core\Plugin\PluginFormFactoryInterface $form_factory */
- $form_factory = \Drupal::service('plugin_form.factory');
- return $form_factory->createInstance($this->plugin, 'configure');
- }
-
}
diff --git a/core/modules/layout_builder/src/Form/TranslateBlockForm.php b/core/modules/layout_builder/src/Form/TranslateBlockForm.php
index 67d845fe6c..e957ad7f59 100644
--- a/core/modules/layout_builder/src/Form/TranslateBlockForm.php
+++ b/core/modules/layout_builder/src/Form/TranslateBlockForm.php
@@ -16,7 +16,7 @@ class TranslateBlockForm extends ConfigureBlockFormBase {
* {@inheritdoc}
*/
protected function submitLabel() {
- $this->t('Save');
+ return $this->t('Translate');
}
/**
diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
index 6b9954d036..e8520bf968 100644
--- a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
+++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
@@ -409,8 +409,6 @@ public function isDefaultTranslation() {
$entity = $this->getEntity();
return $entity->isDefaultTranslation();
}
- // @todo If not translatable should we always consider this the default
- // translation?
return TRUE;
}
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderMultilingualTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderMultilingualTest.php
index 683d6012df..f832d27ca0 100644
--- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderMultilingualTest.php
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderMultilingualTest.php
@@ -21,7 +21,7 @@ class LayoutBuilderMultilingualTest extends BrowserTestBase {
'layout_builder',
'node',
'block_content',
- 'content_translation',
+ 'language',
'locale',
];
@@ -50,7 +50,8 @@ protected function setUp() {
$this->createContentType([
'type' => 'bundle_with_section_field',
]);
- $this->container->get('content_translation.manager')->setEnabled('node', 'bundle_with_section_field', TRUE);
+
+
LayoutBuilderEntityViewDisplay::load('node.bundle_with_section_field.default')
->enableLayoutBuilder()
->setOverridable()
@@ -60,14 +61,23 @@ protected function setUp() {
ConfigurableLanguage::createFromLangcode('es')->save();
// Create a node and translate it.
- $node = $this->createNode([
+ $en_node = $this->createNode([
'type' => 'bundle_with_section_field',
'title' => 'The untranslated node title',
]);
- $node->addTranslation('es', [
- 'title' => 'The translated node title',
+ $en_node->save();
+
+ // Create a second node in Spanish to confirm the UI works in another
+ // language. We cannot use a translation for this because layout
+ // translations do not have the full UI.
+ $es_node = $this->createNode([
+ 'type' => 'bundle_with_section_field',
+ 'title' => 'The untranslated node title',
+ 'langcode' => 'es',
]);
- $node->save();
+ $es_node->save();
+
+
$this->drupalLogin($this->createUser([
'configure any layout',
@@ -81,7 +91,7 @@ protected function setUp() {
public function testCustomBlocks() {
// Check translated and untranslated entities before translating the string.
$this->assertCustomBlocks('node/1');
- $this->assertCustomBlocks('es/node/1');
+ $this->assertCustomBlocks('es/node/2');
// Translate the 'Inline blocks' string used as a category in
// \Drupal\layout_builder\Controller\ChooseBlockController::inlineBlockList().
@@ -90,7 +100,7 @@ public function testCustomBlocks() {
// Check translated and untranslated entities after translating the string.
$this->assertCustomBlocks('node/1');
- $this->assertCustomBlocks('es/node/1');
+ $this->assertCustomBlocks('es/node/2');
}
/**
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTranslationTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTranslationTest.php
index 841d4867ea..3d9a18279e 100644
--- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTranslationTest.php
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTranslationTest.php
@@ -3,6 +3,8 @@
namespace Drupal\Tests\layout_builder\Functional;
use Drupal\Tests\content_translation\Functional\ContentTranslationTestBase;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\Core\Url;
/**
* Tests that the Layout Builder UI works with translated content.
@@ -97,7 +99,8 @@ public function testLayoutPerTranslation() {
$assert_session->pageTextContains('The translated field value');
$assert_session->pageTextContains('Powered by Drupal');
- // The translate layout is not available.
+ // Confirm that layout translation page is accessible once the untranslated
+ // entity has a override.
$this->drupalGet($translated_layout_url);
$assert_session->pageTextNotContains('Access denied');
$assert_session->pageTextNotContains('The untranslated field value');
@@ -105,10 +108,8 @@ public function testLayoutPerTranslation() {
$assert_session->pageTextContains('Powered by Drupal');
$assert_session->buttonExists('Save layout');
- // Confirm that links do not exist to change the layout.
- $assert_session->linkNotExists('Add Section');
- $assert_session->linkNotExists('Add Block');
- $assert_session->linkNotExists('Remove section');
+ $this->assertNonTranslationActionsRemoved();
+
}
@@ -139,4 +140,79 @@ public function testLayoutTranslationNoOverride() {
$assert_session->pageTextContains('Access denied');
}
+ /**
+ * The entity used for testing.
+ *
+ * @var \Drupal\Core\Entity\EntityInterface
+ */
+ protected $entity;
+
+ /**
+ * {@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;
+ }
+
+ /**
+ * Setup translated entity with layouts.
+ */
+ protected function setUpEntities() {
+ $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');
+ }
+
+ /**
+ * 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/LayoutSectionTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
index 1041dd813e..e5b70c0b3f 100644
--- a/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
@@ -203,13 +203,12 @@ public function testLayoutSectionFormatterAccess() {
* Tests the multilingual support of the section formatter.
*/
public function testMultilingualLayoutSectionFormatter() {
- $this->container->get('module_installer')->install(['content_translation']);
+ $this->container->get('module_installer')->install(['language']);
$this->rebuildContainer();
ConfigurableLanguage::createFromLangcode('es')->save();
- $this->container->get('content_translation.manager')->setEnabled('node', 'bundle_with_section_field', TRUE);
- $entity = $this->createSectionNode([
+ $en_entity = $this->createSectionNode([
[
'section' => new Section('layout_onecol', [], [
'baz' => new SectionComponent('baz', 'content', [
@@ -218,28 +217,27 @@ public function testMultilingualLayoutSectionFormatter() {
]),
],
]);
- $entity->addTranslation('es', [
- 'title' => 'Translated node title',
- OverridesSectionStorage::FIELD_NAME => [
- [
- 'section' => new Section('layout_twocol', [], [
- 'foo' => new SectionComponent('foo', 'first', [
- 'id' => 'test_block_instantiation',
- 'display_message' => 'foo text',
- ]),
- 'bar' => new SectionComponent('bar', 'second', [
- 'id' => 'test_block_instantiation',
- 'display_message' => 'bar text',
- ]),
+
+ $es_entity = $this->createSectionNode([
+ [
+ 'section' => new Section('layout_twocol', [], [
+ 'foo' => new SectionComponent('foo', 'first', [
+ 'id' => 'test_block_instantiation',
+ 'display_message' => 'foo text',
]),
- ],
+ 'bar' => new SectionComponent('bar', 'second', [
+ 'id' => 'test_block_instantiation',
+ 'display_message' => 'bar text',
+ ]),
+ ]),
],
]);
- $entity->save();
+ $es_entity->set('langcode', 'es');
+ $es_entity->save();
- $this->drupalGet($entity->toUrl('canonical'));
+ $this->drupalGet($en_entity->toUrl('canonical'));
$this->assertLayoutSection('.layout--onecol', 'Powered by');
- $this->drupalGet($entity->toUrl('canonical')->setOption('prefix', 'es/'));
+ $this->drupalGet($es_entity->toUrl('canonical'));
$this->assertLayoutSection('.layout--twocol', ['foo text', 'bar text']);
}
diff --git a/core/modules/layout_builder/tests/src/Functional/TranslationTestTrait.php b/core/modules/layout_builder/tests/src/Functional/TranslationTestTrait.php
index a7d33d2a1a..cbf54dd9e9 100644
--- a/core/modules/layout_builder/tests/src/Functional/TranslationTestTrait.php
+++ b/core/modules/layout_builder/tests/src/Functional/TranslationTestTrait.php
@@ -2,8 +2,6 @@
namespace Drupal\Tests\layout_builder\Functional;
-use Drupal\Core\Entity\Entity\EntityViewDisplay;
-use Drupal\Core\Url;
/**
* Common functions for testing Layout Builder with translations.
@@ -11,78 +9,16 @@
trait TranslationTestTrait {
/**
- * The entity used for testing.
+ * @param \Drupal\Tests\WebAssert $assert_session
*
- * @var \Drupal\Core\Entity\EntityInterface
+ * @throws \Behat\Mink\Exception\ExpectationException
*/
- protected $entity;
-
- /**
- * {@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;
- }
-
- /**
- * Setup translated entity with layouts.
- */
- protected function setUpEntities() {
- $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');
- }
-
- /**
- * 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();
+ protected function assertNonTranslationActionsRemoved() {
+ $assert_session = $this->assertSession();
+ // Confirm that links do not exist to change the layout.
+ $assert_session->linkNotExists('Add Section');
+ $assert_session->linkNotExists('Add Block');
+ $assert_session->linkNotExists('Remove section');
}
}
diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderDisableInteractionsTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderDisableInteractionsTest.php
index e4b342ba86..1b55d8af7e 100644
--- a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderDisableInteractionsTest.php
+++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderDisableInteractionsTest.php
@@ -18,6 +18,7 @@
class LayoutBuilderDisableInteractionsTest extends WebDriverTestBase {
use ContextualLinkClickTrait;
+ use LayoutBuilderTestTrait;
/**
* {@inheritdoc}
@@ -133,36 +134,6 @@ public function testFormsLinksDisabled() {
$this->assertContextualLinksClickable();
}
- /**
- * Adds a block in the Layout Builder.
- *
- * @param string $block_link_text
- * The link text to add the block.
- * @param string $rendered_locator
- * The CSS locator to confirm the block was rendered.
- */
- protected function addBlock($block_link_text, $rendered_locator) {
- $assert_session = $this->assertSession();
- $page = $this->getSession()->getPage();
-
- // Add a new block.
- $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#layout-builder a:contains(\'Add Block\')'));
- $this->clickLink('Add Block');
- $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas'));
- $assert_session->assertWaitOnAjaxRequest();
-
- $assert_session->linkExists($block_link_text);
- $this->clickLink($block_link_text);
-
- // Wait for off-canvas dialog to reopen with block form.
- $this->assertNotEmpty($assert_session->waitForElementVisible('css', ".layout-builder-add-block"));
- $assert_session->assertWaitOnAjaxRequest();
- $page->pressButton('Add Block');
-
- // Wait for block form to be rendered in the Layout Builder.
- $this->assertNotEmpty($assert_session->waitForElement('css', $rendered_locator));
- }
-
/**
* Checks if element is unclickable.
*
@@ -275,26 +246,4 @@ protected function movePointerTo($selector) {
$driver_session->moveto(['element' => $element->getID()]);
}
- /**
- * Waits for an element to be removed from the page.
- *
- * @param string $selector
- * CSS selector.
- * @param int $timeout
- * (optional) Timeout in milliseconds, defaults to 10000.
- * @param string $message
- * (optional) Custom message to display with the assertion.
- *
- * @todo: Remove after https://www.drupal.org/project/drupal/issues/2892440
- */
- public function assertNoElementAfterWait($selector, $timeout = 10000, $message = '') {
- $page = $this->getSession()->getPage();
- if ($message === '') {
- $message = "Element '$selector' was not on the page after wait.";
- }
- $this->assertTrue($page->waitFor($timeout / 1000, function () use ($page, $selector) {
- return empty($page->find('css', $selector));
- }), $message);
- }
-
}
diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderTestTrait.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderTestTrait.php
new file mode 100644
index 0000000000..0568f869fd
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderTestTrait.php
@@ -0,0 +1,66 @@
+assertSession();
+ $page = $this->getSession()->getPage();
+
+ // Add a new block.
+ $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#layout-builder a:contains(\'Add Block\')'));
+ $this->clickLink('Add Block');
+ $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas'));
+ $assert_session->assertWaitOnAjaxRequest();
+
+ $assert_session->linkExists($block_link_text);
+ $this->clickLink($block_link_text);
+
+ // Wait for off-canvas dialog to reopen with block form.
+ $this->assertNotEmpty($assert_session->waitForElementVisible('css', ".layout-builder-add-block"));
+ $assert_session->assertWaitOnAjaxRequest();
+ if ($label_display) {
+ $page->checkField('settings[label_display]');
+ }
+ if ($label !== NULL) {
+ $page->fillField('settings[label]', $label);
+ }
+ $page->pressButton('Add Block');
+
+ // Wait for block form to be rendered in the Layout Builder.
+ $this->assertNotEmpty($assert_session->waitForElement('css', $rendered_locator));
+ }
+
+ /**
+ * Waits for an element to be removed from the page.
+ *
+ * @param string $selector
+ * CSS selector.
+ * @param int $timeout
+ * (optional) Timeout in milliseconds, defaults to 10000.
+ * @param string $message
+ * (optional) Custom message to display with the assertion.
+ *
+ * @todo: Remove after https://www.drupal.org/project/drupal/issues/2892440
+ */
+ public function assertNoElementAfterWait($selector, $timeout = 10000, $message = '') {
+ $page = $this->getSession()->getPage();
+ if ($message === '') {
+ $message = "Element '$selector' was not on the page after wait.";
+ }
+ $this->assertTrue($page->waitFor($timeout / 1000, function () use ($page, $selector) {
+ return empty($page->find('css', $selector));
+ }), $message);
+ }
+
+}
diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/TranslationTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/TranslationTest.php
new file mode 100644
index 0000000000..f74289b1a4
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/TranslationTest.php
@@ -0,0 +1,140 @@
+drupalPlaceBlock('local_tasks_block');
+
+ $this->createContentType(['type' => 'bundle_with_section_field', 'new_revision' => TRUE]);
+ $this->createNode([
+ 'type' => 'bundle_with_section_field',
+ 'title' => 'The node title',
+ 'body' => [
+ [
+ 'value' => 'The node body',
+ ],
+ ],
+ ]);
+ // Adds a new language.
+ ConfigurableLanguage::createFromLangcode('it')->save();
+
+ // Enable translation for the node type 'bundle_with_section_field'.
+ \Drupal::service('content_translation.manager')->setEnabled('node', 'bundle_with_section_field', TRUE);
+ drupal_static_reset();
+ \Drupal::entityTypeManager()->clearCachedDefinitions();
+ \Drupal::service('router.builder')->rebuild();
+ \Drupal::service('entity.definition_update_manager')->applyUpdates();
+
+ $this->rebuildContainer();
+ }
+
+ /**
+ * Tests that block labels can be translated.
+ */
+ public function testLabelTranslation() {
+ $page = $this->getSession()->getPage();
+ $assert_session = $this->assertSession();
+
+ $this->drupalLogin($this->drupalCreateUser([
+ 'access contextual links',
+ 'configure any layout',
+ 'administer node display',
+ 'administer node fields',
+ 'translate bundle_with_section_field node',
+ 'create content translations',
+ ]));
+
+ // Allow layout overrides.
+ $this->drupalPostForm(
+ static::FIELD_UI_PREFIX . '/display/default',
+ ['layout[enabled]' => TRUE],
+ 'Save'
+ );
+ $this->drupalPostForm(
+ static::FIELD_UI_PREFIX . '/display/default',
+ ['layout[allow_custom]' => TRUE],
+ 'Save'
+ );
+
+ // Add a new inline block to the original node.
+ $this->drupalGet('node/1/layout');
+ $this->addBlock('Powered by Drupal', '.block-system-powered-by-block', TRUE, 'untranslated label');
+ $assert_session->pageTextContains('untranslated label');
+ $assert_session->buttonExists('Save layout');
+ $page->pressButton('Save layout');
+ $assert_session->addressEquals('node/1');
+
+
+ // Create a translation.
+ $add_translation_url = Url::fromRoute("entity.node.content_translation_add", [
+ 'node' => 1,
+ 'source' => 'en',
+ 'target' => 'it',
+ ]);
+ $this->drupalPostForm($add_translation_url, [
+ 'title[0][value]' => 'The translated node title',
+ 'body[0][value]' => 'The translated node body',
+ ], 'Save');
+
+ // Update the translations block label.
+ $this->drupalGet('it/node/1/layout');
+ $this->assertNonTranslationActionsRemoved();
+ $this->clickContextualLink('.block-system-powered-by-block', 'Translate block');
+ $label_input = $assert_session->waitForElementVisible('css', '#drupal-off-canvas [name="settings[translated_label]"]');
+ $this->assertNotEmpty($label_input);
+ $this->assertEquals('untranslated label', $label_input->getValue());
+ $label_input->setValue('label in translation');
+ $page->pressButton('Translate');
+ $this->assertNoElementAfterWait('#drupal-off-canvas');
+ $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'h2:contains("label in translation")'));
+ $assert_session->buttonExists('Save layout');
+ $page->pressButton('Save layout');
+ $assert_session->addressEquals('it/node/1');
+ $assert_session->pageTextContains('label in translation');
+
+ // Confirm that untranslated label is still used on default translation.
+ $this->drupalGet('node/1');
+ $assert_session->pageTextContains('untranslated label');
+ $assert_session->pageTextNotContains('label in translation');
+ }
+
+}
diff --git a/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php b/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php
index 0b90811995..4ff8a1a0a2 100644
--- a/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php
+++ b/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php
@@ -127,7 +127,6 @@ public function providerTestExtractIdFromRoute() {
public function testGetSectionListFromId($success, $expected_entity_type_id, $id) {
$defaults['the_parameter_name'] = $id;
- $this->entityRepository->getTranslationFromContext(Argument::cetera())->shouldNotBeCalled();
if ($expected_entity_type_id) {
$entity_without_layout = $this->prophesize(FieldableEntityInterface::class);
$entity_without_layout->hasField(OverridesSectionStorage::FIELD_NAME)->willReturn(FALSE);