Skip to content
Snippets Groups Projects
Commit d1975eca authored by christian.wiedemann's avatar christian.wiedemann Committed by Pierre Dureau
Browse files

Issue #3461274 by just_like_good_vibes, Christian.wiedemann, pdureau: No...

Issue #3461274 by just_like_good_vibes, Christian.wiedemann, pdureau: No entity context in layout builder
parent e29dc9e0
No related branches found
Tags 2.0.0-beta1
1 merge request!152Add entity and bundle context to layout builder
Pipeline #250888 passed
Showing
with 1327 additions and 658 deletions
......@@ -4,19 +4,17 @@ declare(strict_types=1);
namespace Drupal\ui_patterns_blocks\Plugin\Block;
use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Component\Utility\Html;
use Drupal\Core\Block\Attribute\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Context\ContextInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\ui_patterns\Entity\SampleEntityGeneratorInterface;
use Drupal\ui_patterns\ContextHelperInterface;
use Drupal\ui_patterns\Form\ComponentFormBuilderTrait;
use Drupal\ui_patterns\SourcePluginBase;
use Drupal\ui_patterns_blocks\Plugin\Derivative\ComponentBlock as DerivativeComponentBlock;
......@@ -44,20 +42,14 @@ class ComponentBlock extends BlockBase implements ContainerFactoryPluginInterfac
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Routing\RouteMatchInterface $routeMatch
* The request stack service.
* @param \Drupal\ui_patterns\Entity\SampleEntityGeneratorInterface $sampleEntityGenerator
* The sample entity generator service.
* @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $contextRepository
* The context repository.
* @param \Drupal\ui_patterns\ContextHelperInterface $contextHelper
* The context helper service.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
protected RouteMatchInterface $routeMatch,
protected SampleEntityGeneratorInterface $sampleEntityGenerator,
protected ContextRepositoryInterface $contextRepository,
protected ContextHelperInterface $contextHelper,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
......@@ -70,9 +62,7 @@ class ComponentBlock extends BlockBase implements ContainerFactoryPluginInterfac
$configuration,
$plugin_id,
$plugin_definition,
$container->get('current_route_match'),
$container->get('ui_patterns.sample_entity_generator'),
$container->get('context.repository')
$container->get('ui_patterns.context_helper'),
);
}
......@@ -137,20 +127,12 @@ class ComponentBlock extends BlockBase implements ContainerFactoryPluginInterfac
* Source contexts.
*/
protected function getComponentSourceContexts(FormStateInterface $form_state = NULL): array {
$availableContexts = $this->contextRepository->getAvailableContexts();
if ($form_state) {
$gatheredContexts = $form_state->getTemporaryValue('gathered_contexts') ?: [];
if (!is_array($gatheredContexts)) {
$gatheredContexts = [];
$gatheredContexts = $form_state ? $form_state->getTemporaryValue('gathered_contexts') : [];
$this->contextHelper->applyContextMapping($this, $gatheredContexts ?: []);
if (!isset($this->context['entity']) || !($this->context['entity']->getContextValue() instanceof EntityInterface)) {
if ($entity = $this->contextHelper->guessEntity($this->context, $form_state)) {
$this->context['entity'] = EntityContext::fromEntity($entity);
}
$gatheredContexts = array_filter($gatheredContexts, fn($context) => $context instanceof ContextInterface);
$availableContexts = array_merge($availableContexts, $gatheredContexts);
}
try {
$this->contextHandler()->applyContextMapping($this, $availableContexts);
}
catch (ContextException $e) {
// Do nothing.
}
if (!$this->context["bundle"] && isset($this->context["entity"])) {
$this->context['bundle'] = new Context(ContextDefinition::create('string'), $this->context["entity"]->getContextValue()->bundle() ?? "");
......
......@@ -44,6 +44,7 @@ class ComponentBlock extends DeriverBase implements ContainerDeriverInterface {
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
/** @var array<string, array> $components */
$components = $this->pluginManager->getSortedDefinitions();
foreach ($components as $component_id => $component) {
$this->derivatives[$component_id] = $base_plugin_definition;
......
......@@ -24,6 +24,7 @@ class EntityComponentBlock extends ComponentBlock {
$entity_context_def->setRequired(TRUE);
$definition['context_definitions']["entity"] = $entity_context_def;
}
unset($definition);
return $this->derivatives;
}
......
......@@ -4,15 +4,12 @@ declare(strict_types=1);
namespace Drupal\ui_patterns_field_formatters\Plugin\Field\FieldFormatter;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\layout_builder\SectionStorageInterface;
use Drupal\ui_patterns\Form\ComponentSettingsFormBuilderTrait;
use Drupal\ui_patterns\Plugin\Context\RequirementsContext;
use Drupal\ui_patterns\SourcePluginBase;
......@@ -31,45 +28,13 @@ abstract class ComponentFormatterBase extends FormatterBase {
* @var \Drupal\ui_patterns\ComponentPluginManager
*/
protected $componentPluginManager;
/**
* The context repository.
*
* @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
*/
protected $contextRepository;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The sample entity generator.
*
* @var \Drupal\ui_patterns\Entity\SampleEntityGeneratorInterface
*/
protected $sampleEntityGenerator;
/**
* Entity Field Manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The route match.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* The module handler.
* The context helper.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
* @var \Drupal\ui_patterns\ContextHelperInterface
*/
protected $moduleHandler;
protected $contextHelper;
/**
* The provided plugin contexts.
......@@ -98,12 +63,7 @@ abstract class ComponentFormatterBase extends FormatterBase {
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->componentPluginManager = $container->get('plugin.manager.sdc');
$instance->contextRepository = $container->get('context.repository');
$instance->entityTypeManager = $container->get('entity_type.manager');
$instance->sampleEntityGenerator = $container->get('ui_patterns.sample_entity_generator');
$instance->entityFieldManager = $container->get('entity_field.manager');
$instance->routeMatch = $container->get('current_route_match');
$instance->moduleHandler = $container->get('module_handler');
$instance->contextHelper = $container->get('ui_patterns.context_helper');
return $instance;
}
......@@ -174,37 +134,6 @@ abstract class ComponentFormatterBase extends FormatterBase {
return $this->componentSettingsForm($form, $form_state, $injected_contexts);
}
/**
* Find an entity bundle which has a field.
*
* @param string $entity_type_id
* The entity type id.
* @param string $field_name
* The field name to be found in searched bundle.
*
* @return string
* The bundle.
*/
protected function findEntityBundleWithField(string $entity_type_id, string $field_name) : string {
// @todo better implementation with service 'entity_type.bundle.info'
$bundle = $entity_type_id;
$bundle_entity_type = $this->entityTypeManager->getDefinition($entity_type_id)->getBundleEntityType();
if (NULL !== $bundle_entity_type) {
$bundle_list = $this->entityTypeManager->getStorage($bundle_entity_type)->loadMultiple();
if (count($bundle_list) > 0) {
foreach ($bundle_list as $bundle_entity) {
$bundle_to_test = (string) $bundle_entity->id();
$definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle_to_test);
if (array_key_exists($field_name, $definitions)) {
$bundle = $bundle_to_test;
break;
}
}
}
}
return $bundle;
}
/**
* Set the context of field and entity (override the method trait).
*
......@@ -218,51 +147,29 @@ abstract class ComponentFormatterBase extends FormatterBase {
// @todo does this really makes sense to propagate the externally injected context
$contexts = array_merge($this->context ?? [], $this->getThirdPartySetting('ui_patterns', 'context') ?? []);
$field_definition = $this->fieldDefinition;
$entity_type_id = $field_definition->getTargetEntityTypeId();
// When field items are available, we can get the entity directly.
$entity = ($items) ? $items->getEntity() : NULL;
if (!$entity && ($layoutBuilderContext = $this->getLayoutBuilderEntityContext()) && $layoutBuilderContext["entity"]) {
$contexts = array_merge($contexts, $layoutBuilderContext["context"]);
$entity = $layoutBuilderContext["entity"];
}
$field_name = $field_definition->getName() ?? "";
$contexts['field_name'] = new Context(ContextDefinition::create('string'), $field_name);
$contexts = RequirementsContext::addToContext(["field_formatter"], $contexts);
$bundle = $field_definition->getTargetBundle();
$contexts['bundle'] = new Context(ContextDefinition::create('string'), $bundle ?? "");
if (!$entity || !$this->isCompatibleEntity($entity, $entity_type_id, $field_name)) {
if (!$bundle) {
// Generate a default bundle when it is missing,
// this covers contexts like the display of a field in a view.
// the bundle selected should have the field in definition...
// @todo better implementation with service 'entity_type.bundle.info'?
$bundle = $this->findEntityBundleWithField($entity_type_id, $field_definition->getName());
}
$entity = $this->sampleEntityGenerator->get($entity_type_id, $bundle);
// Get the entity.
$entity_type_id = $field_definition->getTargetEntityTypeId();
// When field items are available, we can get the entity directly.
$entity = ($items) ? $items->getEntity() : NULL;
if (!$entity) {
$entity = $this->contextHelper->guessEntity($contexts);
}
if (!$entity || !$this->contextHelper->checkEntityHasField($entity, $entity_type_id, $field_name)) {
// Generate a default bundle when it is missing,
// this covers contexts like the display of a field in a view.
// the bundle selected should have the field in definition...
$entity = !empty($bundle) ? $this->contextHelper->getSampleEntity($entity_type_id, $bundle) :
$this->contextHelper->getSampleEntityWithField($entity_type_id, $field_name);
}
$contexts['entity'] = EntityContext::fromEntity($entity);
return $contexts;
}
/**
* Check if the entity is compatible with the field.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
* @param string $entity_type_id
* The entity type id.
* @param string $field_name
* The field name.
*
* @return bool
* TRUE if the entity is compatible.
*/
protected function isCompatibleEntity(EntityInterface $entity, string $entity_type_id, string $field_name) : bool {
$field_definitions = $this->entityFieldManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle());
return ($entity->getEntityTypeId() === $entity_type_id &&
array_key_exists($field_name, $field_definitions));
}
/**
* {@inheritdoc}
*/
......@@ -279,50 +186,4 @@ abstract class ComponentFormatterBase extends FormatterBase {
return $dependencies;
}
/**
* Get the entity context when in layout builder.
*
* @return array
* The entity and context.
*
* @see \Drupal\ui_patterns_field_formatters\Plugin\Field\FieldFormatter\ComponentFormatterBase::getComponentSourceContexts()
*/
protected function getLayoutBuilderEntityContext() : array {
$returned = [
"entity" => NULL,
"context" => [],
];
if (!$this->moduleHandler->moduleExists("layout_builder")) {
return $returned;
}
// Infer the entity context, when in layout builder.
$section_storage = $this->routeMatch->getParameter("section_storage");
if ($section_storage instanceof SectionStorageInterface) {
$section_storage_contexts = $section_storage->getContexts();
$returned["context"] = $section_storage_contexts;
if (array_key_exists("entity", $section_storage_contexts) && ($entity = $section_storage_contexts["entity"]->getContextValue())) {
$returned["entity"] = $entity;
return $returned;
}
// Search for the entity in the context, with another name.
foreach ($section_storage_contexts as $section_storage_context) {
if ($section_storage_context instanceof EntityContext) {
// Now check if it is and entity view display.
$entity_in_context = $section_storage_context->getContextValue();
if ($entity_in_context instanceof EntityViewDisplayInterface) {
$entity_type_id = $entity_in_context->getTargetEntityTypeId();
$bundle = $entity_in_context->getTargetBundle();
$returned["entity"] = $this->sampleEntityGenerator->get($entity_type_id, $bundle);
break;
}
elseif ($entity_in_context instanceof EntityInterface) {
$returned["entity"] = $section_storage_context->getContextValue();
break;
}
}
}
}
return $returned;
}
}
......@@ -155,7 +155,7 @@ class FieldFormatterSource extends FieldValueSourceBase {
'#ajax' => [
'callback' => [__CLASS__, 'onFormatterTypeChange'],
'wrapper' => $uniqueID,
'method' => 'replace',
'method' => 'replaceWith',
],
];
......
......@@ -29,64 +29,69 @@ class SourcesDeriverTest extends UIPatternsFieldFormattersTestBase {
foreach (array_keys($field_types) as $field_type_id) {
$field_name = "field_" . $field_type_id;
// Load the field and check if it was created.
$field = \Drupal::entityTypeManager()
->getStorage('field_config')
->load('node.' . $this->bundle . '.' . $field_name);
$this->assertNotNull($field, "Field of type $field_type_id was created. ");
foreach ($this->entityTypes as $oneEntityType) {
$bundle = $oneEntityType->id();
$entity_type_id = $oneEntityType->getEntityType()->getBundleOf();
// Load the field and check if it was created.
$field = \Drupal::entityTypeManager()
->getStorage('field_config')
->load($entity_type_id . '.' . $bundle . '.' . $field_name);
// Ensure the reusable block content is provided as a derivative block
// plugin.
$this->sourceManager->clearCachedDefinitions();
$definitions = $this->sourceManager->getDefinitions();
$plugin_id = implode(PluginBase::DERIVATIVE_SEPARATOR, [
'field_formatter',
'node',
$this->bundle,
$field_name,
]);
$plugin_id_storage = implode(PluginBase::DERIVATIVE_SEPARATOR, [
'field_formatter',
'node',
"",
$field_name,
]);
$this->assertContains($plugin_id, array_keys($definitions), implode("\n", array_keys($definitions)));
$this->assertContains($plugin_id_storage, array_keys($definitions), implode("\n", array_keys($definitions)));
$this->assertTrue($this->sourceManager->hasDefinition($plugin_id));
$this->assertTrue($this->sourceManager->hasDefinition($plugin_id_storage));
$plugin_bundle = $this->sourceManager->getDefinition($plugin_id);
$plugin_storage = $this->sourceManager->getDefinition($plugin_id_storage);
foreach ([$plugin_bundle, $plugin_storage] as $plugin) {
$this->assertEquals(FieldFormatterSource::class, $plugin['class']);
$this->assertIsArray($plugin['prop_types']);
$prop_types = $plugin['prop_types'];
$this->assertContains('slot', $prop_types);
$this->assertCount(1, $prop_types);
$this->assertIsArray($plugin['context_definitions']);
$context_definitions = $plugin['context_definitions'];
$this->assertArrayHasKey('entity', $context_definitions);
$this->assertArrayHasKey('bundle', $context_definitions);
$this->assertArrayHasKey('field_name', $context_definitions);
$this->assertCount(3, $context_definitions);
$entity_context = $context_definitions['entity'];
$this->assertInstanceOf(EntityContextDefinition::class, $entity_context);
/** @var \Drupal\Core\Plugin\Context\ContextDefinition $bundle_context */
$bundle_context = $context_definitions['bundle'];
$this->assertInstanceOf(ContextDefinition::class, $entity_context);
$constraints = $bundle_context->getConstraints();
$this->assertArrayHasKey('AllowedValues', $constraints);
$this->assertContains(($plugin === $plugin_bundle) ? $this->bundle : "", $constraints['AllowedValues']);
$field_name_context = $context_definitions['field_name'];
$this->assertInstanceOf(ContextDefinition::class, $field_name_context);
$this->assertArrayHasKey('AllowedValues', $field_name_context->getConstraints());
$this->assertContains($field_name, $field_name_context->getConstraints()['AllowedValues']);
$this->assertIsArray($plugin['metadata']);
$metadata = $plugin['metadata'];
$this->assertArrayHasKey('field', $metadata);
$this->assertArrayHasKey('field_name', $metadata);
$this->assertArrayHasKey('field_formatter', $metadata);
$this->assertNotNull($field, "Field of type $field_type_id was created. ");
// Ensure the reusable block content is provided as a derivative block
// plugin.
$this->sourceManager->clearCachedDefinitions();
$definitions = $this->sourceManager->getDefinitions();
$plugin_id = implode(PluginBase::DERIVATIVE_SEPARATOR, [
'field_formatter',
$entity_type_id,
$bundle,
$field_name,
]);
$plugin_id_storage = implode(PluginBase::DERIVATIVE_SEPARATOR, [
'field_formatter',
$entity_type_id,
"",
$field_name,
]);
$this->assertContains($plugin_id, array_keys($definitions), implode("\n", array_keys($definitions)));
$this->assertContains($plugin_id_storage, array_keys($definitions), implode("\n", array_keys($definitions)));
$this->assertTrue($this->sourceManager->hasDefinition($plugin_id));
$this->assertTrue($this->sourceManager->hasDefinition($plugin_id_storage));
$plugin_bundle = $this->sourceManager->getDefinition($plugin_id);
$plugin_storage = $this->sourceManager->getDefinition($plugin_id_storage);
foreach ([$plugin_bundle, $plugin_storage] as $plugin) {
$this->assertEquals(FieldFormatterSource::class, $plugin['class']);
$this->assertIsArray($plugin['prop_types']);
$prop_types = $plugin['prop_types'];
$this->assertContains('slot', $prop_types);
$this->assertCount(1, $prop_types);
$this->assertIsArray($plugin['context_definitions']);
$context_definitions = $plugin['context_definitions'];
$this->assertArrayHasKey('entity', $context_definitions);
$this->assertArrayHasKey('bundle', $context_definitions);
$this->assertArrayHasKey('field_name', $context_definitions);
$this->assertCount(3, $context_definitions);
$entity_context = $context_definitions['entity'];
$this->assertInstanceOf(EntityContextDefinition::class, $entity_context);
/** @var \Drupal\Core\Plugin\Context\ContextDefinition $bundle_context */
$bundle_context = $context_definitions['bundle'];
$this->assertInstanceOf(ContextDefinition::class, $entity_context);
$constraints = $bundle_context->getConstraints();
$this->assertArrayHasKey('AllowedValues', $constraints);
$this->assertContains(($plugin === $plugin_bundle) ? $bundle : "", $constraints['AllowedValues']);
$field_name_context = $context_definitions['field_name'];
$this->assertInstanceOf(ContextDefinition::class, $field_name_context);
$this->assertArrayHasKey('AllowedValues', $field_name_context->getConstraints());
$this->assertContains($field_name, $field_name_context->getConstraints()['AllowedValues']);
$this->assertIsArray($plugin['metadata']);
$metadata = $plugin['metadata'];
$this->assertArrayHasKey('field', $metadata);
$this->assertArrayHasKey('field_name', $metadata);
$this->assertArrayHasKey('field_formatter', $metadata);
}
}
}
}
......
......@@ -2,173 +2,43 @@
namespace Drupal\Tests\ui_patterns_field_formatters\Kernel;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\FieldConfigInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\NodeType;
use org\bovigo\vfs\vfsStreamWrapper;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Tests\ui_patterns\Kernel\EntityTestBase;
/**
* Defines a base class for node access kernel tests.
*/
abstract class UIPatternsFieldFormattersTestBase extends KernelTestBase {
abstract class UIPatternsFieldFormattersTestBase extends EntityTestBase {
/**
* {@inheritdoc}
*/
protected string $defaultTheme = 'stark';
protected static $modules = [];
/**
* {@inheritdoc}
*/
protected static $modules = [
'user',
'system',
'sdc',
'field',
'text',
'datetime',
'filter',
'ui_patterns',
'ui_patterns_field_formatters',
'ui_patterns_test',
'ui_patterns_field_formatters',
'ui_patterns_field_formatters_test',
'node',
];
/**
* The source plugin manager.
*
* @var \Drupal\ui_patterns\SourcePluginManager
*/
protected $sourceManager;
/**
* The field type plugin manager.
*
* @var \Drupal\Core\Field\FieldTypePluginManager
*/
protected $fieldTypeManager;
/**
* Bundle.
*
* @var string
*/
protected string $bundle;
/**
* Entity Type.
*
* @var \Drupal\node\NodeTypeInterface
*/
protected $entityType;
/**
* Fields.
* Plugin id suffixes.
*
* @var array<\Drupal\field\FieldConfigInterface>
* @var array<string, string>
*/
protected $fields;
protected $pluginIdSuffixesBySourceId = [];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
vfsStreamWrapper::register();
self::$modules = array_merge(parent::$modules, ['ui_patterns_field_formatters']);
parent::setUp();
$this->installSchema('user', 'users_data');
$this->installConfig(['system', 'user']);
$this->installEntitySchema('user');
$this->fieldTypeManager = $this->container->get('plugin.manager.field.field_type');
$this->sourceManager = $this->container->get('plugin.manager.ui_patterns_source');
$this->setUpNode();
}
/**
* Set up the node with fields.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
private function setUpNode() : void {
$this->installEntitySchema('node');
$this->installConfig(['field', 'node']);
$this->bundle = $this->randomMachineName();
$type = NodeType::load($this->bundle);
if (!$type) {
$type = NodeType::create([
'name' => $this->randomString(10),
'type' => $this->bundle,
]);
$type->save();
}
$this->entityType = $type;
$field_types = $this->fieldTypeManager->getDefinitions();
foreach (array_keys($field_types) as $field_type_id) {
$field_name = sprintf("field_%s_%s", $field_type_id, 1);
$bundle = (string) $this->entityType->id();
$this->fields[$field_name] =
$this->createEntityField($this->entityType->getEntityType()->getBundleOf(), $bundle, $field_name, $field_type_id, 1);
$field_name = sprintf("field_%s", $field_type_id);
$this->fields[$field_name] =
$this->createEntityField($this->entityType->getEntityType()->getBundleOf(), $bundle, $field_name, $field_type_id,
FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$this->pluginIdSuffixesBySourceId = [];
foreach ($this->entityTypes as $oneEntityType) {
$bundle = $oneEntityType->id();
$entity_type_id = $oneEntityType->getEntityType()->getBundleOf();
$this->pluginIdSuffixesBySourceId["field_formatter"] = $entity_type_id . PluginBase::DERIVATIVE_SEPARATOR .
$bundle;
$this->pluginIdSuffixesBySourceId["field_property"] = $entity_type_id .
PluginBase::DERIVATIVE_SEPARATOR;
}
}
/**
* Create a field on a content type.
*/
protected function createEntityField(
string $entity_type,
string $bundle,
string $field_name,
string $field_type_id,
int $cardinality = FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
) : FieldConfigInterface {
$field_type_definition = $this->fieldTypeManager->getDefinition($field_type_id);
// Create a field storage.
$field_storage = [
'field_name' => $field_name,
'entity_type' => $entity_type,
'type' => $field_type_id,
'settings' => [],
'cardinality' => $cardinality,
];
FieldStorageConfig::create($field_storage)->save();
// Create a field instance on the content type.
$field = [
'field_name' => $field_storage['field_name'],
'entity_type' => $entity_type,
'bundle' => $bundle,
'label' => $field_type_definition['label'],
'settings' => [],
];
$field_config = FieldConfig::create($field);
$field_config->save();
// Set cardinality.
$field_storage_reload = FieldStorageConfig::loadByName($entity_type, $field_name);
$field_storage_reload->setCardinality($cardinality);
$field_storage_reload->save();
return $field_config;
}
/**
* Normalize a string of markup for comparison.
*/
protected function normalizeMarkupString(string $markup): string {
$markup = preg_replace('/\s+/', ' ', $markup);
$markup = preg_replace('/\s*>\s*/', '>', $markup);
$markup = preg_replace('/\s*</', '<', $markup);
$markup = str_replace("\n", "", $markup);
$markup = trim($markup);
return $markup;
}
}
<?php
namespace Drupal\ui_patterns_layouts\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Layout\LayoutDefinition;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\ui_patterns\ComponentPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides layout plugin definitions for components.
*
* @see \Drupal\ui_patterns_layouts\Plugin\Layout\ComponentLayout
*/
class ComponentLayout extends DeriverBase implements ContainerDeriverInterface {
/**
* Constructs new ComponentLayout Deriver.
*
* @param \Drupal\ui_patterns\ComponentPluginManager $pluginManager
* The component plugin manager.
*/
public function __construct(protected ComponentPluginManager $pluginManager) {
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('plugin.manager.sdc')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$components = $this->pluginManager->getSortedDefinitions();
foreach ($components as $component) {
/** @var \Drupal\Core\Layout\LayoutDefinition $base_plugin_definition */
$definition = array_merge([
"deriver" => $base_plugin_definition->getDeriver(),
"class" => $base_plugin_definition->getClass(),
], [
'label' => $component['annotated_name'] ?? $component['name'] ?? $component['id'],
'category' => $component['group'] ?? t('Others'),
'provider' => $component['provider'],
'id' => $component['id'],
'context_definitions' => [
'entity' => new ContextDefinition('entity', 'Entity', FALSE),
],
'admin_label' => $component['annotated_name'] ?? $component['name'] ?? $component['id'],
// "context_mapping" => ["entity" => "layout_builder.entity"],
"regions" => [],
]);
if (isset($component['slots']) && is_array($component['slots']) && count($component['slots']) > 0) {
foreach ($component['slots'] as $slot_id => $slot) {
$definition['regions'][$slot_id] = ['label' => $slot['title']];
}
}
$id = str_replace('-', '_', (string) $component['id']);
$this->derivatives[$id] = new LayoutDefinition($definition);
}
return $this->derivatives;
}
}
......@@ -4,21 +4,73 @@ declare(strict_types=1);
namespace Drupal\ui_patterns_layouts\Plugin\Layout;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Layout\Attribute\Layout;
use Drupal\Core\Layout\LayoutDefault;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\ui_patterns\ContextHelperInterface;
use Drupal\ui_patterns\Form\ComponentFormBuilderTrait;
use Drupal\ui_patterns\SourcePluginBase;
use Drupal\ui_patterns_layouts\Plugin\Derivative\ComponentLayout as DerivativeComponentLayout;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Component Layout.
*
* @package Drupal\ui_patterns_layouts\Plugin\Layout
*/
class ComponentLayout extends LayoutDefault implements PluginFormInterface {
#[Layout(
id: "ui_patterns",
deriver: DerivativeComponentLayout::class
)]
class ComponentLayout extends LayoutDefault implements ContainerFactoryPluginInterface, PluginFormInterface {
use ComponentFormBuilderTrait;
/**
* Constructs a new Component Layout instance.
*
* @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\ui_patterns\ContextHelperInterface $contextHelper
* The context helper.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
protected ContextHelperInterface $contextHelper,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$plugin = new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('ui_patterns.context_helper')
);
return $plugin;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return parent::defaultConfiguration() + $this->getComponentFormDefault();
}
/**
* Returns the region names.
*
......@@ -39,39 +91,53 @@ class ComponentLayout extends LayoutDefault implements PluginFormInterface {
return $this->pluginDefinition->getRegions();
}
/**
* Get the entity context if exists.
*
* @return array
* Source contexts.
*/
protected function getComponentSourceContexts(FormStateInterface $form_state = NULL): array {
$gatheredContexts = $form_state ? $form_state->getTemporaryValue('gathered_contexts') : [];
$this->contextHelper->applyContextMapping($this, $gatheredContexts ?: []);
if (!isset($this->context['entity']) || !($this->context['entity']->getContextValue() instanceof EntityInterface)) {
if ($entity = $this->contextHelper->guessEntity($this->context, $form_state)) {
$this->context['entity'] = EntityContext::fromEntity($entity);
}
}
if (!isset($this->context["bundle"]) && isset($this->context["entity"]) && ($entity = $this->context["entity"]->getContextValue())) {
$this->context['bundle'] = new Context(ContextDefinition::create('string'), $entity->bundle() ?? "");
}
return $this->context;
}
/**
* {@inheritdoc}
*/
public function build(array $regions) {
$build = $this->buildComponentRenderable($this->getPluginDefinition()->id());
$build = $this->buildComponentRenderable($this->getPluginDefinition()->id(), $this->getComponentSourceContexts());
$build['#layout'] = $this;
$regions = parent::build($regions);
foreach ($this->getPluginDefinition()->getRegionNames() as $region_name) {
if (array_key_exists($region_name, $regions)) {
$build['#slots'][$region_name] = $regions[$region_name];
// For layout builder we need to pass slots also
// direct to the build array. They are needed to generate contextual
// links and other surrounding markup.
$build[$region_name] = $regions[$region_name];
if (!isset($regions[$region_name])) {
continue;
}
$build['#slots'][$region_name] = $regions[$region_name];
// For layout builder we need to pass slots also
// direct to the build array. They are needed to generate contextual
// links and other surrounding markup.
$build[$region_name] = $regions[$region_name];
}
return $build;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return parent::defaultConfiguration() + $this->getComponentFormDefault();
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$contexts = $this->getComponentSourceContexts($form_state);
$form = parent::buildConfigurationForm($form, $form_state);
$component_id = $this->getPluginDefinition()->id();
return $form + $this->buildComponentsForm($form_state, [], $component_id, FALSE, TRUE);
return $form + $this->buildComponentsForm($form_state, $contexts, $this->getPluginDefinition()->id(), FALSE, TRUE);
}
/**
......@@ -97,7 +163,7 @@ class ComponentLayout extends LayoutDefault implements PluginFormInterface {
// Why no getComponentSourceContexts?
$component_dependencies = $this->calculateComponentDependencies($component_id);
SourcePluginBase::mergeConfigDependencies($dependencies, $component_dependencies);
SourcePluginBase::mergeConfigDependencies($dependencies, ["module" => ["ui_patterns_layout"]]);
SourcePluginBase::mergeConfigDependencies($dependencies, ["module" => ["ui_patterns_layouts"]]);
return $dependencies;
}
......
<?php
namespace Drupal\Tests\ui_patterns_layouts\Kernel;
/**
* Render test with field formatter.
*
* @group ui_patterns_layouts
*/
class RenderFieldFormatterTest extends UIPatternsLayoutsTestBase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
static::$additionalModules = ['ui_patterns_field_formatters'];
parent::setUp();
}
/**
* Test render.
*
* @param string $field_name
* Field name.
* @param array $field_items
* Field items.
* @param array $display_component_settings
* Display component settings.
* @param array $third_party_settings
* Third party settings.
* @param string $expected_output
* Expected output.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*
* @dataProvider providerFieldMapping
*/
public function testRender(
string $field_name,
array $field_items,
array $display_component_settings,
array $third_party_settings,
string $expected_output,
) : void {
$this->doTestRender($field_name, $field_items, $display_component_settings, $third_party_settings, $expected_output);
}
/**
* Test render.
*
* @param string $entity_type_id
* Entity type id.
* @param string $bundle
* Bundle.
* @param string $field_name
* Field name.
* @param array $field_items
* Field items.
* @param array $display_component_settings
* Display component settings.
* @param array $third_party_settings
* Third party settings.
* @param string $expected_output
* Expected output.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*
* @dataProvider providerFieldMappingBundle
*/
public function testRenderBundle(
string $entity_type_id,
string $bundle,
string $field_name,
array $field_items,
array $display_component_settings,
array $third_party_settings,
string $expected_output,
) : void {
$this->doTestRenderBundle($entity_type_id, $bundle, $field_name, $field_items, $display_component_settings, $third_party_settings, $expected_output);
}
/**
* Callback data provider to test one slot, one prop.
*
* @return array[]
* Data provider for function testRender.
*/
public function providerFieldMapping(): array {
$field_type = 'text';
$field_item = "Hello";
$field_name = sprintf('field_%s', $field_type);
$test_field_layout = [];
$third_party_settings = [
"field_layout" => [
"id" => "ui_patterns:ui_patterns_tests_components:slot_test",
"settings" => [
"label" => "alert layout",
"ui_patterns" => [
"component_id" => NULL,
"variant_id" => NULL,
"slots" => [],
"props" => [],
],
],
],
];
$test_field_layout[] = [
$field_name,
[$field_name => [$field_item]],
/*'display_component_settings' => */[
"type" => "ui_patterns_component_per_item",
"settings" => [
"ui_patterns" => [
"component_id" => "ui_patterns_tests_components:slot_test",
"variant_id" => NULL,
"props" => [],
"slots" => [
"my_slot" => [
"sources" => [
0 => [
"source_id" => "field_property:node:field_text:processed",
"_weight" => "0",
],
],
],
],
],
],
"label" => "hidden",
"third_party_settings" => [],
"weight" => "0",
"region" => "my_slot",
],
$third_party_settings,
/*"expected" => */<<<EXPECTED
<div data-component-id="ui_patterns_tests_components:slot_test" class="test-class"><div><div>
<div data-component-id="ui_patterns_tests_components:slot_test" class="test-class">
<p>$field_item</p>
</div>
</div></div></div>
EXPECTED
];
return $test_field_layout;
}
/**
* Callback data provider to test one slot, one prop.
*
* @return array[]
* Data provider for function testRender.
*/
public function providerFieldMappingBundle(): array {
$entity_type_id = "node";
$bundle = self::$defaultNodeBundle;
$field_type = 'text';
$field_item = "Hello";
$field_name = sprintf('field_%s', $field_type);
$test_field_layout = [];
$third_party_settings = [
"field_layout" => [
"id" => "ui_patterns:ui_patterns_tests_components:slot_test",
"settings" => [
"label" => "alert layout",
"ui_patterns" => [
"component_id" => NULL,
"variant_id" => NULL,
"slots" => [],
"props" => [],
],
],
],
];
$test_field_layout[] = [
$entity_type_id,
$bundle,
$field_name,
[$field_name => [$field_item]],
/*'display_component_settings' => */[
"type" => "ui_patterns_component_per_item",
"settings" => [
"ui_patterns" => [
"component_id" => "ui_patterns_tests_components:slot_test",
"variant_id" => NULL,
"props" => [],
"slots" => [
"my_slot" => [
"sources" => [
0 => [
"source_id" => "field_formatter:$entity_type_id:$bundle:field_text",
"source" => [
"type" => "text_default",
],
"_weight" => "0",
],
],
],
],
],
],
"label" => "hidden",
"third_party_settings" => [],
"weight" => "0",
"region" => "my_slot",
],
$third_party_settings,
/*"expected" => */<<<EXPECTED
<div data-component-id="ui_patterns_tests_components:slot_test" class="test-class"><div><div>
<div data-component-id="ui_patterns_tests_components:slot_test" class="test-class">
<p>$field_item</p>
</div>
</div></div></div>
EXPECTED
];
return $test_field_layout;
}
}
<?php
namespace Drupal\Tests\ui_patterns_layouts\Kernel;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\layout_builder\Section;
/**
* Render test with field formatter.
*
* @group ui_patterns_layouts
*/
class RenderLayoutBuilderTest extends UIPatternsLayoutsTestBase {
/**
* Uuid service.
*
* @var \Drupal\Component\Uuid\UuidInterface
*/
protected UuidInterface $uuidService;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
static::$additionalModules = ['ui_patterns_field_formatters', 'layout_builder'];
parent::setUp();
$this->uuidService = $this->container->get('uuid');
}
/**
* Test render.
*
* @param string $entity_type_id
* Entity type id.
* @param string $bundle
* Bundle.
* @param string $field_name
* Field name.
* @param array $field_items
* Field items.
* @param array|null $display_component_settings
* Display component settings.
* @param array $third_party_settings
* Third party settings.
* @param string $expected_output
* Expected output.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*
* @dataProvider providerFieldMappingBundle
*/
public function testRenderBundle(
string $entity_type_id,
string $bundle,
string $field_name,
array $field_items,
?array $display_component_settings,
array $third_party_settings,
string $expected_output,
) : void {
$this->doTestRenderBundle($entity_type_id, $bundle, $field_name, $field_items, $display_component_settings, $third_party_settings, $expected_output);
}
/**
* Callback data provider to test one slot, one prop.
*
* @return array[]
* Data provider for function testRender.
*/
public function providerFieldMappingBundle(): array {
$entity_type_id = "node";
$bundle = self::$defaultNodeBundle;
$field_type = 'text';
$field_item = "Hello";
$field_name = sprintf('field_%s', $field_type);
$test_field_layout = [];
$layoutBuilderSection = new Section(
"ui_patterns:ui_patterns_tests_components:slot_test",
[
"label" => "alert layout",
"context_mapping" => [
"entity" => "layout_builder.entity",
],
"ui_patterns" => [
"component_id" => NULL,
"variant_id" => NULL,
"slots" => [],
"props" => [],
],
],
[],
[]
);
// Section component will be added automatically with field config.
$third_party_settings = [
"field_layout" => [
"id" => "ui_patterns:ui_patterns_tests_components:slot_test",
"settings" => [
"label" => "alert layout",
"ui_patterns" => [
"component_id" => NULL,
"variant_id" => NULL,
"slots" => [],
"props" => [],
],
],
],
"layout_builder" => [
"enabled" => TRUE,
"sections" => [$layoutBuilderSection],
],
];
$test_field_layout[] = [
$entity_type_id,
$bundle,
$field_name,
[$field_name => [$field_item]],
/*'display_component_settings' => */[
"type" => "ui_patterns_component_per_item",
"label" => "hidden",
"settings" => [
"ui_patterns" => [
"component_id" => "ui_patterns_tests_components:slot_test",
"variant_id" => NULL,
"props" => [],
"slots" => [
"my_slot" => [
"sources" => [
0 => [
"source_id" => "field_property:node:field_text:processed",
"_weight" => "0",
],
],
],
],
],
],
],
$third_party_settings,
/*"expected" => */<<<EXPECTED
<div data-component-id="ui_patterns_tests_components:slot_test" class="test-class"><div><div>
<div data-component-id="ui_patterns_tests_components:slot_test" class="test-class">
<p>$field_item</p>
</div>
</div></div></div>
EXPECTED
];
$test_field_layout[] = [
$entity_type_id,
$bundle,
$field_name,
[$field_name => [$field_item]],
/*'display_component_settings' => */[
"type" => "ui_patterns_component_per_item",
"label" => "hidden",
"settings" => [
"ui_patterns" => [
"component_id" => "ui_patterns_tests_components:slot_test",
"variant_id" => NULL,
"props" => [],
"slots" => [
"my_slot" => [
"sources" => [
0 => [
"source_id" => "field_formatter:$entity_type_id:$bundle:field_text",
"source" => [
"type" => "text_default",
],
"_weight" => "0",
],
],
],
],
],
],
],
$third_party_settings,
/*"expected" => */<<<EXPECTED
<div data-component-id="ui_patterns_tests_components:slot_test" class="test-class"><div><div>
<div data-component-id="ui_patterns_tests_components:slot_test" class="test-class">
<p>$field_item</p>
</div>
</div></div></div>
EXPECTED
];
return $test_field_layout;
}
}
<?php
namespace Drupal\Tests\ui_patterns_layouts\Kernel;
/**
* Render test.
*
* @group ui_patterns_layouts
*/
class RenderTest extends UIPatternsLayoutsTestBase {
/**
* Test render.
*
* @param string $field_name
* Field name.
* @param array $field_items
* Field items.
* @param array $display_component_settings
* Display component settings.
* @param array $third_party_settings
* Third party settings.
* @param string $expected_output
* Expected output.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*
* @dataProvider providerFieldMapping
*/
public function testRender(
string $field_name,
array $field_items,
array $display_component_settings,
array $third_party_settings,
string $expected_output,
) : void {
$this->doTestRender($field_name, $field_items, $display_component_settings, $third_party_settings, $expected_output);
}
/**
* Callback data provider to test one slot, one prop.
*
* @return array[]
* Data provider for function testRender.
*/
public function providerFieldMapping(): array {
$field_type = 'text';
$field_item = "Hello";
$field_name = sprintf('field_%s', $field_type);
$test_field_layout = [];
$third_party_settings = [
"field_layout" => [
"id" => "ui_patterns:ui_patterns_tests_components:slot_test",
"settings" => [
"label" => "alert layout",
"ui_patterns" => [
"component_id" => NULL,
"variant_id" => NULL,
"slots" => [],
"props" => [],
],
],
],
];
$test_field_layout[] = [
$field_name,
[$field_name => [$field_item]],
/*'display_component_settings' => */[
"type" => "text_default",
"label" => "hidden",
"settings" => [],
"third_party_settings" => [],
"weight" => "0",
"region" => "my_slot",
],
$third_party_settings,
'<div data-component-id="ui_patterns_tests_components:slot_test" class="test-class"><div><div><p>' . $field_item . '</p></div></div></div>',
];
return $test_field_layout;
}
}
<?php
namespace Drupal\Tests\ui_patterns_layouts\Kernel;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\Display\EntityDisplayInterface;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Render\Markup;
use Drupal\field_layout\Display\EntityDisplayWithLayoutInterface;
use Drupal\Tests\ui_patterns\Kernel\EntityTestBase;
use Symfony\Component\Yaml\Yaml;
/**
* Defines a base class for node access kernel tests.
*/
abstract class UIPatternsLayoutsTestBase extends EntityTestBase {
/**
* Additional modules to activate (child classes should set this).
*
* @var array
*/
protected static array $additionalModules = [];
/**
* {@inheritdoc}
*/
protected $strictConfigSchema = FALSE;
/**
* {@inheritdoc}
*/
protected static $modules = [];
/**
* Config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* Test render.
*
* @param string $field_name
* Field name.
* @param array $field_items
* Field items.
* @param array|null $display_component_settings
* Display component settings.
* @param array $third_party_settings
* Third party settings.
* @param string $expected_output
* Expected output.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
public function doTestRender(
string $field_name,
array $field_items,
?array $display_component_settings,
array $third_party_settings,
string $expected_output,
) : void {
foreach ($this->entityTypes as $oneEntityType) {
$bundle = (string) $oneEntityType->id();
$entity_type_id = $oneEntityType->getEntityType()->getBundleOf();
$this->doTestRenderBundle($entity_type_id, $bundle, $field_name, $field_items, $display_component_settings, $third_party_settings, $expected_output);
}
}
/**
* Test render.
*
* @param string $entity_type_id
* Entity type id.
* @param string $bundle
* Bundle.
* @param string $field_name
* Field name.
* @param array $field_items
* Field items.
* @param array|null $display_component_settings
* Display component settings.
* @param array $third_party_settings
* Third party settings.
* @param string $expected_output
* Expected output.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
public function doTestRenderBundle(
string $entity_type_id,
string $bundle,
string $field_name,
array $field_items,
?array $display_component_settings,
array $third_party_settings,
string $expected_output,
) : void {
$this->setUpFieldDisplay($entity_type_id, (string) $bundle, $field_name, $display_component_settings, $third_party_settings);
$node = $this->drupalCreateNode(
array_merge([
'type' => $bundle,
], $field_items)
);
foreach ($field_items as $field_items_field_name => $field_items_per_field_name) {
$node->set($field_items_field_name, $field_items_per_field_name);
}
$produced = $this->renderEntity($node);
$produced = $this->normalizeMarkupString($produced);
$expected_output = $this->normalizeMarkupString($expected_output);
$config = $this->configFactory->get(sprintf("core.entity_view_display.%s.%s.default", $entity_type_id, $bundle));
$config = Yaml::dump($config->get(), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
$error_string = new FormattableMarkup("failed to verify produced markup for entity render.\nOutput is:\n---\n@output\n---\nExpected is:\n---\n@expected\n---\nFormatter settings is:\n @settings.\n @third_party_settings.\n @config.", [
'@output' => Markup::create($produced),
'@expected' => Markup::create($expected_output),
'@settings' => Markup::create(print_r($display_component_settings, TRUE)),
'@third_party_settings' => Markup::create(print_r($third_party_settings, TRUE)),
'@config' => Markup::create(print_r($config, TRUE)),
]);
$this->assertEquals($expected_output, $produced, $error_string);
}
/**
* {@inheritdoc}
*/
protected function setUp(): void {
self::$modules = array_merge(parent::$modules, ['field_layout', 'layout_discovery', 'ui_patterns_layouts'], static::$additionalModules);
parent::setUp();
$this->installConfig('field_layout');
$entity_save = function (EntityDisplayInterface $entity) {
if ($entity instanceof EntityDisplayWithLayoutInterface) {
$entity->ensureLayout()->save();
}
};
array_map($entity_save, EntityViewDisplay::loadMultiple());
array_map($entity_save, EntityFormDisplay::loadMultiple());
// Invalidate the render cache since all content will now have a layout.
Cache::invalidateTags(['rendered']);
$this->configFactory = $this->container->get('config.factory');
}
}
......@@ -7,47 +7,12 @@
declare(strict_types=1);
use Drupal\Core\Layout\LayoutDefinition;
/**
* Implements hook_layout_alter().
*/
function ui_patterns_layouts_layout_alter(array &$definitions) : void {
$plugin_manager = \Drupal::service('plugin.manager.sdc');
/** @var array<string, mixed>[] $components */
$components = $plugin_manager->getSortedDefinitions();
foreach ($components as $component) {
$definition = [
'label' => $component['annotated_name'] ?? $component['name'] ?? $component['id'],
'category' => $component['group'] ?? t('Others'),
'provider' => $component['provider'],
'class' => '\Drupal\ui_patterns_layouts\Plugin\Layout\ComponentLayout',
'id' => $component['id'],
];
if (isset($component['slots'])) {
foreach ($component['slots'] as $slot_id => $slot) {
$definition['regions'][$slot_id]['label'] = $slot['title'];
}
}
$layout = new LayoutDefinition($definition);
if (isset($component['icon_map'])) {
$layout->setIconMap($component['icon_map']);
}
if (isset($component['icon_path'])) {
$layout->setIconPath($component['icon_path']);
}
$definitions[str_replace('-', '_', (string) $component['id'])] = $layout;
}
}
/**
* Implements hook_element_info_alter().
*/
function ui_patterns_layouts_element_info_alter(array &$types) :void {
if (isset($types['component'])) {
array_unshift($types['component']['#pre_render'], [
"Drupal\ui_patterns_layouts\Element\ComponentAlterer",
"processLayoutBuilderRegions",
]);
array_unshift($types['component']['#pre_render'],
["Drupal\ui_patterns_layouts\Element\ComponentAlterer", "processLayoutBuilderRegions"]);
}
}
<?php
declare(strict_types=1);
namespace Drupal\ui_patterns;
use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
use Drupal\Core\Plugin\Context\ContextInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\field_layout\Entity\FieldLayoutEntityFormDisplay;
use Drupal\field_layout\Entity\FieldLayoutEntityViewDisplay;
use Drupal\field_layout\Form\FieldLayoutEntityFormDisplayEditForm;
use Drupal\field_layout\Form\FieldLayoutEntityViewDisplayEditForm;
use Drupal\layout_builder\DefaultsSectionStorageInterface;
use Drupal\layout_builder\Form\ConfigureSectionForm;
use Drupal\layout_builder\OverridesSectionStorageInterface;
use Drupal\layout_builder\SectionStorageInterface;
use Drupal\ui_patterns\Entity\SampleEntityGeneratorInterface;
/**
* Context helper used by drupal plugins to get the context.
*/
class ContextHelper implements ContextHelperInterface {
public function __construct(
protected SampleEntityGeneratorInterface $sampleEntityGenerator,
protected ContextRepositoryInterface $contextRepository,
protected ContextHandlerInterface $contextHandler,
protected ModuleHandlerInterface $moduleHandler,
protected RouteMatchInterface $routeMatch,
protected EntityTypeManagerInterface $entityTypeManager,
protected EntityFieldManagerInterface $entityFieldManager,
) {
}
/**
* {@inheritdoc}
*/
public function guessEntity(array $contexts = [], FormStateInterface $form_state = NULL): ?EntityInterface {
if ($this->moduleHandler->moduleExists("layout_builder")) {
// Try to get the $section storage.
$section_storage = $this->getLayoutBuilderSectionStorage($form_state);
if ($section_storage && ($entity = $this->guessLayoutBuilderEntity($section_storage))) {
return $entity;
}
}
if ($this->moduleHandler->moduleExists("field_layout")) {
if ($entity = $this->guessFieldLayoutEntity($form_state)) {
return $entity;
}
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function getSampleEntity(string $entity_type_id, string $bundle) : EntityInterface {
return $this->sampleEntityGenerator->get($entity_type_id, $bundle);
}
/**
* {@inheritdoc}
*/
public function getSampleEntityWithField(string $entity_type_id, string $field_name) : EntityInterface {
return $this->getSampleEntity($entity_type_id, $this->findEntityBundleWithField($entity_type_id, $field_name));
}
/**
* {@inheritdoc}
*/
public function checkEntityHasField(EntityInterface $entity, string $entity_type_id, string $field_name) : bool {
$field_definitions = $this->entityFieldManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle());
return ($entity->getEntityTypeId() === $entity_type_id &&
array_key_exists($field_name, $field_definitions));
}
/**
* Find an entity bundle which has a field.
*
* @param string $entity_type_id
* The entity type id.
* @param string $field_name
* The field name to be found in searched bundle.
*
* @return string
* The bundle.
*/
public function findEntityBundleWithField(string $entity_type_id, string $field_name) : string {
// @todo better implementation with service 'entity_type.bundle.info'
$bundle = $entity_type_id;
$bundle_entity_type = $this->entityTypeManager->getDefinition($entity_type_id)->getBundleEntityType();
if (NULL !== $bundle_entity_type) {
$bundle_list = $this->entityTypeManager->getStorage($bundle_entity_type)->loadMultiple();
if (count($bundle_list) > 0) {
foreach ($bundle_list as $bundle_entity) {
$bundle_to_test = (string) $bundle_entity->id();
$definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle_to_test);
if (array_key_exists($field_name, $definitions)) {
$bundle = $bundle_to_test;
break;
}
}
}
}
return $bundle;
}
/**
* {@inheritdoc}
*/
public function applyContextMapping(ContextAwarePluginInterface $plugin, array $gathered_contexts = []) : void {
$available_contexts = $this->contextRepository->getAvailableContexts();
if (!is_array($gathered_contexts)) {
$gathered_contexts = [];
}
if (!empty($gathered_contexts)) {
$gathered_contexts = array_filter($gathered_contexts, fn($context) => $context instanceof ContextInterface);
$available_contexts = array_merge($available_contexts, $gathered_contexts);
}
try {
$this->contextHandler->applyContextMapping($plugin, $available_contexts);
}
catch (ContextException $e) {
// Do nothing.
}
}
/**
* Gets the layout builder section storage.
*
* @param \Drupal\Core\Form\FormStateInterface|null $form_state
* Optional Form state.
*
* @return \Drupal\layout_builder\SectionStorageInterface|null
* The section storage or null.
*/
protected function getLayoutBuilderSectionStorage(FormStateInterface $form_state = NULL) : ?SectionStorageInterface {
if ($form_state !== NULL) {
$form_object = $form_state->getFormObject();
if ($form_object instanceof ConfigureSectionForm) {
return $form_object->getSectionStorage();
}
}
$section_storage = $this->routeMatch->getParameter("section_storage");
if ($section_storage instanceof SectionStorageInterface) {
return $section_storage;
}
return NULL;
}
/**
* Gets entity information from sections storage.
*
* @param \Drupal\layout_builder\SectionStorageInterface $section_storage
* The section to look up info on.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The entity if found or null.
*/
protected function guessLayoutBuilderEntity(SectionStorageInterface $section_storage) : ?EntityInterface {
if ($entity = $this->guessLayoutBuilderEntityFromContexts($section_storage->getContexts())) {
return $entity;
}
$storage_id = $section_storage->getStorageId();
if ($section_storage instanceof DefaultsSectionStorageInterface) {
$display = $this->entityTypeManager->getStorage('entity_view_display')->load($storage_id);
if ($display instanceof EntityViewDisplayInterface) {
return $this->sampleEntityGenerator->get($display->getTargetEntityTypeId(), $display->getTargetBundle());
}
}
elseif ($section_storage instanceof OverridesSectionStorageInterface) {
[$entity_type_id, $id] = explode('.', $storage_id);
if ($entity = $this->entityTypeManager->getStorage($entity_type_id)->load($id)) {
return $entity;
}
}
return NULL;
}
/**
* Gets entity information from sections storage.
*
* @param array<mixed> $section_storage_contexts
* Contexts.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The entity if found or null.
*/
protected function guessLayoutBuilderEntityFromContexts(array $section_storage_contexts) : ?EntityInterface {
if (array_key_exists("entity", $section_storage_contexts) && ($entity = $section_storage_contexts["entity"]->getContextValue())) {
return $entity;
}
// Search for the entity in the context (no matter the name).
foreach ($section_storage_contexts as $section_storage_context) {
if ($section_storage_context instanceof EntityContext) {
// Now check if it is an entity view display.
$entity_in_context = $section_storage_context->getContextValue();
if ($entity_in_context instanceof EntityViewDisplayInterface) {
return $this->sampleEntityGenerator->get($entity_in_context->getTargetEntityTypeId(), $entity_in_context->getTargetBundle());
}
}
}
return NULL;
}
/**
* Guess the entity from the field layout form.
*
* @param \Drupal\Core\Form\FormStateInterface|null $form_state
* Optional Form state.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The entity if found or null.
*/
protected function guessFieldLayoutEntity(?FormStateInterface $form_state = NULL) : ?EntityInterface {
if ($form_state !== NULL) {
$form_object = $form_state->getFormObject();
if (($form_object instanceof FieldLayoutEntityViewDisplayEditForm) ||
($form_object instanceof FieldLayoutEntityFormDisplayEditForm)) {
$entity = $form_object->getEntity();
if (($entity instanceof FieldLayoutEntityViewDisplay)|| ($entity instanceof FieldLayoutEntityFormDisplay)) {
return $this->getSampleEntity($entity->getTargetEntityTypeId(), $entity->getTargetBundle());
}
}
}
return NULL;
}
}
<?php
declare(strict_types=1);
namespace Drupal\ui_patterns;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
/**
* Interface for Context helper used by drupal plugins.
*/
interface ContextHelperInterface {
/**
* Get an entity from contexts.
*
* @param array<mixed> $contexts
* Known contexts.
* @param \Drupal\Core\Form\FormStateInterface|null $form_state
* Optional Form state.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* Entity or null.
*/
public function guessEntity(array $contexts = [], FormStateInterface $form_state = NULL): ?EntityInterface;
/**
* Check if the entity is compatible with the field.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
* @param string $entity_type_id
* The entity type id.
* @param string $field_name
* The field name.
*
* @return bool
* TRUE if the entity is compatible.
*/
public function checkEntityHasField(EntityInterface $entity, string $entity_type_id, string $field_name) : bool;
/**
* Return an entity of the desired type.
*
* @param string $entity_type_id
* The entity type id.
* @param string $bundle
* The bundle.
*
* @return \Drupal\Core\Entity\EntityInterface
* The entity.
*/
public function getSampleEntity(string $entity_type_id, string $bundle) : EntityInterface;
/**
* Get an entity with the desired field.
*
* @param string $entity_type_id
* The entity type id.
* @param string $field_name
* The field name.
*
* @return \Drupal\Core\Entity\EntityInterface
* The entity with the field.
*/
public function getSampleEntityWithField(string $entity_type_id, string $field_name) : EntityInterface;
/**
* Apply context mapping to a plugin.
*
* @param \Drupal\Core\Plugin\ContextAwarePluginInterface $plugin
* The plugin to apply context mapping to.
* @param array $gathered_contexts
* Contexts to use in addition to contexts from contextRepository.
*/
public function applyContextMapping(ContextAwarePluginInterface $plugin, array $gathered_contexts = []) : void;
}
......@@ -147,7 +147,7 @@ class BlockSource extends SourcePluginBase {
'#ajax' => [
'callback' => [__CLASS__, 'onBlockPluginIdChange'],
'wrapper' => $wrapper_id,
'method' => 'replace',
'method' => 'replaceWith',
// 'callback' => [static::class, 'onBlockPluginIdChange'],
],
'#executes_submit_callback' => FALSE,
......
......@@ -173,7 +173,7 @@ abstract class DerivableContextSourceBase extends SourcePluginBase {
'#ajax' => [
'callback' => [__CLASS__, 'onDerivableContextChange'],
'wrapper' => $wrapper_id,
'method' => 'replace',
'method' => 'replaceWith',
],
'#executes_submit_callback' => FALSE,
'#empty_value' => NULL,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment