diff --git a/core/modules/layout_builder/layout_builder.links.contextual.yml b/core/modules/layout_builder/layout_builder.links.contextual.yml
index bcf2a9cf06..4af753702d 100644
--- a/core/modules/layout_builder/layout_builder.links.contextual.yml
+++ b/core/modules/layout_builder/layout_builder.links.contextual.yml
@@ -17,3 +17,13 @@ layout_builder_block_remove:
class: ['use-ajax']
data-dialog-type: dialog
data-dialog-renderer: off_canvas
+
+layout_builder_block_visibility:
+ title: 'Control visibility'
+ route_name: 'layout_builder.visibility'
+ group: 'layout_builder_block'
+ options:
+ attributes:
+ class: ['use-ajax']
+ data-dialog-type: dialog
+ data-dialog-renderer: off_canvas
\ No newline at end of file
diff --git a/core/modules/layout_builder/layout_builder.routing.yml b/core/modules/layout_builder/layout_builder.routing.yml
index 085eb30111..9b7b067312 100644
--- a/core/modules/layout_builder/layout_builder.routing.yml
+++ b/core/modules/layout_builder/layout_builder.routing.yml
@@ -116,5 +116,41 @@ layout_builder.move_block:
section_storage:
layout_builder_tempstore: TRUE
+layout_builder.visibility:
+ path: '/layout_builder/visibility/block/{section_storage_type}/{section_storage}/{delta}/{uuid}'
+ defaults:
+ _form: '\Drupal\layout_builder\Form\BlockVisibilityForm'
+ requirements:
+ _permission: 'configure any layout'
+ options:
+ _admin_route: TRUE
+ parameters:
+ section_storage:
+ layout_builder_tempstore: TRUE
+
+layout_builder.add_visibility:
+ path: '/layout_builder/visibility/block/{section_storage_type}/{section_storage}/{delta}/{uuid}/{plugin_id}'
+ defaults:
+ _form: '\Drupal\layout_builder\Form\ConfigureVisibility'
+ requirements:
+ _permission: 'configure any layout'
+ options:
+ _admin_route: TRUE
+ parameters:
+ section_storage:
+ layout_builder_tempstore: TRUE
+
+layout_builder.delete_visibility:
+ path: '/layout_builder/visibility/block/{section_storage_type}/{section_storage}/{delta}/{uuid}/{plugin_id}/delete'
+ defaults:
+ _form: '\Drupal\layout_builder\Form\DeleteVisibility'
+ requirements:
+ _permission: 'configure any layout'
+ options:
+ _admin_route: TRUE
+ parameters:
+ section_storage:
+ layout_builder_tempstore: TRUE
+
route_callbacks:
- 'layout_builder.routes:getRoutes'
diff --git a/core/modules/layout_builder/layout_builder.services.yml b/core/modules/layout_builder/layout_builder.services.yml
index 999977b607..feeeeb656e 100644
--- a/core/modules/layout_builder/layout_builder.services.yml
+++ b/core/modules/layout_builder/layout_builder.services.yml
@@ -31,3 +31,8 @@ services:
arguments: ['@current_user']
tags:
- { name: event_subscriber }
+ component_visibility_subscriber:
+ class: Drupal\layout_builder\EventSubscriber\SectionComponentVisibility
+ arguments: ['@context.handler', '@plugin.manager.condition']
+ tags:
+ - { name: event_subscriber }
diff --git a/core/modules/layout_builder/src/Controller/LayoutBuilderController.php b/core/modules/layout_builder/src/Controller/LayoutBuilderController.php
index 3f3b95e49b..30cdd0b606 100644
--- a/core/modules/layout_builder/src/Controller/LayoutBuilderController.php
+++ b/core/modules/layout_builder/src/Controller/LayoutBuilderController.php
@@ -172,7 +172,7 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s
$section = $section_storage->getSection($delta);
$layout = $section->getLayout();
- $build = $section->toRenderArray($this->getAvailableContexts($section_storage));
+ $build = $section->toRenderArray($this->getAvailableContexts($section_storage), TRUE);
$layout_definition = $layout->getPluginDefinition();
foreach ($layout_definition->getRegions() as $region => $info) {
diff --git a/core/modules/layout_builder/src/EventSubscriber/SectionComponentVisibility.php b/core/modules/layout_builder/src/EventSubscriber/SectionComponentVisibility.php
new file mode 100644
index 0000000000..200f103512
--- /dev/null
+++ b/core/modules/layout_builder/src/EventSubscriber/SectionComponentVisibility.php
@@ -0,0 +1,82 @@
+contextHandler = $context_handler;
+ $this->conditionManager = $condition_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents() {
+ $events[LayoutBuilderEvents::SECTION_COMPONENT_BUILD_RENDER_ARRAY] = ['onBuildRender', 255];
+ return $events;
+ }
+
+ /**
+ * Determines the visibility of section components.
+ *
+ * @param \Drupal\layout_builder\Event\SectionComponentBuildRenderArrayEvent $event
+ * The section component build render array event.
+ */
+ public function onBuildRender(SectionComponentBuildRenderArrayEvent $event) {
+ $conditions = [];
+ if (!$event->isPreview()) {
+ foreach ($event->getComponent()->get('visibility') as $uuid => $condition) {
+ $condition = $this->conditionManager->createInstance($condition['id'], $condition);
+ if ($condition instanceof ContextAwarePluginInterface) {
+ $this->contextHandler->applyContextMapping($condition, $event->getContexts());
+ }
+ $event->addCacheableDependency($condition);
+ $conditions[$uuid] = $condition;
+ }
+ }
+
+ // @todo Add and/or resolution form element.
+ if ($conditions && !$this->resolveConditions($conditions, 'and')) {
+ $event->addCacheableDependency($event->getPlugin());
+ // If conditions do not resolve, do not process other subscribers.
+ $event->stopPropagation();
+ }
+ }
+
+}
diff --git a/core/modules/layout_builder/src/Form/BlockVisibilityForm.php b/core/modules/layout_builder/src/Form/BlockVisibilityForm.php
new file mode 100644
index 0000000000..180e2acb1f
--- /dev/null
+++ b/core/modules/layout_builder/src/Form/BlockVisibilityForm.php
@@ -0,0 +1,209 @@
+conditionManager = $condition_manager;
+ $this->formBuilder = $form_builder;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('plugin.manager.condition'),
+ $container->get('form_builder')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ return 'layout_builder_block_visibility';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL, $delta = NULL, $uuid = NULL) {
+ $this->sectionStorage = $section_storage;
+ $this->delta = $delta;
+ $this->uuid = $uuid;
+
+ $visibility_conditions = $section_storage->getSection($delta)->getComponent($uuid)->get('visibility');
+ if (!$visibility_conditions) {
+ $visibility_conditions = [];
+ }
+ $conditions = [];
+ foreach ($this->conditionManager->getDefinitionsForContexts($this->getAvailableContexts($section_storage)) as $plugin_id => $definition) {
+ $conditions[$plugin_id] = $definition['label'];
+ }
+ $form['condition'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Add a visibility condition'),
+ '#options' => $conditions,
+ '#empty_value' => '',
+ ];
+ $items = [];
+ foreach ($visibility_conditions as $visibility_id => $configuration) {
+ /** @var \Drupal\Core\Condition\ConditionInterface $condition */
+ $condition = $this->conditionManager->createInstance($configuration['id'], $configuration);
+ $condition->summary();
+ $options = [
+ 'attributes' => [
+ 'class' => ['use-ajax'],
+ 'data-dialog-type' => 'dialog',
+ 'data-dialog-renderer' => 'off_canvas',
+ 'data-outside-in-edit' => TRUE,
+ ],
+ ];
+ $items[] = [
+ ['#markup' => $condition->getPluginId() . '
' . $condition->summary()],
+ [
+ '#type' => 'operations',
+ '#links' => [
+ 'edit' => [
+ 'title' => $this->t('Edit'),
+ 'url' => Url::fromRoute('layout_builder.add_visibility', $this->getParameters($visibility_id), $options),
+ ],
+ 'delete' => [
+ 'title' => $this->t('Delete'),
+ 'url' => Url::fromRoute('layout_builder.delete_visibility', $this->getParameters($visibility_id), $options),
+ ],
+ ],
+ ],
+ ];
+ }
+ if ($items) {
+ $form['visibility_title'] = [
+ '#markup' => '
' . $this->t('Configured Conditions') . '
',
+ ];
+ $form['visibility'] = [
+ '#prefix' => '',
+ '#suffix' => '
',
+ '#theme' => 'item_list',
+ '#items' => $items,
+ '#empty' => $this->t('No required conditions have been configured.'),
+ ];
+ }
+ $form['actions']['submit'] = [
+ '#type' => 'submit',
+ '#value' => $this->t('Add Condition'),
+ ];
+ if ($this->isAjax()) {
+ $form['actions']['submit']['#ajax']['callback'] = '::ajaxSubmit';
+ $form['actions']['submit']['#ajax']['event'] = 'click';
+ }
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state) {
+ $condition = $form_state->getValue('condition');
+ $parameters = $this->getParameters($condition);
+ $new_form = $this->formBuilder->getForm('\Drupal\layout_builder\Form\ConfigureVisibility', $this->sectionStorage, $parameters['delta'], $parameters['uuid'], $parameters['plugin_id']);
+ $new_form['#action'] = (new Url('layout_builder.add_visibility', $parameters))->toString();
+ $new_form['actions']['submit']['#attached']['drupalSettings']['ajax'][$new_form['actions']['submit']['#id']]['url'] = new Url('layout_builder.add_visibility', $parameters, ['query' => [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]]);
+ $response = new AjaxResponse();
+ $response->addCommand(new OpenOffCanvasDialogCommand($this->t("Configure Condition"), $new_form));
+ return $response;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $parameters = $this->getParameters($form_state->getValue('condition'));
+ $url = new Url('layout_builder.add_visibility', $parameters);
+ $response = new RedirectResponse($url->toString());
+ $form_state->setResponse($response);
+ }
+
+ /**
+ * Gets the parameters needed for the various url and form invocations.
+ *
+ * @param string $visibility_id
+ * The id of the visibility plugin.
+ *
+ * @return array
+ */
+ protected function getParameters($visibility_id) {
+ return [
+ 'section_storage_type' => $this->sectionStorage->getStorageType(),
+ 'section_storage' => $this->sectionStorage->getStorageId(),
+ 'delta' => $this->delta,
+ 'uuid' => $this->uuid,
+ 'plugin_id' => $visibility_id,
+ ];
+ }
+
+}
diff --git a/core/modules/layout_builder/src/Form/ConfigureVisibility.php b/core/modules/layout_builder/src/Form/ConfigureVisibility.php
new file mode 100644
index 0000000000..7967be457e
--- /dev/null
+++ b/core/modules/layout_builder/src/Form/ConfigureVisibility.php
@@ -0,0 +1,243 @@
+layoutTempstoreRepository = $layout_tempstore_repository;
+ $this->conditionManager = $condition_manager;
+ $this->uuidGenerator = $uuid_generator;
+ $this->pluginFormFactory = $plugin_form_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('layout_builder.tempstore_repository'),
+ $container->get('plugin.manager.condition'),
+ $container->get('uuid'),
+ $container->get('plugin_form.factory')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ return 'layout_builder_configure_visibility';
+ }
+
+ /**
+ * Prepares the condition plugin based on the condition ID.
+ *
+ * @param string $condition_id
+ * A condition UUID, or the plugin ID used to create a new condition.
+ *
+ * @param array $value
+ * The condition configuration.
+ *
+ * @return \Drupal\Core\Condition\ConditionInterface
+ * The condition plugin.
+ */
+ protected function prepareCondition($condition_id, array $value) {
+ if ($value) {
+ return $this->conditionManager->createInstance($value['id'], $value);
+ }
+ /** @var \Drupal\Core\Condition\ConditionInterface $condition */
+ $condition = $this->conditionManager->createInstance($condition_id);
+ $configuration = $condition->getConfiguration();
+ $configuration['uuid'] = $this->uuidGenerator->generate();
+ $condition->setConfiguration($configuration);
+ return $condition;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL, $delta = NULL, $uuid = NULL, $plugin_id = NULL) {
+ $this->sectionStorage = $section_storage;
+ $this->delta = $delta;
+ $this->uuid = $uuid;
+
+ $visibility_conditions = $section_storage->getSection($delta)->getComponent($uuid)->get('visibility');
+ $configuration = !empty($visibility_conditions[$plugin_id]) ? $visibility_conditions[$plugin_id] : [];
+ $this->condition = $this->prepareCondition($plugin_id, $configuration);
+
+ $form_state->setTemporaryValue('gathered_contexts', $this->getAvailableContexts($section_storage));
+
+ $form['#tree'] = TRUE;
+ $form['settings'] = [];
+ $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state);
+ $form['settings'] = $this->getPluginForm($this->condition)->buildConfigurationForm($form['settings'], $subform_state);
+ $form['settings']['id'] = [
+ '#type' => 'value',
+ '#value' => $this->condition->getPluginId(),
+ ];
+
+ $form['actions']['submit'] = [
+ '#type' => 'submit',
+ '#value' => $configuration ? $this->t('Update') : $this->t('Add Condition'),
+ '#button_type' => 'primary',
+ ];
+
+ if ($this->isAjax()) {
+ $form['actions']['submit']['#ajax']['callback'] = '::ajaxSubmit';
+ }
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateForm(array &$form, FormStateInterface $form_state) {
+ $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state);
+ $this->getPluginForm($this->condition)->validateConfigurationForm($form['settings'], $subform_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ // Call the plugin submit handler.
+ $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state);
+ $this->getPluginForm($this->condition)->submitConfigurationForm($form, $subform_state);
+
+ // If this block is context-aware, set the context mapping.
+ if ($this->condition instanceof ContextAwarePluginInterface) {
+ $this->condition->setContextMapping($subform_state->getValue('context_mapping', []));
+ }
+
+ $configuration = $this->condition->getConfiguration();
+
+ $component = $this->sectionStorage->getSection($this->delta)->getComponent($this->uuid);
+ $visibility_conditions = $component->get('visibility');
+ $visibility_conditions[$configuration['uuid']] = $configuration;
+ $component->set('visibility', $visibility_conditions);
+
+ $this->layoutTempstoreRepository->set($this->sectionStorage);
+ $form_state->setRedirectUrl($this->sectionStorage->getLayoutBuilderUrl());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state) {
+ return $this->rebuildAndClose($this->sectionStorage);
+ }
+
+ /**
+ * Retrieves the plugin form for a given condition.
+ *
+ * @param \Drupal\Core\Condition\ConditionInterface $condition
+ * The condition plugin.
+ *
+ * @return \Drupal\Core\Plugin\PluginFormInterface
+ * The plugin form for the condition.
+ */
+ protected function getPluginForm(ConditionInterface $condition) {
+ if ($condition instanceof PluginWithFormsInterface) {
+ return $this->pluginFormFactory->createInstance($condition, 'configure');
+ }
+ return $condition;
+ }
+
+}
diff --git a/core/modules/layout_builder/src/Form/DeleteVisibility.php b/core/modules/layout_builder/src/Form/DeleteVisibility.php
new file mode 100644
index 0000000000..62b718d55f
--- /dev/null
+++ b/core/modules/layout_builder/src/Form/DeleteVisibility.php
@@ -0,0 +1,162 @@
+layoutTempstoreRepository = $layout_tempstore_repository;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('layout_builder.tempstore_repository')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL, $delta = NULL, $uuid = NULL, $plugin_id = NULL) {
+ $this->sectionStorage = $section_storage;
+ $this->delta = $delta;
+ $this->uuid = $uuid;
+ $this->plugin_id = $plugin_id;
+ $form = parent::buildForm($form, $form_state);
+ $form['actions']['cancel'] = $this->buildCancelLink();
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getQuestion() {
+ return $this->t("Are you sure you want to delete this visibility condition?");
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCancelUrl() {
+ $parameters = $this->getParameters();
+ return new Url('layout_builder.visibility', $parameters);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ return 'layout_builder_delete_visibility';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $component = $this->sectionStorage->getSection($this->delta)->getComponent($this->uuid);
+ $visibility_conditions = $component->get('visibility');
+ unset($visibility_conditions[$this->plugin_id]);
+ $component->set('visibility', $visibility_conditions);
+ $this->layoutTempstoreRepository->set($this->sectionStorage);
+ $form_state->setRedirectUrl($this->sectionStorage->getLayoutBuilderUrl());
+ }
+
+ /**
+ * Build a cancel button for the confirm form.
+ */
+ protected function buildCancelLink() {
+ return [
+ '#type' => 'button',
+ '#value' => $this->getCancelText(),
+ '#ajax' => [
+ 'callback' => '::ajaxCancel',
+ ],
+ ];
+ }
+
+ /**
+ * Provides an ajax callback for the cancel button.
+ */
+ public function ajaxCancel(array &$form, FormStateInterface $form_state) {
+ $parameters = $this->getParameters();
+ $new_form = \Drupal::formBuilder()->getForm('\Drupal\layout_builder\Form\BlockVisibilityForm', $parameters['section_storage_type'], $parameters['section_storage'], $parameters['delta'], $parameters['uuid']);
+ $new_form['#action'] = $this->getCancelUrl()->toString();
+ $response = new AjaxResponse();
+ $response->addCommand(new OpenOffCanvasDialogCommand($this->t("Configure Condition"), $new_form));
+ return $response;
+ }
+
+ /**
+ * Gets the parameters needed for the various url and form invocations.
+ *
+ * @return array
+ */
+ protected function getParameters() {
+ return [
+ 'section_storage_type' => $this->sectionStorage->getStorageType(),
+ 'section_storage' => $this->sectionStorage->getStorageId(),
+ 'delta' => $this->delta,
+ 'uuid' => $this->uuid,
+ ];
+ }
+
+}
diff --git a/core/modules/layout_builder/src/Section.php b/core/modules/layout_builder/src/Section.php
index 02f274e784..e9f19e7d64 100644
--- a/core/modules/layout_builder/src/Section.php
+++ b/core/modules/layout_builder/src/Section.php
@@ -68,14 +68,16 @@ public function __construct($layout_id, array $layout_settings = [], array $comp
*
* @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
* An array of available contexts.
+ * @param bool $preview
+ * (Optional) Determines if we're rendering a preview or not.
*
* @return array
* A renderable array representing the content of the section.
*/
- public function toRenderArray(array $contexts = []) {
+ public function toRenderArray(array $contexts = [], $preview = FALSE) {
$regions = [];
foreach ($this->getComponents() as $component) {
- if ($output = $component->toRenderArray($contexts)) {
+ if ($output = $component->toRenderArray($contexts, $preview)) {
$regions[$component->getRegion()][$component->getUuid()] = $output;
}
}
diff --git a/core/modules/layout_builder/src/SectionComponent.php b/core/modules/layout_builder/src/SectionComponent.php
index 6daa17c0d2..079da4e1f1 100644
--- a/core/modules/layout_builder/src/SectionComponent.php
+++ b/core/modules/layout_builder/src/SectionComponent.php
@@ -91,12 +91,14 @@ public function __construct($uuid, $region, array $configuration = [], array $ad
*
* @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
* An array of available contexts.
+ * @param bool $preview
+ * (Optional) Determines if we're rendering a preview or not.
*
* @return array
* A renderable array representing the content of the component.
*/
- public function toRenderArray(array $contexts = []) {
- $event = new SectionComponentBuildRenderArrayEvent($this, $contexts);
+ public function toRenderArray(array $contexts = [], $preview = FALSE) {
+ $event = new SectionComponentBuildRenderArrayEvent($this, $contexts, $preview);
$this->eventDispatcher()->dispatch(LayoutBuilderEvents::SECTION_COMPONENT_BUILD_RENDER_ARRAY, $event);
$output = $event->getBuild();
$event->getCacheableMetadata()->applyTo($output);