From 8af503bb67bc548ed08b09212a153921e3544062 Mon Sep 17 00:00:00 2001
From: Jakob Perry <japerry007@gmail.com>
Date: Mon, 13 Apr 2020 15:26:18 -0700
Subject: [PATCH] 2960739

---
 composer.json                                 |   7 +-
 config/schema/page_manager.schema.yml         |   9 +
 page_manager.info.yml                         |   2 +-
 page_manager.module                           |  41 +++
 page_manager.post_update.php                  |  39 +++
 page_manager.services.yml                     |   2 +-
 page_manager_ui/page_manager_ui.module        |  45 ++-
 .../src/Form/PageVariantConfigureForm.php     |   2 +-
 .../src/Form/PageVariantSelectionForm.php     |   1 -
 src/Entity/LayoutBuilderStorage.php           |  41 +++
 src/Entity/PageVariant.php                    |   3 +
 src/EventSubscriber/CurrentUserContext.php    |  46 +--
 src/Form/LayoutBuilderForm.php                | 140 +++++++++
 .../LayoutBuilderDisplayVariant.php           | 121 ++++++++
 .../PageManagerLayoutBuilderStorage.php       | 122 ++++++++
 .../PageManagerSectionStorage.php             | 272 ++++++++++++++++++
 .../fixtures/update/page_manager.2960739.php  |  60 ++++
 .../update/page_manager.page.test_page.yml    |  11 +
 ...page_variant.test_page-block_display-0.yml |  46 +++
 tests/src/Functional/PageNodeAccessTest.php   |   2 +-
 .../Functional/Update/UpdateContextName.php   |  41 +++
 .../LayoutBuilderDisplayVariantTest.php       | 121 ++++++++
 tests/src/Unit/CurrentUserContextTest.php     |  50 ++--
 23 files changed, 1149 insertions(+), 75 deletions(-)
 create mode 100644 page_manager.module
 create mode 100644 page_manager.post_update.php
 create mode 100644 src/Entity/LayoutBuilderStorage.php
 create mode 100644 src/Form/LayoutBuilderForm.php
 create mode 100644 src/Plugin/DisplayVariant/LayoutBuilderDisplayVariant.php
 create mode 100644 src/Plugin/LayoutBuilderStorage/PageManagerLayoutBuilderStorage.php
 create mode 100644 src/Plugin/SectionStorage/PageManagerSectionStorage.php
 create mode 100644 tests/fixtures/update/page_manager.2960739.php
 create mode 100644 tests/fixtures/update/page_manager.page.test_page.yml
 create mode 100644 tests/fixtures/update/page_manager.page_variant.test_page-block_display-0.yml
 create mode 100644 tests/src/Functional/Update/UpdateContextName.php
 create mode 100644 tests/src/FunctionalJavascript/LayoutBuilderDisplayVariantTest.php

diff --git a/composer.json b/composer.json
index 1fb50f2..28a77dd 100644
--- a/composer.json
+++ b/composer.json
@@ -19,6 +19,11 @@
   "minimum-stability": "dev",
   "require": {
     "drupal/core": "^8.8 || ^9",
-    "drupal/ctools": "~3.1"
+    "drupal/ctools": "^3.1"
+  },
+  "extra": {
+    "branch-alias": {
+      "dev-8.x-4.x": "4.x-dev"
+    }
   }
 }
diff --git a/config/schema/page_manager.schema.yml b/config/schema/page_manager.schema.yml
index 49e5cfc..95b1483 100644
--- a/config/schema/page_manager.schema.yml
+++ b/config/schema/page_manager.schema.yml
@@ -142,3 +142,12 @@ display_variant.plugin.http_status_code:
     redirect_location:
       type: string
       label: 'Redirect location'
+
+display_variant.plugin.layout_builder:
+  type: display_variant.plugin
+  label: 'Layout Builder'
+  mapping:
+    sections:
+      type: sequence
+      sequence:
+        type: layout_builder.section
diff --git a/page_manager.info.yml b/page_manager.info.yml
index e9cb3a9..d7e7c14 100644
--- a/page_manager.info.yml
+++ b/page_manager.info.yml
@@ -5,4 +5,4 @@ package: Layout
 core_version_requirement: '^8.8 || ^9'
 dependencies:
   - drupal:block
-  - ctools:ctools (>=3.1)
+  - ctools:ctools
diff --git a/page_manager.module b/page_manager.module
new file mode 100644
index 0000000..7acfd65
--- /dev/null
+++ b/page_manager.module
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Provides a way to place blocks on a custom page.
+ */
+
+use Drupal\page_manager\Entity\LayoutBuilderStorage;
+use Drupal\page_manager\Form\LayoutBuilderForm;
+
+/**
+ * Implements hook_entity_type_build().
+ */
+function page_manager_entity_type_build(array &$entity_types) {
+  if (_page_manager_is_layout_builder_enabled()) {
+    /* @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
+    $entity_types['page_variant']
+      ->setHandlerClass('storage', LayoutBuilderStorage::class)
+      ->setFormClass('layout_builder', LayoutBuilderForm::class);
+  }
+}
+
+/**
+ * Implements hook_display_variant_plugin_alter().
+ */
+function page_manager_display_variant_plugin_alter(array &$definitions) {
+  // Disable the layout builder plugin if layout builder is not enabled.
+  if (!_page_manager_is_layout_builder_enabled()) {
+    unset($definitions['layout_builder']);
+  }
+}
+
+/**
+ * Helper to check if layout builder is enabled.
+ *
+ * @return bool
+ *   TRUE is layout_builder is enabled, otherwise FALSE.
+ */
+function _page_manager_is_layout_builder_enabled() {
+  return Drupal::moduleHandler()->moduleExists('layout_builder');
+}
diff --git a/page_manager.post_update.php b/page_manager.post_update.php
new file mode 100644
index 0000000..dc2d578
--- /dev/null
+++ b/page_manager.post_update.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Post update functions for page_manager module.
+ */
+
+use Drupal\Core\Config\Entity\ConfigEntityUpdater;
+use Drupal\page_manager\PageVariantInterface;
+
+/**
+ * Updating page variants with new current_user context name.
+ */
+function page_manager_post_update_replace_current_user_context(&$sandbox = NULL) {
+  \Drupal::classResolver(ConfigEntityUpdater::class)
+    ->update($sandbox, 'page_variant', function (PageVariantInterface $pageVariant) {
+      $process_contexts = function(&$context_mapping) {
+        foreach ($context_mapping as $type => $name) {
+          if ($name == 'current_user') {
+            $context_mapping[$type] = '@user.current_user_context:current_user';
+          }
+        }
+      };
+
+      $conditions = $pageVariant->get('selection_criteria');
+      foreach ($conditions as &$condition) {
+        $process_contexts($condition['context_mapping']);
+      }
+      $pageVariant->set('selection_criteria', $conditions);
+
+      $variant_settings = $pageVariant->get('variant_settings');
+      foreach ($variant_settings['blocks'] as &$block) {
+        $process_contexts($block['context_mapping']);
+      }
+      $pageVariant->set('variant_settings', $variant_settings);
+
+      $pageVariant->save();
+    });
+}
diff --git a/page_manager.services.yml b/page_manager.services.yml
index f8fcf78..f37751e 100644
--- a/page_manager.services.yml
+++ b/page_manager.services.yml
@@ -1,7 +1,7 @@
 services:
   page_manager.current_user_context:
     class: Drupal\page_manager\EventSubscriber\CurrentUserContext
-    arguments: ['@current_user', '@entity_type.manager']
+    arguments: ['@context.repository']
     tags:
       - { name: 'event_subscriber' }
   page_manager.route_param_context:
diff --git a/page_manager_ui/page_manager_ui.module b/page_manager_ui/page_manager_ui.module
index 6812a24..11438b2 100644
--- a/page_manager_ui/page_manager_ui.module
+++ b/page_manager_ui/page_manager_ui.module
@@ -5,6 +5,7 @@
  * Provides a UI for Page Manager.
  */
 
+use Drupal\Core\Url;
 use Drupal\page_manager_ui\Entity\PageListBuilder;
 use Drupal\page_manager_ui\Form\PageDeleteForm;
 use Drupal\page_manager_ui\ConfigTranslation\PageConfigMapper;
@@ -13,6 +14,7 @@ use Drupal\page_manager_ui\Wizard\PageAddWizard;
 use Drupal\page_manager_ui\Wizard\PageEditWizard;
 use Drupal\page_manager_ui\Wizard\PageVariantAddWizard;
 
+
 /**
  * Implements hook_entity_type_build().
  */
@@ -31,7 +33,7 @@ function page_manager_ui_entity_type_build(array &$entity_types) {
       ->setHandlerClass('wizard', [
         'add' => PageAddWizard::class,
         'edit' => PageEditWizard::class,
-    ]);
+      ]);
   }
 
   if (isset($entity_types['page_variant'])) {
@@ -40,7 +42,7 @@ function page_manager_ui_entity_type_build(array &$entity_types) {
       ->setLinkTemplate('edit-form', '/admin/structure/page_manager/manage/{machine_name}/{step}')
       ->setHandlerClass('wizard', [
         'add_variant' => PageVariantAddWizard::class,
-    ]);
+      ]);
   }
 }
 
@@ -112,7 +114,7 @@ function template_preprocess_page_manager_wizard_tree(&$variables) {
 
     $parent[$step] = [
       'title' => !empty($operation['title']) ? $operation['title'] : '',
-      'url' => new \Drupal\Core\Url($wizard->getRouteName(), $parameters),
+      'url' => new Url($wizard->getRouteName(), $parameters),
       'step' => $step,
     ];
   }
@@ -139,3 +141,40 @@ function page_manager_ui_local_tasks_alter(&$local_tasks) {
   unset($local_tasks['config_translation.local_tasks:entity.page.config_translation_overview']);
   unset($local_tasks['config_translation.local_tasks:entity.page_variant.config_translation_overview']);
 }
+
+/**
+ * Implements hook_theme_registry_alter().
+ *
+ * @todo Refactor/remove if https://www.drupal.org/project/drupal/issues/3005403 lands.
+ */
+function page_manager_ui_theme_registry_alter(&$theme_registry) {
+  // Seven removes all block contextual links via a preprocess.
+  // Layout builder variants require contextual links to configure blocks.
+  // @see https://www.drupal.org/project/drupal/issues/2487025
+  if (!empty($theme_registry['block']['preprocess functions'])) {
+    $preprocess_functions = &$theme_registry['block']['preprocess functions'];
+
+    // Remove seven's implementation.
+    if ($index = array_search('seven_preprocess_block', $preprocess_functions)) {
+      unset($preprocess_functions[$index]);
+    }
+  }
+}
+
+/**
+ * Implements hook_preprocess_HOOK().
+ *
+ * @todo Refactor/remove if https://www.drupal.org/project/drupal/issues/3005403 lands.
+ */
+function page_manager_ui_preprocess_block(&$variables) {
+  if (Drupal::theme()->getActiveTheme()->getName() === 'seven') {
+    // If active theme is seven and the block has layout_builder contextual
+    // links, do nothing.
+    if (isset($variables['elements']['#contextual_links']['layout_builder_block'])) {
+      return;
+    }
+
+    // Fallback to seven.
+    seven_preprocess_block($variables);
+  }
+}
diff --git a/page_manager_ui/src/Form/PageVariantConfigureForm.php b/page_manager_ui/src/Form/PageVariantConfigureForm.php
index 8008081..2cde00f 100644
--- a/page_manager_ui/src/Form/PageVariantConfigureForm.php
+++ b/page_manager_ui/src/Form/PageVariantConfigureForm.php
@@ -38,7 +38,7 @@ class PageVariantConfigureForm extends FormBase {
     ];
 
     $variant_plugin = $page_variant->getVariantPlugin();
-    $form['variant_settings'] = $variant_plugin->buildConfigurationForm([], (new FormState())->setValues($form_state->getValue('variant_settings', [])));
+    $form['variant_settings'] = $variant_plugin->buildConfigurationForm([], (new FormState())->setValues($form_state->getValue('variant_settings', []) + ['page_variant' => $page_variant]));
     $form['variant_settings']['#tree'] = TRUE;
 
     if (!$page->isNew()) {
diff --git a/page_manager_ui/src/Form/PageVariantSelectionForm.php b/page_manager_ui/src/Form/PageVariantSelectionForm.php
index df8eded..184819a 100644
--- a/page_manager_ui/src/Form/PageVariantSelectionForm.php
+++ b/page_manager_ui/src/Form/PageVariantSelectionForm.php
@@ -86,7 +86,6 @@ class PageVariantSelectionForm extends ManageConditions {
       $route_parameters,
       ['query' => [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]]
     )->toString();
-
     $response = new AjaxResponse();
     $response->addCommand(new OpenModalDialogCommand($this->t('Configure Required Context'), $content, array('width' => '700')));
     return $response;
diff --git a/src/Entity/LayoutBuilderStorage.php b/src/Entity/LayoutBuilderStorage.php
new file mode 100644
index 0000000..aa73f67
--- /dev/null
+++ b/src/Entity/LayoutBuilderStorage.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\page_manager\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityStorage;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\layout_builder\Section;
+
+/**
+ * Provides storage for page manager entities that have layouts.
+ */
+class LayoutBuilderStorage extends ConfigEntityStorage {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function mapToStorageRecord(EntityInterface $entity) {
+    $record = parent::mapToStorageRecord($entity);
+
+    if (!empty($record['variant_settings']['sections'])) {
+      $record['variant_settings']['sections'] = array_map(function (Section $section) {
+        return $section->toArray();
+      }, $record['variant_settings']['sections']);
+    }
+    return $record;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function mapFromStorageRecords(array $records) {
+    foreach ($records as &$record) {
+      if (!empty($record['variant_settings']['sections'])) {
+        $sections = &$record['variant_settings']['sections'];
+        $sections = array_map([Section::class, 'fromArray'], $sections);
+      }
+    }
+    return parent::mapFromStorageRecords($records);
+  }
+
+}
diff --git a/src/Entity/PageVariant.php b/src/Entity/PageVariant.php
index 162d45f..1a309c3 100644
--- a/src/Entity/PageVariant.php
+++ b/src/Entity/PageVariant.php
@@ -428,6 +428,7 @@ class PageVariant extends ConfigEntityBase implements PageVariantInterface {
    * Wraps the condition plugin manager.
    *
    * @return \Drupal\Core\Condition\ConditionManager
+   *   The condition manager service.
    */
   protected function getConditionManager() {
     return \Drupal::service('plugin.manager.condition');
@@ -437,6 +438,7 @@ class PageVariant extends ConfigEntityBase implements PageVariantInterface {
    * Wraps the context mapper.
    *
    * @return \Drupal\page_manager\ContextMapperInterface
+   *   The context mapper service.
    */
   protected function getContextMapper() {
     return \Drupal::service('page_manager.context_mapper');
@@ -446,6 +448,7 @@ class PageVariant extends ConfigEntityBase implements PageVariantInterface {
    * Wraps the page entity storage.
    *
    * @return \Drupal\Core\Entity\EntityStorageInterface
+   *   The Page entity storage service.
    */
   protected function getPageStorage() {
     return \Drupal::entityTypeManager()->getStorage('page');
diff --git a/src/EventSubscriber/CurrentUserContext.php b/src/EventSubscriber/CurrentUserContext.php
index 638e8f6..d36b8fc 100644
--- a/src/EventSubscriber/CurrentUserContext.php
+++ b/src/EventSubscriber/CurrentUserContext.php
@@ -2,13 +2,8 @@
 
 namespace Drupal\page_manager\EventSubscriber;
 
-use Drupal\Core\Cache\CacheableMetadata;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Session\AccountInterface;
-use Drupal\page_manager\Context\ContextDefinitionFactory;
+use Drupal\Core\Plugin\Context\LazyContextRepository;
 use Drupal\page_manager\Event\PageManagerContextEvent;
-use Drupal\Core\Plugin\Context\Context;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\page_manager\Event\PageManagerEvents;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
@@ -17,33 +12,21 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  */
 class CurrentUserContext implements EventSubscriberInterface {
 
-  use StringTranslationTrait;
-
-  /**
-   * The account proxy.
-   *
-   * @var \Drupal\Core\Session\AccountProxyInterface
-   */
-  protected $account;
-
   /**
-   * The user storage.
+   * Constructs a new CurrentUserContext.
    *
-   * @var \Drupal\user\UserStorageInterface
+   * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
    */
-  protected $userStorage;
+  protected $contextRepository;
 
   /**
-   * Constructs a new CurrentUserContext.
+   * Creates LanguageInterfaceContext object.
    *
-   * @param \Drupal\Core\Session\AccountInterface $account
-   *   The current account.
-   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   *   The entity type manager.
+   * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
+   *   The context repository service.
    */
-  public function __construct(AccountInterface $account, EntityTypeManagerInterface $entity_type_manager) {
-    $this->account = $account;
-    $this->userStorage = $entity_type_manager->getStorage('user');
+  public function __construct(LazyContextRepository $context_repository) {
+    $this->contextRepository = $context_repository;
   }
 
   /**
@@ -53,14 +36,9 @@ class CurrentUserContext implements EventSubscriberInterface {
    *   The page entity context event.
    */
   public function onPageContext(PageManagerContextEvent $event) {
-    $id = $this->account->id();
-    $current_user = $this->userStorage->load($id);
-
-    $context = new Context(ContextDefinitionFactory::create('entity:user')->setLabel($this->t('Current user')), $current_user);
-    $cacheability = new CacheableMetadata();
-    $cacheability->setCacheContexts(['user']);
-    $context->addCacheableDependency($cacheability);
-    $event->getPage()->addContext('current_user', $context);
+    $contexts = $this->contextRepository->getRuntimeContexts(['@user.current_user_context:current_user']);
+    $context = reset($contexts);
+    $event->getPage()->addContext('@user.current_user_context:current_user', $context);
   }
 
   /**
diff --git a/src/Form/LayoutBuilderForm.php b/src/Form/LayoutBuilderForm.php
new file mode 100644
index 0000000..9c80ec4
--- /dev/null
+++ b/src/Form/LayoutBuilderForm.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace Drupal\page_manager\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\Context\EntityContext;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
+use Drupal\layout_builder\Form\PreviewToggleTrait;
+use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
+use Drupal\layout_builder\SectionStorage\SectionStorageManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a form containing the Layout Builder UI for Page Manager.
+ *
+ * @todo Refactor this after https://www.drupal.org/node/3035189 is resolved.
+ */
+class LayoutBuilderForm extends FormBase {
+
+  use PreviewToggleTrait;
+
+  /**
+   * Layout tempstore repository.
+   *
+   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
+   */
+  protected $layoutTempstoreRepository;
+
+  /**
+   * The Section Storage Manager.
+   *
+   * @var \Drupal\layout_builder\SectionStorage\SectionStorageManager
+   */
+  protected $sectionStorageManager;
+
+  /**
+   * The section storage.
+   *
+   * @var \Drupal\layout_builder\SectionStorageInterface
+   */
+  protected $sectionStorage;
+
+  /**
+   * The tempstore directory.
+   *
+   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
+   */
+  protected $tempstore;
+
+  /**
+   * Constructs a new LayoutBuilderForm.
+   *
+   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
+   *   The layout tempstore repository.
+   * @param \Drupal\layout_builder\SectionStorage\SectionStorageManager $section_storage_manager
+   *   The section storage manager.
+   * @param \Drupal\Core\TempStore\SharedTempStoreFactory $tempstore
+   *   The tempstore factory.
+   */
+  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, SectionStorageManager $section_storage_manager, SharedTempStoreFactory $tempstore) {
+    $this->layoutTempstoreRepository = $layout_tempstore_repository;
+    $this->sectionStorageManager = $section_storage_manager;
+    $this->tempstore = $tempstore;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('layout_builder.tempstore_repository'),
+      $container->get('plugin.manager.layout_builder.section_storage'),
+      $container->get('tempstore.shared')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'page_manager_layout_builder_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    /** @var \Drupal\page_manager\PageVariantInterface $page_variant */
+    $page_variant = $cached_values['page_variant'];
+
+    // If this is a new variant, put it in the tempstore so that we can
+    // retrieve it by id later.
+    // @see \Drupal\page_manager\Plugin\SectionStorage\PageManagerSectionStorage::deriveContextsFromRoute.
+    if ($page_variant->isNew()) {
+      $this->tempstore->get('page_manager.layout_builder')->set($page_variant->id(), $page_variant);
+    }
+
+    $section_storage = $this->sectionStorageManager->load('page_manager', [
+      'entity' => EntityContext::fromEntity($page_variant),
+    ]);
+
+    $this->sectionStorage = $this->layoutTempstoreRepository->get($section_storage);
+
+    $form['preview_toggle'] = $this->buildContentPreviewToggle();
+
+    $form['layout_builder'] = [
+      '#type' => 'layout_builder',
+      '#section_storage' => $this->sectionStorage,
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+
+    /** @var \Drupal\page_manager\Entity\PageVariant $page_variant */
+    $page_variant = $this->sectionStorage->getContextValue('entity');
+
+    // Pass down the variant settings and let the plugin handle saving it.
+    /** @var \Drupal\page_manager\Plugin\DisplayVariant\LayoutBuilderDisplayVariant $variant_plugin */
+    $variant_plugin = $cached_values['plugin'];
+    $variant_plugin->setConfiguration($page_variant->get('variant_settings'));
+
+    // The form wizard takes care of saving the variant.
+    // Clear the layout tempstore.
+    $this->layoutTempstoreRepository->delete($this->sectionStorage);
+    $this->messenger()->addMessage($this->t('The layout has been saved.'));
+
+    // Clean up the tempstore.
+    $this->tempstore->get('page_manager.layout_builder')->delete($page_variant->id());
+    $this->tempstore->get('page_manager.page_variant')->delete($page_variant->id());
+  }
+
+}
diff --git a/src/Plugin/DisplayVariant/LayoutBuilderDisplayVariant.php b/src/Plugin/DisplayVariant/LayoutBuilderDisplayVariant.php
new file mode 100644
index 0000000..3ab6467
--- /dev/null
+++ b/src/Plugin/DisplayVariant/LayoutBuilderDisplayVariant.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace Drupal\page_manager\Plugin\DisplayVariant;
+
+use Drupal\Core\Display\ContextAwareVariantInterface;
+use Drupal\Core\Display\VariantBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\ctools\Plugin\PluginWizardInterface;
+use Drupal\layout_builder\LayoutEntityHelperTrait;
+use Drupal\layout_builder\SectionStorage\SectionStorageTrait;
+use Drupal\page_manager\Form\LayoutBuilderForm;
+
+/**
+ * Provides a Layout Builder variant.
+ *
+ * @DisplayVariant(
+ *   id = "layout_builder",
+ *   admin_label = @Translation("Layout Builder")
+ * )
+ */
+class LayoutBuilderDisplayVariant extends VariantBase implements PluginWizardInterface, ContextAwareVariantInterface {
+
+  use SectionStorageTrait;
+  use LayoutEntityHelperTrait;
+
+  /**
+   * An array of collected contexts.
+   *
+   * This is only used on runtime, and is not stored.
+   *
+   * @var \Drupal\Component\Plugin\Context\ContextInterface[]
+   */
+  protected $contexts = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    $build = [];
+    $contexts = $this->getContexts();
+    foreach ($this->getSections() as $delta => $section) {
+      $build[$delta] = $section->toRenderArray($contexts);
+    }
+    return $build;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return parent::defaultConfiguration() + [
+      'sections' => [],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWizardOperations($cached_values) {
+    $operations = [];
+    $operations['layout_builder'] = [
+      'title' => $this->t('Layout'),
+      'form' => LayoutBuilderForm::class,
+    ];
+
+    return $operations;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setSections(array $sections) {
+    $this->configuration['sections'] = array_values($sections);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSections() {
+    if (!isset($this->configuration['sections'])) {
+      $this->configuration['sections'] = [];
+    }
+    return $this->configuration['sections'];
+  }
+
+  /**
+   * Returns instance of the layout plugin used by this page variant.
+   *
+   * @return \Drupal\Core\Layout\LayoutInterface
+   *   A layout plugin instance.
+   */
+  public function getLayout() {
+    if (!isset($this->layout)) {
+      $this->layout = $this->layoutManager->createInstance($this->configuration['layout'], $this->configuration['layout_settings']);
+    }
+    return $this->layout;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContexts() {
+    return $this->contexts;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setContexts(array $contexts) {
+    $this->contexts = $contexts;
+    return $this;
+  }
+
+}
diff --git a/src/Plugin/LayoutBuilderStorage/PageManagerLayoutBuilderStorage.php b/src/Plugin/LayoutBuilderStorage/PageManagerLayoutBuilderStorage.php
new file mode 100644
index 0000000..084ac32
--- /dev/null
+++ b/src/Plugin/LayoutBuilderStorage/PageManagerLayoutBuilderStorage.php
@@ -0,0 +1,122 @@
+<?php
+
+namespace Drupal\panels\Plugin\PanelsStorage;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\page_manager\Plugin\DisplayVariant\LayoutBuilderDisplayVariant;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * A Page Manager storage service that stores Layout Builder displays.
+ */
+class PageManagerLayoutBuilderStorage extends PluginBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a PageManagerLayoutBuilderStorage.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * Load a page variant entity.
+   *
+   * @param string $id
+   *   The page variant entity's id.
+   *
+   * @return \Drupal\page_manager\PageVariantInterface
+   *   The variant object.
+   *
+   * @throws \Exception
+   */
+  protected function loadPageVariant($id) {
+    return $this->entityTypeManager->getStorage('page_variant')->load($id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(LayoutBuilderDisplayVariant $lb_display) {
+    $id = $lb_display->getStorageId();
+    if ($id && ($page_variant = $this->loadPageVariant($id))) {
+      $variant_plugin = $page_variant->getVariantPlugin();
+      if (!($variant_plugin instanceof LayoutBuilderDisplayVariant)) {
+        throw new \Exception("Page variant doesn't use a Layout Builder display variant");
+      }
+      $variant_plugin->setConfiguration($lb_display->getConfiguration());
+      $page_variant->save();
+    }
+    else {
+      throw new \Exception("Couldn't find page variant to store Layout Builder display");
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load($id) {
+    if ($page_variant = $this->loadPageVariant($id)) {
+      $lb_display = $page_variant->getVariantPlugin();
+
+      // If this page variant doesn't have a Panels display on it, then we treat
+      // it the same as if there was no such page variant.
+      if (!($lb_display instanceof LayoutBuilderDisplayVariant)) {
+        return NULL;
+      }
+
+      // Pass down the contexts because the display has no other way to get them
+      // from the variant.
+      $lb_display->setContexts($page_variant->getContexts());
+
+      return $lb_display;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access($id, $op, AccountInterface $account) {
+    if ($op == 'change layout') {
+      $op = 'update';
+    }
+    if ($page_variant = $this->loadPageVariant($id)) {
+      return $page_variant->access($op, $account, TRUE);
+    }
+
+    return AccessResult::forbidden();
+  }
+
+}
diff --git a/src/Plugin/SectionStorage/PageManagerSectionStorage.php b/src/Plugin/SectionStorage/PageManagerSectionStorage.php
new file mode 100644
index 0000000..5c011f8
--- /dev/null
+++ b/src/Plugin/SectionStorage/PageManagerSectionStorage.php
@@ -0,0 +1,272 @@
+<?php
+
+namespace Drupal\page_manager\Plugin\SectionStorage;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
+use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\Context\Context;
+use Drupal\Core\Plugin\Context\EntityContext;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
+use Drupal\Core\Url;
+use Drupal\layout_builder\Entity\SampleEntityGeneratorInterface;
+use Drupal\layout_builder\Plugin\SectionStorage\SectionStorageBase;
+use Drupal\page_manager\Entity\PageVariant;
+use Drupal\page_manager\Plugin\DisplayVariant\LayoutBuilderDisplayVariant;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Defines the 'page_manager' section storage type.
+ *
+ * @SectionStorage(
+ *   id = "page_manager",
+ *   context_definitions = {
+ *     "entity" = @ContextDefinition("entity:page_variant"),
+ *   },
+ * )
+ */
+class PageManagerSectionStorage extends SectionStorageBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The sample entity generator.
+   *
+   * @var \Drupal\layout_builder\Entity\SampleEntityGeneratorInterface
+   */
+  protected $sampleEntityGenerator;
+
+  /**
+   * The tempstore factory.
+   *
+   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
+   */
+  protected $tempstore;
+
+  /**
+   * The entity bundle info.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
+   */
+  protected $entityTypeBundleInfo;
+
+  /**
+   * PageManagerSectionStorage constructor.
+   *
+   * @param array $configuration
+   *   The plugin configuration, i.e. an array with configuration values keyed
+   *   by configuration option name. The special key 'context' may be used to
+   *   initialize the defined contexts by setting it to an array of context
+   *   values keyed by context names.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\layout_builder\Entity\SampleEntityGeneratorInterface $sample_entity_generator
+   *   The sample entity generator.
+   * @param \Drupal\Core\TempStore\SharedTempStoreFactory $tempstore
+   *   The tempstore factory.
+   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
+   *   The entity bundle information.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, SampleEntityGeneratorInterface $sample_entity_generator, SharedTempStoreFactory $tempstore, EntityTypeBundleInfoInterface $entity_type_bundle_info) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $this->entityTypeManager = $entity_type_manager;
+    $this->sampleEntityGenerator = $sample_entity_generator;
+    $this->tempstore = $tempstore;
+    $this->entityTypeBundleInfo = $entity_type_bundle_info;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('entity_type.manager'),
+      $container->get('layout_builder.sample_entity_generator'),
+      $container->get('tempstore.shared'),
+      $container->get('entity_type.bundle.info')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getSectionList() {
+    return $this->getContextValue('entity')->getVariantPlugin();
+  }
+
+  /**
+   * Gets the page variant entity.
+   *
+   * @return \Drupal\page_manager\Entity\PageVariant
+   *   The page variant entity.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   */
+  protected function getPageVariant() {
+    return $this->getContextValue('entity');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getStorageId() {
+    return $this->getContextValue('entity')->id();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRedirectUrl() {
+    return Url::fromUri($this->getPageVariant()->getPage()->getPath());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLayoutBuilderUrl($rel = 'view') {
+    return Url::fromRoute("layout_builder.page_manager.view", ['page_variant' => $this->getPageVariant()->id()]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRoutes(RouteCollection $collection) {
+    $path = '/admin/structure/page_manager/{page_variant}/layout';
+
+    $options['parameters']['page_variant']['type'] = 'entity:page_variant';
+
+    $options['_admin_route'] = FALSE;
+
+    $this->buildLayoutRoutes($collection, $this->getPluginDefinition(), $path, [], [], $options, '', 'page_variant');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deriveContextsFromRoute($value, $definition, $name, array $defaults) {
+    // Try to load from defaults.
+    $entity = $this->extractEntityFromRoute($value, $defaults);
+
+    // Otherwise try the tempstore.
+    if (!$entity) {
+      $entity = $this->tempstore->get('page_manager.layout_builder')->get($value);
+    }
+
+    return [
+      'entity' => EntityContext::fromEntity($entity),
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  private function extractEntityFromRoute($value, array $defaults) {
+    if (!empty($value)) {
+      return PageVariant::load($value);
+    }
+
+    return PageVariant::load($defaults['page_variant']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function label() {
+    return $this->getPageVariant()->label();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save() {
+    $page_variant = $this->getPageVariant();
+    return $page_variant->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
+    $result = AccessResult::allowedIf($this->isLayoutBuilderEnabled())->addCacheableDependency($this);
+    return $return_as_object ? $result : $result->isAllowed();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isApplicable(RefinableCacheableDependencyInterface $cacheability) {
+    return $this->isLayoutBuilderEnabled();
+  }
+
+  /**
+   * Determines if Layout Builder is enabled.
+   *
+   * @return bool
+   *   TRUE if Layout Builder is enabled, FALSE otherwise.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   */
+  public function isLayoutBuilderEnabled() {
+    return $this->getContextValue('entity')->getVariantPlugin() instanceof LayoutBuilderDisplayVariant;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSectionListFromId($id) {
+    // @todo
+    // This is deprecated and can be removed before Drupal 9.0.0.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function extractIdFromRoute($value, $definition, $name, array $defaults) {
+    // @todo
+    // This is deprecated and can be removed before Drupal 9.0.0.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContextsDuringPreview() {
+    $contexts = $this->getPageVariant()->getContexts() + $this->getPageVariant()->getStaticContexts();
+
+    foreach ($contexts as $name => $context) {
+      if (!$context->hasContextValue()) {
+        $data_type = $context->getContextDefinition()->getDataType();
+        if (strpos($data_type, 'entity:') === 0) {
+          list(, $entity_type_id) = explode(':', $data_type, 2);
+
+          $bundle = $entity_type_id;
+          if ($this->entityTypeManager->getDefinition($entity_type_id)->hasKey('bundle')) {
+            $bundle = key($this->entityTypeBundleInfo->getBundleInfo($entity_type_id));
+          }
+
+          $sample = $this->sampleEntityGenerator->get($entity_type_id, $bundle);
+          $contexts[$name] = Context::createFromContext($context, $sample);
+        }
+      }
+    }
+
+    return $contexts;
+  }
+
+}
diff --git a/tests/fixtures/update/page_manager.2960739.php b/tests/fixtures/update/page_manager.2960739.php
new file mode 100644
index 0000000..63bced9
--- /dev/null
+++ b/tests/fixtures/update/page_manager.2960739.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Test fixture for task #2960739.
+ *
+ * @see https://www.drupal.org/project/page_manager/issues/2960739
+ */
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Serialization\Yaml;
+
+$connection = Database::getConnection();
+
+$ext = $connection->select('config')
+  ->fields('config', ['data'])
+  ->where("name = 'core.extension'")
+  ->execute();
+
+$ext = unserialize($ext->fetchObject()->data);
+$ext['module']['page_manager'] = 0;
+$ext['module']['ctools'] = 0;
+
+$connection->update('config')
+  ->fields([
+    'data' => serialize($ext),
+  ])
+  ->where("name = 'core.extension'")
+  ->execute();
+
+$connection->insert('key_value')
+  ->fields([
+    'collection' => 'entity.definitions.installed',
+    'name' => 'page.entity_type',
+    'value' => 'O:42:"Drupal\Core\Config\Entity\ConfigEntityType":44:{s:16:" * config_prefix";N;s:15:" * static_cache";b:0;s:14:" * lookup_keys";a:1:{i:0;s:4:"uuid";}s:16:" * config_export";a:8:{i:0;s:2:"id";i:1;s:5:"label";i:2;s:11:"description";i:3;s:15:"use_admin_theme";i:4;s:4:"path";i:5;s:12:"access_logic";i:6;s:17:"access_conditions";i:7;s:10:"parameters";}s:21:" * mergedConfigExport";a:0:{}s:15:" * render_cache";b:1;s:19:" * persistent_cache";b:1;s:14:" * entity_keys";a:9:{s:2:"id";s:2:"id";s:5:"label";s:5:"label";s:6:"status";s:6:"status";s:8:"revision";s:0:"";s:6:"bundle";s:0:"";s:8:"langcode";s:8:"langcode";s:16:"default_langcode";s:16:"default_langcode";s:29:"revision_translation_affected";s:29:"revision_translation_affected";s:4:"uuid";s:4:"uuid";}s:5:" * id";s:4:"page";s:16:" * originalClass";s:31:"Drupal\page_manager\Entity\Page";s:11:" * handlers";a:2:{s:6:"access";s:37:"Drupal\page_manager\Entity\PageAccess";s:7:"storage";s:45:"Drupal\Core\Config\Entity\ConfigEntityStorage";}s:19:" * admin_permission";s:16:"administer pages";s:25:" * permission_granularity";s:11:"entity_type";s:8:" * links";a:0:{}s:17:" * label_callback";N;s:21:" * bundle_entity_type";N;s:12:" * bundle_of";N;s:15:" * bundle_label";N;s:13:" * base_table";N;s:22:" * revision_data_table";N;s:17:" * revision_table";N;s:13:" * data_table";N;s:11:" * internal";b:0;s:15:" * translatable";b:0;s:19:" * show_revision_ui";b:0;s:8:" * label";O:48:"Drupal\Core\StringTranslation\TranslatableMarkup":3:{s:9:" * string";s:4:"Page";s:12:" * arguments";a:0:{}s:10:" * options";a:0:{}}s:19:" * label_collection";s:0:"";s:17:" * label_singular";s:0:"";s:15:" * label_plural";s:0:"";s:14:" * label_count";a:0:{}s:15:" * uri_callback";N;s:8:" * group";s:13:"configuration";s:14:" * group_label";O:48:"Drupal\Core\StringTranslation\TranslatableMarkup":3:{s:9:" * string";s:13:"Configuration";s:12:" * arguments";a:0:{}s:10:" * options";a:1:{s:7:"context";s:17:"Entity type group";}}s:22:" * field_ui_base_route";N;s:26:" * common_reference_target";b:0;s:22:" * list_cache_contexts";a:0:{}s:18:" * list_cache_tags";a:1:{i:0;s:16:"config:page_list";}s:14:" * constraints";a:0:{}s:13:" * additional";a:0:{}s:8:" * class";s:31:"Drupal\page_manager\Entity\Page";s:11:" * provider";s:12:"page_manager";s:14:" * _serviceIds";a:0:{}s:18:" * _entityStorages";a:0:{}s:20:" * stringTranslation";N;}',
+  ])
+  ->execute();
+$connection->insert('key_value')
+  ->fields([
+    'collection' => 'entity.definitions.installed',
+    'name' => 'page_variant.entity_type',
+    'value' => 'O:42:"Drupal\Core\Config\Entity\ConfigEntityType":44:{s:16:" * config_prefix";N;s:15:" * static_cache";b:0;s:14:" * lookup_keys";a:2:{i:0;s:4:"page";i:1;s:4:"uuid";}s:16:" * config_export";a:10:{i:0;s:2:"id";i:1;s:5:"label";i:2;s:4:"uuid";i:3;s:7:"variant";i:4;s:16:"variant_settings";i:5;s:4:"page";i:6;s:6:"weight";i:7;s:18:"selection_criteria";i:8;s:15:"selection_logic";i:9;s:14:"static_context";}s:21:" * mergedConfigExport";a:0:{}s:15:" * render_cache";b:1;s:19:" * persistent_cache";b:1;s:14:" * entity_keys";a:8:{s:2:"id";s:2:"id";s:5:"label";s:5:"label";s:4:"uuid";s:4:"uuid";s:8:"revision";s:0:"";s:6:"bundle";s:0:"";s:8:"langcode";s:8:"langcode";s:16:"default_langcode";s:16:"default_langcode";s:29:"revision_translation_affected";s:29:"revision_translation_affected";}s:5:" * id";s:12:"page_variant";s:16:" * originalClass";s:38:"Drupal\page_manager\Entity\PageVariant";s:11:" * handlers";a:3:{s:12:"view_builder";s:49:"Drupal\page_manager\Entity\PageVariantViewBuilder";s:6:"access";s:44:"Drupal\page_manager\Entity\PageVariantAccess";s:7:"storage";s:45:"Drupal\Core\Config\Entity\ConfigEntityStorage";}s:19:" * admin_permission";s:16:"administer pages";s:25:" * permission_granularity";s:11:"entity_type";s:8:" * links";a:0:{}s:17:" * label_callback";N;s:21:" * bundle_entity_type";N;s:12:" * bundle_of";N;s:15:" * bundle_label";N;s:13:" * base_table";N;s:22:" * revision_data_table";N;s:17:" * revision_table";N;s:13:" * data_table";N;s:11:" * internal";b:0;s:15:" * translatable";b:0;s:19:" * show_revision_ui";b:0;s:8:" * label";O:48:"Drupal\Core\StringTranslation\TranslatableMarkup":3:{s:9:" * string";s:12:"Page Variant";s:12:" * arguments";a:0:{}s:10:" * options";a:0:{}}s:19:" * label_collection";s:0:"";s:17:" * label_singular";s:0:"";s:15:" * label_plural";s:0:"";s:14:" * label_count";a:0:{}s:15:" * uri_callback";N;s:8:" * group";s:13:"configuration";s:14:" * group_label";O:48:"Drupal\Core\StringTranslation\TranslatableMarkup":3:{s:9:" * string";s:13:"Configuration";s:12:" * arguments";a:0:{}s:10:" * options";a:1:{s:7:"context";s:17:"Entity type group";}}s:22:" * field_ui_base_route";N;s:26:" * common_reference_target";b:0;s:22:" * list_cache_contexts";a:0:{}s:18:" * list_cache_tags";a:1:{i:0;s:24:"config:page_variant_list";}s:14:" * constraints";a:0:{}s:13:" * additional";a:0:{}s:8:" * class";s:38:"Drupal\page_manager\Entity\PageVariant";s:11:" * provider";s:12:"page_manager";s:14:" * _serviceIds";a:0:{}s:18:" * _entityStorages";a:0:{}s:20:" * stringTranslation";N;}',
+  ])
+  ->execute();
+
+$connection->insert('config')
+  ->fields([
+    'collection' => '',
+    'name' => 'page_manager.page.test_page',
+    'data' => serialize(Yaml::decode(file_get_contents(__DIR__ . '/page_manager.page.test_page.yml'))),
+  ])
+  ->execute();
+
+$connection->insert('config')
+  ->fields([
+    'collection' => '',
+    'name' => 'page_manager.page_variant.test_page-block_display-0',
+    'data' => serialize(Yaml::decode(file_get_contents(__DIR__ . '/page_manager.page_variant.test_page-block_display-0.yml'))),
+  ])
+  ->execute();
diff --git a/tests/fixtures/update/page_manager.page.test_page.yml b/tests/fixtures/update/page_manager.page.test_page.yml
new file mode 100644
index 0000000..c2714f5
--- /dev/null
+++ b/tests/fixtures/update/page_manager.page.test_page.yml
@@ -0,0 +1,11 @@
+langcode: en
+status: true
+dependencies: {  }
+id: test_page
+label: 'Test page'
+description: ''
+use_admin_theme: false
+path: /test-url
+access_logic: and
+access_conditions: {  }
+parameters: {  }
diff --git a/tests/fixtures/update/page_manager.page_variant.test_page-block_display-0.yml b/tests/fixtures/update/page_manager.page_variant.test_page-block_display-0.yml
new file mode 100644
index 0000000..7de8d7c
--- /dev/null
+++ b/tests/fixtures/update/page_manager.page_variant.test_page-block_display-0.yml
@@ -0,0 +1,46 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - page_manager.page.test_page
+  module:
+    - ctools
+    - user
+id: test_page-block_display-0
+label: 'Block page'
+variant: block_display
+variant_settings:
+  blocks:
+    29550d0e-39f1-4fb9-bad6-c390dda5bd00:
+      id: 'entity_view:user'
+      label: 'Entity view (User)'
+      provider: ctools
+      label_display: visible
+      view_mode: default
+      region: top
+      weight: 0
+      uuid: 29550d0e-39f1-4fb9-bad6-c390dda5bd00
+      context_mapping:
+        entity: current_user
+  id: block_display
+  uuid: 19f8aeb3-5ba1-4abc-859f-0bcec05ada58
+  label: null
+  weight: 0
+  page_title: ''
+page: test_page
+weight: 0
+selection_criteria:
+  -
+    id: user_role
+    roles:
+      authenticated: authenticated
+    negate: false
+    context_mapping:
+      user: current_user
+  -
+    id: request_path
+    pages: /test-url
+    negate: false
+    context_mapping: {  }
+selection_logic: and
+static_context: {  }
diff --git a/tests/src/Functional/PageNodeAccessTest.php b/tests/src/Functional/PageNodeAccessTest.php
index 13cad53..b6585b5 100644
--- a/tests/src/Functional/PageNodeAccessTest.php
+++ b/tests/src/Functional/PageNodeAccessTest.php
@@ -73,7 +73,7 @@ class PageNodeAccessTest extends BrowserTestBase {
         RoleInterface::AUTHENTICATED_ID => RoleInterface::AUTHENTICATED_ID,
       ],
       'context_mapping' => [
-        'user' => 'current_user',
+        'user' => '@user.current_user_context:current_user',
       ],
     ]);
     $this->page->addAccessCondition([
diff --git a/tests/src/Functional/Update/UpdateContextName.php b/tests/src/Functional/Update/UpdateContextName.php
new file mode 100644
index 0000000..2c3bee1
--- /dev/null
+++ b/tests/src/Functional/Update/UpdateContextName.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\Tests\page_manager\Functional\Update;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+
+/**
+ * Tests context changes.
+ *
+ * @group Update
+ * @group page_manager
+ */
+class UpdateContextName extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      DRUPAL_ROOT . '/core/modules/system/tests/fixtures/update/drupal-8.8.0.filled.standard.php.gz',
+      __DIR__ . '/../../../fixtures/update/page_manager.2960739.php',
+    ];
+  }
+
+  /**
+   * Testing of current_user.
+   */
+  public function testUpdateCurrentUserContextName() {
+    $this->runUpdates();
+    $pageVariant = \Drupal::entityTypeManager()
+      ->getStorage('page_variant')
+      ->load('test_page-block_display-0');
+
+    $selection_criteria = $pageVariant->get('selection_criteria');
+    $this->assertEqual($selection_criteria[0]['context_mapping']['user'], '@user.current_user_context:current_user');
+
+    $variant_settings = $pageVariant->get('variant_settings');
+    $this->assertEqual($variant_settings['blocks']['29550d0e-39f1-4fb9-bad6-c390dda5bd00']['context_mapping']['entity'], '@user.current_user_context:current_user');
+  }
+
+}
diff --git a/tests/src/FunctionalJavascript/LayoutBuilderDisplayVariantTest.php b/tests/src/FunctionalJavascript/LayoutBuilderDisplayVariantTest.php
new file mode 100644
index 0000000..75f9990
--- /dev/null
+++ b/tests/src/FunctionalJavascript/LayoutBuilderDisplayVariantTest.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace Drupal\Tests\page_manager\FunctionalJavascript;
+
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait;
+use Drupal\Tests\user\Traits\UserCreationTrait;
+
+/**
+ * Test the layout_builder variant plugin.
+ *
+ * @group page_manager
+ */
+class LayoutBuilderDisplayVariantTest extends WebDriverTestBase {
+
+  use UserCreationTrait;
+  use ContextualLinkClickTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'layout_builder',
+    'page_manager_ui',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->drupalLogin($this->createUser(array_keys($this->container->get('user.permissions')
+      ->getPermissions())));
+  }
+
+  /**
+   * Tests the layout builder variant.
+   */
+  public function testLayoutBuilderVariant() {
+    $this->drupalGet('admin/structure/page_manager/add');
+    $page = $this->getSession()->getPage();
+    $assert_session = $this->assertSession();
+
+    $page->fillField('label', 'Example');
+    $assert_session->waitForElementVisible('css', '.machine-name-value');
+    $this->submitForm([
+      'path' => '/test-page',
+      'variant_plugin_id' => 'layout_builder',
+    ], t('Next'));
+
+    $this->submitForm([
+      'page_variant_label' => 'Layout Builder',
+    ], t('Next'));
+
+    // Add a two column section.
+    $page->find('css', '.layout-builder__link--add')->click();
+    $assert_session->waitForElementVisible('named', ['link', 'One column']);
+    $this->clickLink('Two column');
+    $assert_session->assertWaitOnAjaxRequest();
+    $page->pressButton('Add section');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    // Place a block in the first region.
+    $page->find('css', '.layout__region--first .layout-builder__link--add')
+      ->click();
+    $assert_session->assertWaitOnAjaxRequest();
+    $this->clickLink('Powered by Drupal');
+    $assert_session->assertWaitOnAjaxRequest();
+    $page->find('css', 'form.layout-builder-add-block .form-submit')->click();
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->pageTextContains('Powered by Drupal');
+
+    // Test content preview.
+    $page->uncheckField('toggle_content_preview');
+    $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.layout-builder-block__content-preview-placeholder-label'));
+
+    $page->pressButton('Finish');
+    $assert_session->pageTextContains('The layout has been saved.');
+
+    // Go the layout builder variant.
+    $this->drupalGet('admin/structure/page_manager/manage/example/page_variant__example-layout_builder-0__layout_builder');
+
+    // Place another block in the first region.
+    $page->find('css', '.layout__region--second .layout-builder__link--add')
+      ->click();
+    $assert_session->assertWaitOnAjaxRequest();
+    $this->clickLink('User account menu');
+    $assert_session->assertWaitOnAjaxRequest();
+    $page->find('css', 'form.layout-builder-add-block .form-submit')->click();
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->pageTextContains('User account menu');
+
+    // Place user view block.
+    $page->find('css', '.layout__region--second .layout-builder__link--add')
+      ->click();
+    $assert_session->assertWaitOnAjaxRequest();
+    $this->clickLink('Entity view (User)');
+    $assert_session->assertWaitOnAjaxRequest();
+    $page->find('css', 'form.layout-builder-add-block .form-submit')->click();
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->pageTextContains('Entity view (User)');
+
+    // Test contextual links.
+    $assert_session->waitForElement('css', '.block-system-powered-by-block .contextual');
+    $this->clickContextualLink('.block-system-powered-by-block', 'Configure');
+    $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas'));
+
+    // Save page.
+    $page->pressButton('Update and save');
+
+    // Check if block is rendered in the correct region.
+    $this->drupalGet('test-page');
+    $assert_session->waitForElementVisible('css', '.layout__region--first');
+    $assert_session->elementTextContains('css', '.layout__region--first', 'Powered by Drupal');
+    $assert_session->waitForElementVisible('css', '.layout__region--second');
+    $assert_session->elementTextContains('css', '.layout__region--second', 'User account menu');
+    $assert_session->elementTextContains('css', '.layout__region--second', 'Entity view (User)');
+  }
+
+}
diff --git a/tests/src/Unit/CurrentUserContextTest.php b/tests/src/Unit/CurrentUserContextTest.php
index a690f6e..69eaeac 100644
--- a/tests/src/Unit/CurrentUserContextTest.php
+++ b/tests/src/Unit/CurrentUserContextTest.php
@@ -2,14 +2,12 @@
 
 namespace Drupal\Tests\page_manager\Unit;
 
-use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
 use Drupal\Core\Plugin\Context\Context;
-use Drupal\Core\Session\AccountInterface;
-use Drupal\Core\TypedData\DataDefinition;
+use Drupal\Core\Plugin\Context\EntityContext;
+use Drupal\Core\Plugin\Context\EntityContextDefinition;
+use Drupal\Core\Plugin\Context\LazyContextRepository;
+use Drupal\Core\Session\AccountProxyInterface;
 use Drupal\page_manager\EventSubscriber\CurrentUserContext;
-use Drupal\user\UserInterface;
 use Prophecy\Argument;
 
 /**
@@ -25,32 +23,20 @@ class CurrentUserContextTest extends PageContextTestBase {
    * @covers ::onPageContext
    */
   public function testOnPageContext() {
-    $account = $this->prophesize(AccountInterface::class);
-    $account->id()->willReturn(1);
-    $user = $this->prophesize(UserInterface::class);
-
-    $data_definition = new DataDefinition(['type' => 'entity:user']);
-
-    $this->typedDataManager->create($data_definition, $user)
-      ->willReturn(EntityAdapter::createFromEntity($user->reveal()));
-
-    $this->typedDataManager->getDefaultConstraints($data_definition)
-      ->willReturn([]);
-
-    $this->typedDataManager->createDataDefinition('entity:user')
-      ->will(function () use ($data_definition) {
-        return $data_definition;
-      });
-
-    $this->page->addContext('current_user', Argument::type(Context::class))->shouldBeCalled();
-
-    $user_storage = $this->prophesize(EntityStorageInterface::class);
-    $user_storage->load(1)->willReturn($user->reveal());
-
-    $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
-    $entity_type_manager->getStorage('user')->willReturn($user_storage->reveal());
-
-    $route_param_context = new CurrentUserContext($account->reveal(), $entity_type_manager->reveal());
+    $currentUser = $this->getMockBuilder(AccountProxyInterface::class)
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $contextRepository = $this->getMockBuilder(LazyContextRepository::class)
+      ->disableOriginalConstructor()
+      ->getMock();
+    $currentUserContext = new EntityContext(new EntityContextDefinition('user', 'current_user_context'), $currentUser->getAccount());
+    $contextRepository->expects($this->once())
+      ->method('getRunTimeContexts')
+      ->willReturn(['@user.current_user_context:current_user' => $currentUserContext]);
+
+    $this->page->addContext('@user.current_user_context:current_user', Argument::type(Context::class))->shouldBeCalled();
+    $route_param_context = new CurrentUserContext($contextRepository);
     $route_param_context->onPageContext($this->event);
   }
 
-- 
2.20.1

