diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml
index 9278240..6263d37 100644
--- a/core/config/schema/core.data_types.schema.yml
+++ b/core/config/schema/core.data_types.schema.yml
@@ -332,6 +332,9 @@ block_settings:
sequence:
type: string
+block.settings.*:
+ type: block_settings
+
condition.plugin:
type: mapping
label: 'Condition'
diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml
index df2a2fd..18742ac 100644
--- a/core/config/schema/core.entity.schema.yml
+++ b/core/config/schema/core.entity.schema.yml
@@ -78,6 +78,27 @@ core.entity_view_display.*.*.*:
label: 'Third party settings'
sequence:
type: field.formatter.third_party.[%key]
+ blocks:
+ type: sequence
+ label: 'Block fields'
+ sequence:
+ type: mapping
+ mapping:
+ id:
+ type: string
+ label: 'ID'
+ plugin_id:
+ type: string
+ label: 'Plugin ID'
+ weight:
+ type: integer
+ label: 'Weight'
+ region:
+ type: string
+ label: 'Region'
+ settings:
+ type: block.settings.[id]
+
hidden:
type: sequence
label: 'Field display setting'
diff --git a/core/core.services.yml b/core/core.services.yml
index 30fbcc2..b98fae8 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -338,7 +338,7 @@ services:
- [setValidationConstraintManager, ['@validation.constraint']]
context.handler:
class: Drupal\Core\Plugin\Context\ContextHandler
- arguments: ['@typed_data_manager']
+ arguments: ['@context.repository']
context.repository:
class: Drupal\Core\Plugin\Context\LazyContextRepository
arguments: ['@service_container']
diff --git a/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php b/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php
index 3598b51..a930f30 100644
--- a/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php
+++ b/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php
@@ -46,4 +46,24 @@ public function build(FieldableEntityInterface $entity);
*/
public function buildMultiple(array $entities);
+ /**
+ * @todo.
+ */
+ public function getBlocks();
+
+ /**
+ * @todo.
+ */
+ public function getBlock($name);
+
+ /**
+ * @todo.
+ */
+ public function removeBlock($name);
+
+ /**
+ * @todo.
+ */
+ public function setBlock($name, $block);
+
}
diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
index 74b15b6..0a29fb1 100644
--- a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
+++ b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
@@ -8,6 +8,7 @@
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\EntityDisplayBase;
+use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\TypedData\TranslatableInterface;
/**
@@ -27,6 +28,7 @@
* "bundle",
* "mode",
* "content",
+ * "blocks",
* "hidden",
* }
* )
@@ -39,6 +41,47 @@ class EntityViewDisplay extends EntityDisplayBase implements EntityViewDisplayIn
protected $displayContext = 'view';
/**
+ * @todo.
+ *
+ * @var array
+ */
+ protected $blocks = [];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBlocks() {
+ return $this->blocks;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBlock($name) {
+ $blocks = $this->getBlocks();
+ return isset($blocks[$name]) ? $blocks[$name] : NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeBlock($name) {
+ unset($this->blocks[$name]);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setBlock($name, $block) {
+ $this->blocks[$name] = $block + [
+ 'weight' => 0,
+ 'region' => $this->getDefaultRegion(),
+ ];
+ return $this;
+ }
+
+ /**
* Returns the display objects used to render a set of entities.
*
* Depending on the configuration of the view mode for each bundle, this can
@@ -263,6 +306,21 @@ public function buildMultiple(array $entities) {
}
}
+ foreach ($this->getBlocks() as $name => $block_info) {
+ /** @var \Drupal\Core\Block\BlockPluginInterface $block */
+ $block = \Drupal::service('plugin.manager.block')->createInstance($block_info['plugin_id'], $block_info['settings']);
+
+ if ($block instanceof ContextAwarePluginInterface) {
+ \Drupal::service('context.handler')->applyRuntimeContext($block);
+ }
+
+ $block_access = $block->access(\Drupal::currentUser(), TRUE);
+ foreach ($entities as $id => $entity) {
+ $build_list[$id][$name] = $block_access->isAllowed() ? $block->build() : [];
+ $this->renderer->addCacheableDependency($build_list[$id][$name], $block_access);
+ }
+ }
+
foreach ($entities as $id => $entity) {
// Assign the configured weights.
foreach ($this->getComponents() as $name => $options) {
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
index 2a13d36..470511d 100644
--- a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
@@ -12,6 +12,23 @@
class ContextHandler implements ContextHandlerInterface {
/**
+ * The context repository.
+ *
+ * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
+ */
+ protected $contextRepository;
+
+ /**
+ * Constructs a new ContextHandler.
+ *
+ * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
+ * The context repository.
+ */
+ public function __construct(ContextRepositoryInterface $context_repository) {
+ $this->contextRepository = $context_repository;
+ }
+
+ /**
* {@inheritdoc}
*/
public function filterPluginDefinitionsByContexts(array $contexts, array $definitions) {
@@ -118,4 +135,12 @@ public function applyContextMapping(ContextAwarePluginInterface $plugin, $contex
}
}
+ /**
+ * {@inheritdoc}
+ */
+ public function applyRuntimeContext(ContextAwarePluginInterface $plugin) {
+ $contexts = $this->contextRepository->getRuntimeContexts(array_values($plugin->getContextMapping()));
+ $this->applyContextMapping($plugin, $contexts);
+ }
+
}
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php b/core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php
index a0d5703..1c5c622 100644
--- a/core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php
@@ -61,6 +61,9 @@ public function getMatchingContexts(array $contexts, ContextDefinitionInterface
/**
* Prepares a plugin for evaluation.
*
+ * This method is for more complex use cases, see ::applyRuntimeContext() for
+ * the more common approach.
+ *
* @param \Drupal\Core\Plugin\ContextAwarePluginInterface $plugin
* A plugin about to be evaluated.
* @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
@@ -77,4 +80,12 @@ public function getMatchingContexts(array $contexts, ContextDefinitionInterface
*/
public function applyContextMapping(ContextAwarePluginInterface $plugin, $contexts, $mappings = []);
+ /**
+ * Applies all relevant runtime contexts to a plugin.
+ *
+ * @param \Drupal\Core\Plugin\ContextAwarePluginInterface $plugin
+ * A context-aware plugin.
+ */
+ public function applyRuntimeContext(ContextAwarePluginInterface $plugin);
+
}
diff --git a/core/modules/block/config/schema/block.schema.yml b/core/modules/block/config/schema/block.schema.yml
index 16d79bc..7dc4f53 100644
--- a/core/modules/block/config/schema/block.schema.yml
+++ b/core/modules/block/config/schema/block.schema.yml
@@ -30,6 +30,3 @@ block.block.*:
sequence:
type: condition.plugin.[id]
label: 'Visibility Condition'
-
-block.settings.*:
- type: block_settings
diff --git a/core/modules/block/src/BlockAccessControlHandler.php b/core/modules/block/src/BlockAccessControlHandler.php
index 176c326..73af3c4 100644
--- a/core/modules/block/src/BlockAccessControlHandler.php
+++ b/core/modules/block/src/BlockAccessControlHandler.php
@@ -12,7 +12,6 @@
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
-use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -34,20 +33,12 @@ class BlockAccessControlHandler extends EntityAccessControlHandler implements En
protected $contextHandler;
/**
- * The context manager service.
- *
- * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
- */
- protected $contextRepository;
-
- /**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
- $container->get('context.handler'),
- $container->get('context.repository')
+ $container->get('context.handler')
);
}
@@ -58,13 +49,10 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
* The entity type definition.
* @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler
* The ContextHandler for applying contexts to conditions properly.
- * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
- * The lazy context repository service.
*/
- public function __construct(EntityTypeInterface $entity_type, ContextHandlerInterface $context_handler, ContextRepositoryInterface $context_repository ) {
+ public function __construct(EntityTypeInterface $entity_type, ContextHandlerInterface $context_handler) {
parent::__construct($entity_type);
$this->contextHandler = $context_handler;
- $this->contextRepository = $context_repository;
}
@@ -87,8 +75,7 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
foreach ($entity->getVisibilityConditions() as $condition_id => $condition) {
if ($condition instanceof ContextAwarePluginInterface) {
try {
- $contexts = $this->contextRepository->getRuntimeContexts(array_values($condition->getContextMapping()));
- $this->contextHandler->applyContextMapping($condition, $contexts);
+ $this->contextHandler->applyRuntimeContext($condition);
}
catch (ContextException $e) {
$missing_context = TRUE;
@@ -113,8 +100,7 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
$block_plugin = $entity->getPlugin();
try {
if ($block_plugin instanceof ContextAwarePluginInterface) {
- $contexts = $this->contextRepository->getRuntimeContexts(array_values($block_plugin->getContextMapping()));
- $this->contextHandler->applyContextMapping($block_plugin, $contexts);
+ $this->contextHandler->applyRuntimeContext($block_plugin);
}
$access = $block_plugin->access($account, TRUE);
}
diff --git a/core/modules/block/src/BlockViewBuilder.php b/core/modules/block/src/BlockViewBuilder.php
index 3b20d7b..018d617 100644
--- a/core/modules/block/src/BlockViewBuilder.php
+++ b/core/modules/block/src/BlockViewBuilder.php
@@ -140,8 +140,7 @@ protected static function buildPreRenderableBlock($entity, ModuleHandlerInterfac
// Inject runtime contexts.
if ($plugin instanceof ContextAwarePluginInterface) {
- $contexts = \Drupal::service('context.repository')->getRuntimeContexts($plugin->getContextMapping());
- \Drupal::service('context.handler')->applyContextMapping($plugin, $contexts);
+ \Drupal::service('context.handler')->applyRuntimeContext($plugin);
}
// Create the render array for the block as a whole.
diff --git a/core/modules/field_layout/src/Form/FieldLayoutEntityViewDisplayEditForm.php b/core/modules/field_layout/src/Form/FieldLayoutEntityViewDisplayEditForm.php
index 92bfbde..11725d8 100644
--- a/core/modules/field_layout/src/Form/FieldLayoutEntityViewDisplayEditForm.php
+++ b/core/modules/field_layout/src/Form/FieldLayoutEntityViewDisplayEditForm.php
@@ -3,6 +3,7 @@
namespace Drupal\field_layout\Form;
use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Core\Block\BlockManagerInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Layout\LayoutPluginManagerInterface;
use Drupal\field_ui\Form\EntityViewDisplayEditForm;
@@ -22,11 +23,13 @@ class FieldLayoutEntityViewDisplayEditForm extends EntityViewDisplayEditForm {
* The field type manager.
* @param \Drupal\Component\Plugin\PluginManagerBase $plugin_manager
* The formatter plugin manager.
+ * @param \Drupal\Core\Block\BlockManagerInterface $block_manager
+ * The block manager.
* @param \Drupal\Core\Layout\LayoutPluginManagerInterface $layout_plugin_manager
* The field layout plugin manager.
*/
- public function __construct(FieldTypePluginManagerInterface $field_type_manager, PluginManagerBase $plugin_manager, LayoutPluginManagerInterface $layout_plugin_manager) {
- parent::__construct($field_type_manager, $plugin_manager);
+ public function __construct(FieldTypePluginManagerInterface $field_type_manager, PluginManagerBase $plugin_manager, BlockManagerInterface $block_manager, LayoutPluginManagerInterface $layout_plugin_manager) {
+ parent::__construct($field_type_manager, $plugin_manager, $block_manager);
$this->layoutPluginManager = $layout_plugin_manager;
}
@@ -37,6 +40,7 @@ public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.field.field_type'),
$container->get('plugin.manager.field.formatter'),
+ $container->get('plugin.manager.block'),
$container->get('plugin.manager.core.layout')
);
}
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index 79b3f15..8f54bdf 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -11,6 +11,7 @@
use Drupal\Core\Entity\EntityViewModeInterface;
use Drupal\Core\Entity\EntityFormModeInterface;
use Drupal\Core\Url;
+use Drupal\field_ui\Controller\FieldAddBlockController;
use Drupal\field_ui\FieldUI;
use Drupal\field_ui\Plugin\Derivative\FieldUiLocalTask;
@@ -81,6 +82,7 @@ function field_ui_entity_type_build(array &$entity_types) {
$entity_types['entity_form_display']->setFormClass('edit', 'Drupal\field_ui\Form\EntityFormDisplayEditForm');
$entity_types['entity_view_display']->setFormClass('edit', 'Drupal\field_ui\Form\EntityViewDisplayEditForm');
+ $entity_types['entity_view_display']->setFormClass('add_block', FieldAddBlockController::class);
$form_mode = $entity_types['entity_form_mode'];
$form_mode->setListBuilderClass('Drupal\field_ui\EntityFormModeListBuilder');
diff --git a/core/modules/field_ui/src/Controller/FieldAddBlockController.php b/core/modules/field_ui/src/Controller/FieldAddBlockController.php
new file mode 100644
index 0000000..273becc
--- /dev/null
+++ b/core/modules/field_ui/src/Controller/FieldAddBlockController.php
@@ -0,0 +1,205 @@
+blockManager = $block_manager;
+ $this->contextRepository = $context_repository;
+ $this->entityFieldManager = $entity_field_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('plugin.manager.block'),
+ $container->get('context.repository'),
+ $container->get('entity_field.manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) {
+ $route_parameters = $route_match->getParameters()->all();
+
+ return $this->getEntityDisplay($route_parameters['entity_type_id'], $route_parameters['bundle'], $route_parameters['view_mode_name']);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getEntityDisplay($entity_type_id, $bundle, $mode) {
+ return entity_get_display($entity_type_id, $bundle, $mode);
+ }
+
+ /**
+ * @todo.
+ *
+ * @return array
+ * A render array as expected by the renderer.
+ */
+ public function form(array $form, FormStateInterface $form_state) {
+ $form['filter'] = [
+ '#type' => 'search',
+ '#title' => $this->t('Filter'),
+ '#title_display' => 'invisible',
+ '#size' => 30,
+ '#placeholder' => $this->t('Filter by block name'),
+ '#attributes' => [
+ 'class' => ['block-filter-text'],
+ 'data-element' => '.block-add-table',
+ 'title' => $this->t('Enter a part of the block name to filter by.'),
+ ],
+ ];
+ $form['blocks'] = [
+ '#type' => 'tableselect',
+ '#header' => [
+ 'title' => $this->t('Block'),
+ 'category' => $this->t('Category'),
+ ],
+ '#js_select' => FALSE,
+ '#empty' => $this->t('No blocks available.'),
+ '#attributes' => [
+ 'class' => ['block-add-table'],
+ ],
+ '#element_validate' => [[$this, 'validateTableselect']],
+ ];
+
+ // Only add blocks which work without any available context.
+ $definitions = $this->blockManager->getDefinitionsForContexts($this->contextRepository->getAvailableContexts());
+ // Order by category, and then by admin label.
+ $definitions = $this->blockManager->getSortedDefinitions($definitions);
+
+ foreach ($definitions as $plugin_id => $plugin_definition) {
+ $form['blocks']['#options'][$plugin_id] = [
+ 'title' => [
+ 'data' => [
+ '#type' => 'inline_template',
+ '#template' => '
{{ label }}
',
+ '#context' => [
+ 'label' => $plugin_definition['admin_label'],
+ ],
+ ],
+ ],
+ 'category' => $plugin_definition['category'],
+ ];
+ }
+
+ $form['#attached']['library'][] = 'block/drupal.block.admin';
+
+
+ return $form;
+ }
+
+ /**
+ * @todo.
+ */
+ public function validateTableselect($element, FormStateInterface $form_state) {
+ $values = array_filter(NestedArray::getValue($form_state->getValues(), $element['#parents']));
+ $form_state->setValueForElement($element, $values);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
+ $block_ids = [];
+ foreach (array_keys(array_filter($form_state->getValue('blocks'))) as $block_id) {
+ /** @var \Drupal\Core\Block\BlockPluginInterface $block */
+ $block = $this->blockManager->createInstance($block_id);
+ $suggestion = $block->getMachineNameSuggestion();
+ $count = 1;
+ $machine_default = $suggestion;
+ while (isset($block_ids[$machine_default])) {
+ $machine_default = $suggestion . '_' . ++$count;
+ }
+ $block->setConfigurationValue('label', $block->label());
+ $entity->setBlock($machine_default, [
+ 'id' => $machine_default,
+ 'plugin_id' => $block_id,
+ 'settings' => $block->getConfiguration(),
+ ]);
+ $form_state->unsetValue(['blocks', $block_id]);
+ }
+ $this->entityFieldManager->clearCachedFieldDefinitions();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ parent::submitForm($form, $form_state);
+ $entity_type = $this->entityTypeManager->getDefinition($this->entity->getTargetEntityTypeId());
+ $form_state->setRedirect('entity.entity_view_display.' . $this->entity->getTargetEntityTypeId() . '.view_mode', [
+ 'view_mode_name' => $this->entity->getMode(),
+ ] + FieldUI::getRouteBundleParameter($entity_type, $this->entity->getTargetBundle()));
+ drupal_set_message($this->t('Your settings have been saved.'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function actions(array $form, FormStateInterface $form_state) {
+ $actions = parent::actions($form, $form_state);
+ $actions['submit']['#value'] = $this->t('Add block');
+ return $actions;
+ }
+
+}
diff --git a/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php b/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php
index c27f3d4..303da96 100644
--- a/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php
+++ b/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php
@@ -2,9 +2,16 @@
namespace Drupal\field_ui\Form;
+use Drupal\Component\Plugin\ContextAwarePluginInterface;
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Block\BlockManagerInterface;
+use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Field\PluginSettingsInterface;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Form\SubformState;
use Drupal\Core\Url;
use Drupal\field_ui\FieldUI;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -15,17 +22,47 @@
class EntityViewDisplayEditForm extends EntityDisplayFormBase {
/**
+ * The entity being used by this form.
+ *
+ * @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface
+ */
+ protected $entity;
+
+ /**
* {@inheritdoc}
*/
protected $displayContext = 'view';
/**
+ * The block manager.
+ *
+ * @var \Drupal\Core\Block\BlockManagerInterface
+ */
+ protected $blockManager;
+
+ /**
+ * EntityViewDisplayEditForm constructor.
+ *
+ * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
+ * The field type manager.
+ * @param \Drupal\Component\Plugin\PluginManagerBase $plugin_manager
+ * The formatter plugin manager.
+ * @param \Drupal\Core\Block\BlockManagerInterface $block_manager
+ * The block manager.
+ */
+ public function __construct(FieldTypePluginManagerInterface $field_type_manager, PluginManagerBase $plugin_manager, BlockManagerInterface $block_manager) {
+ parent::__construct($field_type_manager, $plugin_manager);
+ $this->blockManager = $block_manager;
+ }
+
+ /**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.field.field_type'),
- $container->get('plugin.manager.field.formatter')
+ $container->get('plugin.manager.field.formatter'),
+ $container->get('plugin.manager.block')
);
}
@@ -83,6 +120,203 @@ protected function buildExtraFieldRow($field_id, $extra_field) {
/**
* {@inheritdoc}
*/
+ public function form(array $form, FormStateInterface $form_state) {
+ $form = parent::form($form, $form_state);
+
+ $form_state->setTemporaryValue('gathered_contexts', \Drupal::service('context.repository')->getAvailableContexts());
+ foreach ($this->entity->getBlocks() as $block_id => $block_info) {
+ $form['fields'][$block_id] = $this->buildBlockFieldRow($block_id, $block_info, $form, $form_state);
+ }
+
+ $form['add_block'] = [
+ '#weight' => -100,
+ '#type' => 'link',
+ '#title' => $this->t('Add field block'),
+ '#url' => Url::fromRoute('entity.entity_view_display.' . $this->entity->getTargetEntityTypeId() . '.add_block', $this->getRouteBundleParameters($this->entity->getMode())),
+ '#attributes' => [
+ 'class' => ['use-ajax', 'button', 'button--small'],
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => Json::encode([
+ 'width' => 700,
+ ]),
+ ],
+ ];
+ return $form;
+ }
+
+ /**
+ * @todo.
+ */
+ protected function getRouteBundleParameters($mode) {
+ $entity_type = $this->entityTypeManager->getDefinition($this->entity->getTargetEntityTypeId());
+ return ['view_mode_name' => $mode] + FieldUI::getRouteBundleParameter($entity_type, $this->entity->getTargetBundle());
+ }
+
+ /**
+ * @todo.
+ */
+ protected function buildBlockFieldRow($field_id, $extra_field, $form, FormStateInterface $form_state) {
+ $display_options = $this->entity->getBlock($field_id);
+ /** @var \Drupal\Core\Block\BlockPluginInterface $block */
+ $block = $this->blockManager->createInstance($extra_field['plugin_id'], $extra_field['settings']);
+
+ $regions = array_keys($this->getRegions());
+ $label = $block->label();
+ $extra_field_row = [
+ '#attributes' => ['class' => ['draggable', 'tabledrag-leaf']],
+ '#row_type' => 'extra_field',
+ '#region_callback' => [$this, 'getRowRegion'],
+ '#js_settings' => ['rowHandler' => 'field'],
+ 'human_name' => [
+ '#markup' => $label,
+ ],
+ 'weight' => [
+ '#type' => 'textfield',
+ '#title' => $this->t('Weight for @title', ['@title' => $label]),
+ '#title_display' => 'invisible',
+ '#default_value' => $display_options ? $display_options['weight'] : 0,
+ '#size' => 3,
+ '#attributes' => ['class' => ['field-weight']],
+ ],
+ 'parent_wrapper' => [
+ 'parent' => [
+ '#type' => 'select',
+ '#title' => $this->t('Parents for @title', ['@title' => $label]),
+ '#title_display' => 'invisible',
+ '#options' => array_combine($regions, $regions),
+ '#empty_value' => '',
+ '#attributes' => ['class' => ['js-field-parent', 'field-parent']],
+ '#parents' => ['fields', $field_id, 'parent'],
+ ],
+ 'hidden_name' => [
+ '#type' => 'hidden',
+ '#default_value' => $field_id,
+ '#attributes' => ['class' => ['field-name']],
+ ],
+ ],
+ 'region' => [
+ '#type' => 'select',
+ '#title' => $this->t('Region for @title', ['@title' => $label]),
+ '#title_display' => 'invisible',
+ '#options' => $this->getRegionOptions(),
+ '#default_value' => $display_options ? $display_options['region'] : 'visible',
+ '#attributes' => ['class' => ['field-region']],
+ ],
+ 'empty_cell' => [
+ '#markup' => ' ',
+ ],
+ 'plugin' => [
+ 'type' => [
+ '#type' => 'hidden',
+ '#value' => $display_options ? 'visible' : 'hidden',
+ '#parents' => ['fields', $field_id, 'type'],
+ '#attributes' => ['class' => ['field-plugin-type']],
+ ],
+ ],
+ 'settings_summary' => [],
+ 'settings_edit' => [],
+ ];
+
+ // Base button element for the various plugin settings actions.
+ $base_button = [
+ '#submit' => ['::multistepSubmit'],
+ '#ajax' => [
+ 'callback' => '::multistepAjax',
+ 'wrapper' => 'field-display-overview-wrapper',
+ 'effect' => 'fade',
+ ],
+ '#field_name' => $field_id,
+ ];
+
+ if ($form_state->get('plugin_settings_edit') == $field_id) {
+ // Generate the settings form and allow other modules to alter it.
+ $extra_field_row['plugin']['#cell_attributes'] = ['colspan' => 3];
+ $extra_field_row['plugin']['settings_edit_form'] = [
+ '#type' => 'container',
+ '#attributes' => ['class' => ['field-plugin-settings-edit-form']],
+ '#parents' => ['fields', $field_id, 'settings_edit_form'],
+ 'label' => [
+ '#markup' => $this->t('Plugin settings'),
+ ],
+ 'settings' => [],
+ 'actions' => [
+ '#type' => 'actions',
+ 'save_settings' => $base_button + [
+ '#type' => 'submit',
+ '#button_type' => 'primary',
+ '#name' => $field_id . '_plugin_settings_update',
+ '#value' => $this->t('Update'),
+ '#op' => 'update',
+ ],
+ 'cancel_settings' => $base_button + [
+ '#type' => 'submit',
+ '#name' => $field_id . '_plugin_settings_cancel',
+ '#value' => $this->t('Cancel'),
+ '#op' => 'cancel',
+ // Do not check errors for the 'Cancel' button, but make sure we
+ // get the value of the 'plugin type' select.
+ '#limit_validation_errors' => [['fields', $field_id, 'type']],
+ ],
+ ],
+ ];
+ $extra_field_row['#attributes']['class'][] = 'field-plugin-settings-editing';
+ $subform_state = SubformState::createForSubform($extra_field_row['plugin']['settings_edit_form']['settings'], $form, $form_state);
+ $extra_field_row['plugin']['settings_edit_form']['settings'] = $block->buildConfigurationForm($extra_field_row['plugin']['settings_edit_form']['settings'], $subform_state);
+ }
+ else {
+ // Check selected plugin settings to display edit link or not.
+ $extra_field_row['settings_edit'] = $base_button + [
+ '#type' => 'image_button',
+ '#name' => $field_id . '_settings_edit',
+ '#src' => 'core/misc/icons/787878/cog.svg',
+ '#attributes' => ['class' => ['field-plugin-settings-edit'], 'alt' => $this->t('Edit')],
+ '#op' => 'edit',
+ // Do not check errors for the 'Edit' button, but make sure we get
+ // the value of the 'plugin type' select.
+ '#limit_validation_errors' => [['fields', $field_id, 'type']],
+ '#prefix' => '',
+ '#suffix' => '
',
+ ];
+ }
+
+ return $extra_field_row;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
+ /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $entity */
+ parent::copyFormValuesToEntity($entity, $form, $form_state);
+
+ foreach ($entity->getBlocks() as $name => $block_info) {
+ $form_values = $form_state->getValue(['fields', $name]);
+ $options = $entity->getBlock($name);
+ if (!isset($options['id']) || $form_values['region'] == 'hidden') {
+ $entity->removeBlock($name);
+ }
+ else {
+ if ($form_state->get('plugin_settings_update') === $name) {
+ $form_state->set('plugin_settings_update', NULL);
+ $block = $this->blockManager->createInstance($options['plugin_id'], $options['settings']);
+ $sub_form_state = SubformState::createForSubform($form['fields'][$name]['plugin']['settings_edit_form']['settings'], $form, $form_state);
+ $block->submitConfigurationForm($form, $sub_form_state);
+ if ($block instanceof ContextAwarePluginInterface && $block->getContextDefinitions()) {
+ $context_mapping = $sub_form_state->getValue('context_mapping', []);
+ $block->setContextMapping($context_mapping);
+ }
+ $options['settings'] = $block->getConfiguration();
+ }
+ $options['weight'] = $form_values['weight'];
+ $options['region'] = $form_values['region'];
+ $entity->setBlock($name, $options);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
protected function getEntityDisplay($entity_type_id, $bundle, $mode) {
return entity_get_display($entity_type_id, $bundle, $mode);
}
@@ -137,10 +371,7 @@ protected function getTableHeader() {
* {@inheritdoc}
*/
protected function getOverviewUrl($mode) {
- $entity_type = $this->entityManager->getDefinition($this->entity->getTargetEntityTypeId());
- return Url::fromRoute('entity.entity_view_display.' . $this->entity->getTargetEntityTypeId() . '.view_mode', [
- 'view_mode_name' => $mode,
- ] + FieldUI::getRouteBundleParameter($entity_type, $this->entity->getTargetBundle()));
+ return Url::fromRoute('entity.entity_view_display.' . $this->entity->getTargetEntityTypeId() . '.view_mode', $this->getRouteBundleParameters($mode));
}
/**
diff --git a/core/modules/field_ui/src/Routing/RouteSubscriber.php b/core/modules/field_ui/src/Routing/RouteSubscriber.php
index b08e7f8..0f9a66b 100644
--- a/core/modules/field_ui/src/Routing/RouteSubscriber.php
+++ b/core/modules/field_ui/src/Routing/RouteSubscriber.php
@@ -154,6 +154,17 @@ protected function alterRoutes(RouteCollection $collection) {
$options
);
$collection->add("entity.entity_view_display.{$entity_type_id}.view_mode", $route);
+
+ $route = new Route(
+ "$path/display/{view_mode_name}/add-block",
+ [
+ '_entity_form' => 'entity_view_display.add_block',
+ '_title' => 'Add block',
+ ] + $defaults,
+ ['_field_ui_view_mode_access' => 'administer ' . $entity_type_id . ' display'],
+ $options
+ );
+ $collection->add("entity.entity_view_display.{$entity_type_id}.add_block", $route);
}
}
}
diff --git a/core/modules/field_ui/tests/src/FunctionalJavascript/EntityDisplayTest.php b/core/modules/field_ui/tests/src/FunctionalJavascript/EntityDisplayTest.php
index 6da3c41..b6a092b 100644
--- a/core/modules/field_ui/tests/src/FunctionalJavascript/EntityDisplayTest.php
+++ b/core/modules/field_ui/tests/src/FunctionalJavascript/EntityDisplayTest.php
@@ -15,7 +15,12 @@ class EntityDisplayTest extends JavascriptTestBase {
/**
* {@inheritdoc}
*/
- public static $modules = ['field_ui', 'entity_test'];
+ public static $modules = ['field_ui', 'entity_test', 'block_test'];
+
+ /**
+ * @var \Drupal\Core\Session\AccountInterface
+ */
+ protected $user;
/**
* {@inheritdoc}
@@ -30,7 +35,7 @@ protected function setUp() {
]],
]);
$entity->save();
- $this->drupalLogin($this->drupalCreateUser([
+ $this->user = $this->drupalCreateUser([
'access administration pages',
'view test entity',
'administer entity_test content',
@@ -38,7 +43,8 @@ protected function setUp() {
'administer entity_test display',
'administer entity_test form display',
'view the administration theme',
- ]));
+ ]);
+ $this->drupalLogin($this->user);
}
/**
@@ -87,6 +93,41 @@ public function testEntityView() {
}
/**
+ * Tests that block-based extra fields are available.
+ */
+ public function testEntityViewWithBlocks() {
+ $this->drupalGet('entity_test/structure/entity_test/display');
+ $this->clickLink('Add field block');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ // Ensure that blocks with unsatisfiable contexts are not shown.
+ $this->assertSession()->pageTextNotContains('Test context-aware unsatisfied block');
+ // Ensure that context-aware blocks are shown.
+ $this->assertSession()->pageTextContains('Test context-aware block');
+
+ // Set the context-aware to visible, but do not assign a context mapping.
+ $this->getSession()->getPage()->checkField('blocks[test_context_aware]');
+ $this->getSession()->getPage()->find('css', '.ui-dialog-buttonpane .button--primary')->press();
+ $this->assertSession()->pageTextContains('Your settings have been saved.');
+
+ $this->drupalGet('entity_test/1');
+ $this->assertSession()->pageTextContains('No context mapping selected.');
+
+ // Set the context mapping.
+ $this->drupalGet('entity_test/structure/entity_test/display');
+ $this->click('#edit-fields-testcontextawareblock-settings-edit');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $this->getSession()->getPage()->selectFieldOption('fields[testcontextawareblock][settings_edit_form][settings][context_mapping][user]', '@user.current_user_context:current_user');
+ $this->submitForm([], 'Update');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $this->submitForm([], 'Save');
+ $this->assertSession()->pageTextContains('Your settings have been saved.');
+
+ $this->drupalGet('entity_test/1');
+ $this->assertSession()->pageTextNotContains('No context mapping selected');
+ $this->assertSession()->pageTextContains($this->user->getAccountName());
+ }
+
+ /**
* Tests extra fields.
*/
public function testExtraFields() {
diff --git a/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php
index 4ed5e6e..989af05 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php
@@ -11,6 +11,8 @@
use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Context\ContextHandler;
+use Drupal\Core\Plugin\Context\ContextInterface;
+use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\Plugin\DataType\StringData;
@@ -30,12 +32,20 @@ class ContextHandlerTest extends UnitTestCase {
protected $contextHandler;
/**
+ * The context repository.
+ *
+ * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
+ */
+ protected $contextRepository;
+
+ /**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
- $this->contextHandler = new ContextHandler();
+ $this->contextRepository = $this->prophesize(ContextRepositoryInterface::class);
+ $this->contextHandler = new ContextHandler($this->contextRepository->reveal());
}
/**
@@ -487,6 +497,37 @@ public function testApplyContextMappingConfigurableAssignedMiss() {
$this->contextHandler->applyContextMapping($plugin, $contexts, ['miss' => 'name']);
}
+ /**
+ * @covers ::applyRuntimeContext
+ * @covers ::applyContextMapping
+ */
+ public function testApplyRuntimeContext() {
+ $context_data = StringData::createInstance(DataDefinition::create('string'));
+ $context_data->setValue('foo');
+ $context = $this->prophesize(ContextInterface::class);
+ $context->getContextData()->willReturn($context_data);
+ $context->hasContextValue()->willReturn(TRUE);
+
+ $contexts = [
+ 'name' => $context->reveal(),
+ ];
+
+ $context_definition = (new ContextDefinition())->setRequired(FALSE);
+
+ $plugin_context = $this->prophesize(ContextInterface::class);
+ $plugin_context->addCacheableDependency($context)->shouldBeCalled();
+
+ $plugin = $this->prophesize(ContextAwarePluginInterface::class);
+ $plugin->getContextMapping()->willReturn(['hit' => 'name']);
+ $plugin->getContextDefinitions()->willReturn(['hit' => $context_definition]);
+ $plugin->getContext('hit')->willReturn($plugin_context->reveal());
+ $plugin->setContextValue('hit', $context_data)->shouldBeCalled();
+
+ $this->contextRepository->getRuntimeContexts(['name'])->willReturn($contexts)->shouldBeCalled();
+
+ $this->contextHandler->applyRuntimeContext($plugin->reveal());
+ }
+
}
interface TestConfigurableContextAwarePluginInterface extends ContextAwarePluginInterface, ConfigurablePluginInterface {