diff --git a/core/modules/field_layout/field_layout.install b/core/modules/field_layout/field_layout.install index 5956bf1095..18875181c9 100644 --- a/core/modules/field_layout/field_layout.install +++ b/core/modules/field_layout/field_layout.install @@ -40,3 +40,19 @@ function field_layout_uninstall() { // Invalidate the render cache since all content will no longer have a layout. Cache::invalidateTags(['rendered']); } + +/** + * Implements hook_requirements(). + */ +function field_layout_requirements($phase) { + $requirements = []; + if ($phase === 'install') { + if (\Drupal::moduleHandler()->moduleExists('layout_builder')) { + $requirements['field_layout'] = [ + 'description' => t('Field Layout cannot be installed because the Layout Builder module is installed and incompatible.'), + 'severity' => REQUIREMENT_ERROR, + ]; + } + } + return $requirements; +} diff --git a/core/modules/layout_builder/layout_builder.install b/core/modules/layout_builder/layout_builder.install new file mode 100644 index 0000000000..64b64078eb --- /dev/null +++ b/core/modules/layout_builder/layout_builder.install @@ -0,0 +1,22 @@ +moduleExists('field_layout')) { + $requirements['layout_builder'] = [ + 'description' => t('Layout Builder cannot be installed because the Field Layout module is installed and incompatible.'), + 'severity' => REQUIREMENT_ERROR, + ]; + } + } + return $requirements; +} diff --git a/core/modules/layout_builder/layout_builder.services.yml b/core/modules/layout_builder/layout_builder.services.yml index 575dbc75ad..518d9ee942 100644 --- a/core/modules/layout_builder/layout_builder.services.yml +++ b/core/modules/layout_builder/layout_builder.services.yml @@ -23,3 +23,8 @@ services: arguments: ['@entity.manager', '@layout_builder.tempstore_repository'] tags: - { name: paramconverter, priority: 10 } + cache_context.layout_builder_is_active: + class: Drupal\layout_builder\Cache\LayoutBuilderIsActiveCacheContext + arguments: ['@current_route_match'] + tags: + - { name: cache.context} diff --git a/core/modules/layout_builder/src/Cache/LayoutBuilderIsActiveCacheContext.php b/core/modules/layout_builder/src/Cache/LayoutBuilderIsActiveCacheContext.php new file mode 100644 index 0000000000..c632f4b33a --- /dev/null +++ b/core/modules/layout_builder/src/Cache/LayoutBuilderIsActiveCacheContext.php @@ -0,0 +1,87 @@ +routeMatch = $route_match; + } + + /** + * {@inheritdoc} + */ + public static function getLabel() { + return t('Layout Builder'); + } + + /** + * {@inheritdoc} + */ + public function getContext($entity_type_id = NULL) { + if (!$entity_type_id) { + throw new \LogicException('Missing entity type ID'); + } + + $display = $this->getDisplay($entity_type_id); + return ($display && $display->getThirdPartySetting('layout_builder', 'allow_custom', FALSE)) ? '1' : '0'; + } + + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($entity_type_id = NULL) { + if (!$entity_type_id) { + throw new \LogicException('Missing entity type ID'); + } + + $cacheable_metadata = new CacheableMetadata(); + if ($display = $this->getDisplay($entity_type_id)) { + $cacheable_metadata->addCacheableDependency($display); + } + return $cacheable_metadata; + } + + /** + * Returns the entity view display for a given entity type and view mode. + * + * @param string $entity_type_id + * The entity type ID. + * @param string $view_mode + * (optional) The view mode that should be used to render the entity. + * + * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface|null + * The entity view display, if it exists. + */ + protected function getDisplay($entity_type_id, $view_mode = 'full') { + if ($entity = $this->routeMatch->getParameter($entity_type_id)) { + return EntityViewDisplay::collectRenderDisplay($entity, $view_mode); + } + } + +} diff --git a/core/modules/layout_builder/src/Plugin/Derivative/LayoutBuilderLocalTaskDeriver.php b/core/modules/layout_builder/src/Plugin/Derivative/LayoutBuilderLocalTaskDeriver.php index 02a1ea14df..7054dd3605 100644 --- a/core/modules/layout_builder/src/Plugin/Derivative/LayoutBuilderLocalTaskDeriver.php +++ b/core/modules/layout_builder/src/Plugin/Derivative/LayoutBuilderLocalTaskDeriver.php @@ -57,6 +57,7 @@ public function getDerivativeDefinitions($base_plugin_definition) { 'base_route' => "entity.$entity_type_id.canonical", 'entity_type_id' => $entity_type_id, 'class' => LayoutBuilderLocalTask::class, + 'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id], ]; $this->derivatives["entity.$entity_type_id.save_layout"] = $base_plugin_definition + [ 'route_name' => "entity.$entity_type_id.save_layout", @@ -64,6 +65,7 @@ public function getDerivativeDefinitions($base_plugin_definition) { 'parent_id' => "layout_builder_ui:entity.$entity_type_id.layout_builder", 'entity_type_id' => $entity_type_id, 'class' => LayoutBuilderLocalTask::class, + 'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id], ]; $this->derivatives["entity.$entity_type_id.cancel_layout"] = $base_plugin_definition + [ 'route_name' => "entity.$entity_type_id.cancel_layout", @@ -72,6 +74,7 @@ public function getDerivativeDefinitions($base_plugin_definition) { 'entity_type_id' => $entity_type_id, 'class' => LayoutBuilderLocalTask::class, 'weight' => 5, + 'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id], ]; } diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderTest.php index eefe63be34..13ad29e300 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderTest.php @@ -77,36 +77,42 @@ protected function setUp() { 'configure any layout', 'administer node display', ], 'foobar')); + } + + /** + * Tests the Layout Builder UI. + */ + public function testLayoutBuilderUi() { + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + // Ensure the block is not displayed initially. + $this->drupalGet($this->node->toUrl('canonical')); + $assert_session->pageTextContains('The node body'); + $assert_session->pageTextNotContains('Powered by Drupal'); + $assert_session->linkNotExists('Layout'); // Enable layout support. $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display'); - $page = $this->getSession()->getPage(); $page->checkField('layout[allow_custom]'); $page->pressButton('Save'); - } - - /** - * Tests the Layout Builder UI. - */ - public function testLayoutBuilderUi() { - $assert_session = $this->assertSession(); - $page = $this->getSession()->getPage(); - - // Ensure the block is not displayed initially. - $this->drupalGet($this->node->toUrl('canonical')); - $assert_session->pageTextNotContains('Powered by Drupal'); // Enter the layout editing mode. + $this->drupalGet($this->node->toUrl('canonical')); + $assert_session->pageTextNotContains('The node body'); + $assert_session->linkExists('Layout'); $this->clickLink('Layout'); $this->markCurrentPage(); $assert_session->linkExists('Add Section'); $assert_session->linkNotExists('Add Block'); // Add a new section. + $assert_session->linkExists('Add Section'); $this->clickLink('Add Section'); $assert_session->assertWaitOnAjaxRequest(); $assert_session->elementExists('css', '#drupal-off-canvas'); + $assert_session->linkExists('One column'); $this->clickLink('One column'); $assert_session->assertWaitOnAjaxRequest(); $assert_session->elementNotExists('css', '#drupal-off-canvas'); @@ -115,11 +121,13 @@ public function testLayoutBuilderUi() { $assert_session->linkExists('Add Block'); // Add a new block. + $assert_session->linkExists('Add Block'); $this->clickLink('Add Block'); $assert_session->assertWaitOnAjaxRequest(); $assert_session->elementExists('css', '#drupal-off-canvas'); + $assert_session->linkExists('Powered by Drupal'); $this->clickLink('Powered by Drupal'); $assert_session->assertWaitOnAjaxRequest(); $assert_session->elementExists('css', '#drupal-off-canvas'); @@ -146,6 +154,7 @@ public function testLayoutBuilderUi() { $assert_session->pageTextContains('Powered by Drupal'); // Save the layout, and the new block is visible. + $assert_session->linkExists('Save Layout'); $this->clickLink('Save Layout'); $assert_session->addressEquals($this->node->toUrl('canonical')); $assert_session->pageTextContains('Powered by Drupal'); @@ -334,21 +343,31 @@ public function testLayoutNoDialog() { * @todo Workaround for https://www.drupal.org/node/2918718. */ protected function clickContextualLink($selector, $link_locator, $force_visible = TRUE) { + $assert_session = $this->assertSession(); + if ($force_visible) { $this->getSession()->executeScript("jQuery('{$selector} .contextual .trigger').removeClass('visually-hidden');"); + $assert_session->assertWaitOnAjaxRequest(); } $element = $this->getSession()->getPage()->find('css', $selector); $link = $element->findLink($link_locator); - // If the link cannot be found, click the contextual link button first. - if (!$link || !$link->isVisible()) { - $element->find('css', '.contextual button')->press(); - $link = $element->findLink($link_locator); + if (!$link) { + $this->fail("Link $link_locator was found"); + } + else { + // If the link is not visible, click the contextual link button first. + if (!$link->isVisible()) { + $element->find('css', '.contextual button')->press(); + $assert_session->assertWaitOnAjaxRequest(); + } + $this->assertTrue($link->isVisible(), "Link $link_locator is visible."); + $link->click(); } - $link->click(); if ($force_visible) { $this->getSession()->executeScript("jQuery('{$selector} .contextual .trigger').addClass('visually-hidden');"); + $assert_session->assertWaitOnAjaxRequest(); } }