From 8d555ae156926bb0ab5e473cb45f872e2846e491 Mon Sep 17 00:00:00 2001 From: Mikael Meulle <21535-just_like_good_vibes@users.noreply.drupalcode.org> Date: Fri, 17 Jan 2025 17:07:44 +0000 Subject: [PATCH] Issue #3499625 by just_like_good_vibes, pdureau, dalemoore: New field prop entity source plugin --- src/Element/ComponentElementBuilder.php | 2 +- .../EntityFieldSourceDeriverBase.php | 3 + ...ceFieldPropertyDerivableContextDeriver.php | 35 ++++++++ .../EntityReferencedDerivableContext.php | 83 +++++++++++++++---- .../Source/DerivableContextSourceBase.php | 14 +++- .../EntityReferenceFieldPropertySource.php | 69 +++++++++++++++ .../Source/EntityReferencedSource.php | 12 +++ 7 files changed, 201 insertions(+), 17 deletions(-) create mode 100644 src/Plugin/Derivative/EntityReferenceFieldPropertyDerivableContextDeriver.php create mode 100644 src/Plugin/UiPatterns/Source/EntityReferenceFieldPropertySource.php diff --git a/src/Element/ComponentElementBuilder.php b/src/Element/ComponentElementBuilder.php index 401f52925..7b0d85a46 100644 --- a/src/Element/ComponentElementBuilder.php +++ b/src/Element/ComponentElementBuilder.php @@ -40,7 +40,7 @@ class ComponentElementBuilder implements TrustedCallbackInterface { * Build component data provided to the SDC element. */ public function build(array $element): array { - if (!isset($element['#ui_patterns'])) { + if (!isset($element['#ui_patterns']) || !isset($element['#component'])) { return $element; } $configuration = $element['#ui_patterns']; diff --git a/src/Plugin/Derivative/EntityFieldSourceDeriverBase.php b/src/Plugin/Derivative/EntityFieldSourceDeriverBase.php index fce8bfe0b..3201fc4b8 100644 --- a/src/Plugin/Derivative/EntityFieldSourceDeriverBase.php +++ b/src/Plugin/Derivative/EntityFieldSourceDeriverBase.php @@ -131,6 +131,7 @@ abstract class EntityFieldSourceDeriverBase extends DeriverBase implements Conta continue; } $field_storage_definition = $field_storage_definitions[$field_name]; + $main_property_name = (is_object($field_storage_definition) && method_exists($field_storage_definition, "getMainPropertyName")) ? $field_storage_definition->getMainPropertyName() : NULL; $is_base = (in_array($field_name, $entityFieldsClassification["fields_base"]) || ($field_storage_definition instanceof BaseFieldDefinition)); $returned[$field_name] = [ "label" => $field_storage_definition->getLabel(), @@ -143,6 +144,7 @@ abstract class EntityFieldSourceDeriverBase extends DeriverBase implements Conta "base" => $is_base, "cardinality" => $field_storage_definition->getCardinality(), ], + "main_property" => $main_property_name, 'config_dependencies' => [], 'properties' => [], ]; @@ -158,6 +160,7 @@ abstract class EntityFieldSourceDeriverBase extends DeriverBase implements Conta '@field' => $field_storage_definition->getLabel(), ]), "data_type" => $property_definition->getDataType(), + "entity_reference" => (($main_property_name === $property_id) && ($property_definition instanceof DataReferenceTargetDefinition)), ]; } } diff --git a/src/Plugin/Derivative/EntityReferenceFieldPropertyDerivableContextDeriver.php b/src/Plugin/Derivative/EntityReferenceFieldPropertyDerivableContextDeriver.php new file mode 100644 index 000000000..281aaedce --- /dev/null +++ b/src/Plugin/Derivative/EntityReferenceFieldPropertyDerivableContextDeriver.php @@ -0,0 +1,35 @@ +<?php + +namespace Drupal\ui_patterns\Plugin\Derivative; + +use Drupal\Component\Plugin\PluginBase; + +/** + * Provides derivable context for every field referencing an entity. + */ +class EntityReferenceFieldPropertyDerivableContextDeriver extends EntityFieldSourceDeriverBase { + + /** + * {@inheritdoc} + */ + protected function getDerivativeDefinitionsForEntityStorageFieldProperty(string $entity_type_id, string $field_name, string $property, array $base_plugin_derivative): void { + $id = implode(PluginBase::DERIVATIVE_SEPARATOR, [ + $entity_type_id, + $field_name, + $property, + ]); + if (!$this->entityFieldsMetadata[$entity_type_id]["field_storages"][$field_name]["properties"][$property]['entity_reference']) { + return; + } + // $base_plugin_derivative["context_definitions"]["field_name"] = + $this->derivatives[$id] = array_merge( + $base_plugin_derivative, + [ + "id" => $id, + "label" => $this->t("Field prop: entity"), + "context_requirements" => array_merge($base_plugin_derivative["context_requirements"], ["field_granularity:item"]), + "tags" => array_merge($base_plugin_derivative["tags"], ["xxx"]), + ]); + } + +} diff --git a/src/Plugin/UiPatterns/DerivableContext/EntityReferencedDerivableContext.php b/src/Plugin/UiPatterns/DerivableContext/EntityReferencedDerivableContext.php index 90e4b45cb..9451e7c93 100644 --- a/src/Plugin/UiPatterns/DerivableContext/EntityReferencedDerivableContext.php +++ b/src/Plugin/UiPatterns/DerivableContext/EntityReferencedDerivableContext.php @@ -14,6 +14,7 @@ use Drupal\Core\Plugin\Context\EntityContextDefinition; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\ui_patterns\Attribute\DerivableContext; use Drupal\ui_patterns\DerivableContextPluginBase; +use Drupal\ui_patterns\Plugin\Context\RequirementsContext; use Drupal\ui_patterns\Plugin\Derivative\EntityReferencedDerivableContextDeriver; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -75,36 +76,90 @@ class EntityReferencedDerivableContext extends DerivableContextPluginBase { * {@inheritdoc} */ public function getDerivedContexts(): array { - $contexts = $this->context; - // Base, entity_type, bundle, field name, target_entity_type, target_bundle. - $split_plugin_id = explode(PluginBase::DERIVATIVE_SEPARATOR, $this->getPluginId()); - [$bundle, $entity_type_id, $ref_field_name] = array_slice(array_reverse($split_plugin_id), 0, 3); + $referenced_entities = $this->getEntities(); + $removed_context_keys = ["entity", "ui_patterns:field:", "bundle"]; + $base_context = array_filter($this->context, function ($one_context, $one_context_id) use (&$removed_context_keys) { + if (in_array($one_context_id, $removed_context_keys)) { + return FALSE; + } + foreach ($removed_context_keys as $removed_context_key) { + if (str_starts_with($one_context_id, $removed_context_key)) { + return FALSE; + } + } + return TRUE; + }, ARRAY_FILTER_USE_BOTH); + $base_context = RequirementsContext::removeFromContext(["field_granularity:item"], $base_context); + $metadata = $this->getMetadata(); + $entity_type_id = $metadata["entity_type_id"]; + $bundle = $metadata["bundle"]; // Bundle context definition. $bundle_context_definition = new ContextDefinition("string", "Bundle"); - $contexts['bundle'] = new Context($bundle_context_definition, $bundle); + $base_context['bundle'] = new Context($bundle_context_definition, $bundle); // Entity context definition. - $entity = $this->context["entity"]->getContextValue(); $entity_context_definition = new EntityContextDefinition($entity_type_id); if (!empty($bundle)) { $entity_context_definition->addConstraint('Bundle', [$bundle]); } - // Get referenced entities. - $referenced_entities = $this->getReferencedEntities($entity, $ref_field_name, $bundle); - if ((count($referenced_entities) === 0) && !$entity->id()) { - // Case when the entity is a sample (we are probably in a form) - // we generate a sample referenced entity. - $referenced_entities[] = $this->sampleEntityGenerator->get($entity_type_id, empty($bundle) ? $this->findEntityBundleWithField($entity_type_id, NULL) : $bundle); - } + // Generate the contexts. $returned_contexts = []; foreach ($referenced_entities as $referenced_entity) { - $returned_contexts[] = array_merge($contexts, [ + $returned_contexts[] = array_merge($base_context, [ "entity" => new Context($entity_context_definition, $referenced_entity), ]); } return $returned_contexts; } + /** + * Get metadata about the current derivable context. + * + * @return array + * Metadata about the current derivable context + */ + protected function getMetadata() { + // Base, entity_type, bundle, field name, target_entity_type, target_bundle. + $split_plugin_id = explode(PluginBase::DERIVATIVE_SEPARATOR, $this->getPluginId()); + [$bundle, $entity_type_id, $ref_field_name] = array_slice(array_reverse($split_plugin_id), 0, 3); + // Bundle context definition. + return [ + "entity_type_id" => $entity_type_id, + "bundle" => $bundle, + "field_name" => $ref_field_name, + ]; + } + + /** + * Get entities for this derivable context. + * + * @return array + * The references entities. + */ + protected function getEntities() : array { + $entity = $this->context["entity"]->getContextValue(); + $metadata = $this->getMetadata(); + $entity_type_id = $metadata["entity_type_id"]; + $bundle = $metadata["bundle"]; + // Get referenced entities. + $referenced_entities = $this->getReferencedEntities($entity, $metadata["field_name"], $bundle); + if ((count($referenced_entities) === 0) && !$entity->id()) { + // Case when the entity is a sample (we are probably in a form) + // we generate a sample referenced entity. + $referenced_entities[] = $this->sampleEntityGenerator->get($entity_type_id, empty($bundle) ? $this->findEntityBundleWithField($entity_type_id, NULL) : $bundle); + } + else { + // Check ui_patterns:field:index. + if (isset($this->context["ui_patterns:field:index"])) { + $field_index = $this->context["ui_patterns:field:index"]->getContextValue(); + if (isset($referenced_entities[$field_index])) { + $referenced_entities = [$referenced_entities[$field_index]]; + } + } + } + return $referenced_entities; + } + /** * Get the referenced entities. * diff --git a/src/Plugin/UiPatterns/Source/DerivableContextSourceBase.php b/src/Plugin/UiPatterns/Source/DerivableContextSourceBase.php index a022b171c..e1103c9ef 100644 --- a/src/Plugin/UiPatterns/Source/DerivableContextSourceBase.php +++ b/src/Plugin/UiPatterns/Source/DerivableContextSourceBase.php @@ -104,6 +104,16 @@ abstract class DerivableContextSourceBase extends SourcePluginBase { return empty($returned) ? NULL : $returned; } + /** + * Get the context to pass to the derivable context plugin. + * + * @return array + * The context. + */ + protected function getContextForDerivation(): array { + return $this->context; + } + /** * Set the source plugin according to configuration. * @@ -117,7 +127,7 @@ abstract class DerivableContextSourceBase extends SourcePluginBase { return $this->sourcePlugins; } /** @var \Drupal\ui_patterns\DerivableContextInterface $derivable_context_plugin */ - $derivable_context_plugin = $this->derivableContextManager->createInstance($derivable_context, DerivableContextPluginBase::buildConfiguration($this->context)); + $derivable_context_plugin = $this->derivableContextManager->createInstance($derivable_context, DerivableContextPluginBase::buildConfiguration($this->getContextForDerivation())); if (!$derivable_context_plugin) { return $this->sourcePlugins; } @@ -448,7 +458,7 @@ abstract class DerivableContextSourceBase extends SourcePluginBase { * @return array * Definitions of blocks */ - private function listDerivableContexts() : array { + protected function listDerivableContexts() : array { return $this->derivableContextManager->getDefinitionsMatchingContextsAndTags($this->context, $this->getDerivationTagFilter()); } diff --git a/src/Plugin/UiPatterns/Source/EntityReferenceFieldPropertySource.php b/src/Plugin/UiPatterns/Source/EntityReferenceFieldPropertySource.php new file mode 100644 index 000000000..738ddb18f --- /dev/null +++ b/src/Plugin/UiPatterns/Source/EntityReferenceFieldPropertySource.php @@ -0,0 +1,69 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\ui_patterns\Plugin\UiPatterns\Source; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\Context\ContextDefinition; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\ui_patterns\Attribute\Source; +use Drupal\ui_patterns\Plugin\Derivative\EntityReferenceFieldPropertyDerivableContextDeriver; + +/** + * Plugin implementation of the source. + */ +#[Source( + id: 'entity:field_property', + label: new TranslatableMarkup('Entity reference field property'), + description: new TranslatableMarkup('Entity reference field property source plugin for props.'), + deriver: EntityReferenceFieldPropertyDerivableContextDeriver::class, + context_definitions: [ + 'entity' => new ContextDefinition('entity', label: new TranslatableMarkup('Entity'), required: TRUE), + ] +)] +class EntityReferenceFieldPropertySource extends DerivableContextSourceBase { + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state): array { + $form = parent::settingsForm($form, $form_state); + $form["derivable_context"]["#type"] = "hidden"; + $form["derivable_context"]["#value"] = $form["derivable_context"]["#default_value"]; + return $form; + } + + /** + * {@inheritdoc} + */ + protected function listDerivableContexts() : array { + $derivable_contexts = parent::listDerivableContexts(); + $field_name = $this->context['field_name']->getContextValue(); + return array_filter($derivable_contexts, function ($derivable_context, $derivable_context_id) use ($field_name) { + return isset($derivable_context['metadata']) && is_array($derivable_context['metadata']) + && isset($derivable_context['metadata']['field_name']) && $derivable_context['metadata']['field_name'] === $field_name; + }, ARRAY_FILTER_USE_BOTH); + } + + /** + * {@inheritDoc} + */ + protected function getSourcesTagFilter(): array { + return [ + "widget:dismissible" => FALSE, + "widget" => FALSE, + ]; + } + + /** + * {@inheritDoc} + */ + protected function getDerivationTagFilter(): ?array { + return [ + // "entity" => TRUE, + "entity_referenced" => TRUE, + ]; + } + +} diff --git a/src/Plugin/UiPatterns/Source/EntityReferencedSource.php b/src/Plugin/UiPatterns/Source/EntityReferencedSource.php index d69e4f376..f5ec1a821 100644 --- a/src/Plugin/UiPatterns/Source/EntityReferencedSource.php +++ b/src/Plugin/UiPatterns/Source/EntityReferencedSource.php @@ -31,6 +31,18 @@ class EntityReferencedSource extends DerivableContextSourceBase { return $form; } + /** + * Get the context to pass to the derivable context plugin. + * + * @return array + * The context. + */ + protected function getContextForDerivation(): array { + return array_filter($this->context, function ($key) { + return $key !== 'ui_patterns:field:index'; + }, ARRAY_FILTER_USE_KEY); + } + /** * {@inheritDoc} */ -- GitLab