From 038e65153847cd7111ec478672efa908daf86431 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Thu, 25 Jan 2024 10:00:41 +0000
Subject: [PATCH] Issue #3190542 by godotislate, clayfreeman, tim.plunkett,
 neclimdul, smustgrave, acbramley: Layout Builder overrides section storage
 sets local tasks block cache max-age to 0 on content entity pages without
 overrides enabled

(cherry picked from commit 40a48863628158744b9b16c54efbd7364be1de5e)
---
 .../OverridesSectionStorage.php               |  10 +-
 .../Functional/LayoutBuilderLocalTaskTest.php | 111 ++++++++++++++++++
 .../src/Functional/LayoutSectionTest.php      |   2 +-
 .../src/Unit/OverridesSectionStorageTest.php  |   2 +-
 4 files changed, 119 insertions(+), 6 deletions(-)
 create mode 100644 core/modules/layout_builder/tests/src/Functional/LayoutBuilderLocalTaskTest.php

diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
index e762ac839711..4f020bab8de5 100644
--- a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
+++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
@@ -188,7 +188,9 @@ public function deriveContextsFromRoute($value, $definition, $name, array $defau
    *   The route defaults array.
    *
    * @return \Drupal\Core\Entity\EntityInterface|null
-   *   The entity for the route, or NULL if none exist.
+   *   The entity for the route, or NULL if none exist. The entity is not
+   *   guaranteed to be fieldable, or contain the necessary field for this
+   *   section storage plugin.
    *
    * @see \Drupal\layout_builder\SectionStorageInterface::deriveContextsFromRoute()
    * @see \Drupal\Core\ParamConverter\ParamConverterInterface::convert()
@@ -206,9 +208,7 @@ private function extractEntityFromRoute($value, array $defaults) {
     }
 
     $entity = $this->entityRepository->getActive($entity_type_id, $entity_id);
-    if ($entity instanceof FieldableEntityInterface && $entity->hasField(static::FIELD_NAME)) {
-      return $entity;
-    }
+    return ($entity instanceof FieldableEntityInterface) ? $entity : NULL;
   }
 
   /**
@@ -359,6 +359,8 @@ public function access($operation, AccountInterface $account = NULL, $return_as_
 
     // Access also depends on the default being enabled.
     $result = $result->andIf($this->getDefaultSectionStorage()->access($operation, $account, TRUE));
+    // Access also depends on the default layout being overridable.
+    $result = $result->andIf(AccessResult::allowedIf($this->getDefaultSectionStorage()->isOverridable())->addCacheableDependency($this->getDefaultSectionStorage()));
     $result = $this->handleTranslationAccess($result, $operation, $account);
     return $return_as_object ? $result : $result->isAllowed();
   }
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderLocalTaskTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderLocalTaskTest.php
new file mode 100644
index 000000000000..1e485554ec67
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderLocalTaskTest.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace Drupal\Tests\layout_builder\Functional;
+
+use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests Layout Builder local tasks.
+ *
+ * @group layout_builder
+ */
+class LayoutBuilderLocalTaskTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'layout_builder',
+    'block',
+    'node',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->drupalPlaceBlock('local_tasks_block');
+  }
+
+  /**
+   * Tests the cacheability of local tasks with Layout Builder module installed.
+   */
+  public function testLocalTaskLayoutBuilderInstalledCacheability() {
+    // Create only one bundle and do not enable layout builder on its display.
+    $this->drupalCreateContentType([
+      'type' => 'bundle_no_lb_display',
+    ]);
+
+    LayoutBuilderEntityViewDisplay::load('node.bundle_no_lb_display.default')
+      ->disableLayoutBuilder()
+      ->save();
+
+    $node = $this->drupalCreateNode([
+      'type' => 'bundle_no_lb_display',
+    ]);
+
+    $assert_session = $this->assertSession();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'configure any layout',
+    ]));
+
+    $this->drupalGet('node/' . $node->id());
+    $assert_session->responseHeaderEquals('X-Drupal-Cache-Max-Age', '-1 (Permanent)');
+    $assert_session->statusCodeEquals(200);
+  }
+
+  /**
+   * Tests the cacheability of local tasks with multiple content types.
+   */
+  public function testLocalTaskMultipleContentTypesCacheability() {
+    // Test when there are two content types, one with a display having Layout
+    // Builder enabled with overrides, and another with display not having
+    // Layout Builder enabled.
+    $this->drupalCreateContentType([
+      'type' => 'bundle_no_lb_display',
+    ]);
+    LayoutBuilderEntityViewDisplay::load('node.bundle_no_lb_display.default')
+      ->disableLayoutBuilder()
+      ->save();
+
+    $node_without_lb = $this->drupalCreateNode([
+      'type' => 'bundle_no_lb_display',
+    ]);
+
+    $this->drupalCreateContentType([
+      'type' => 'bundle_with_overrides',
+    ]);
+    LayoutBuilderEntityViewDisplay::load('node.bundle_with_overrides.default')
+      ->enableLayoutBuilder()
+      ->setOverridable()
+      ->save();
+
+    $node_with_overrides = $this->drupalCreateNode([
+      'type' => 'bundle_with_overrides',
+    ]);
+
+    $assert_session = $this->assertSession();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'configure any layout',
+    ]));
+
+    $this->drupalGet('node/' . $node_without_lb->id());
+    $assert_session->responseHeaderEquals('X-Drupal-Cache-Max-Age', '-1 (Permanent)');
+    $assert_session->statusCodeEquals(200);
+
+    $this->drupalGet('node/' . $node_with_overrides->id());
+    $assert_session->responseHeaderEquals('X-Drupal-Cache-Max-Age', '-1 (Permanent)');
+    $assert_session->statusCodeEquals(200);
+  }
+
+}
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
index 3bbc2410dc59..f04478f8b008 100644
--- a/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
@@ -236,7 +236,7 @@ public function testLayoutUrlNoSectionField() {
     $node->save();
 
     $this->drupalGet($node->toUrl('canonical')->toString() . '/layout');
-    $this->assertSession()->statusCodeEquals(404);
+    $this->assertSession()->statusCodeEquals(403);
   }
 
   /**
diff --git a/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php b/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php
index fbab039a0c9c..6510e6289340 100644
--- a/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php
+++ b/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php
@@ -134,7 +134,7 @@ public function providerTestExtractEntityFromRoute() {
       [],
     ];
     $data['with value, without layout'] = [
-      FALSE,
+      TRUE,
       'my_entity_type',
       'my_entity_type.entity_without_layout',
       [],
-- 
GitLab