diff --git a/modules/ui_patterns_blocks/src/Plugin/Block/ComponentBlock.php b/modules/ui_patterns_blocks/src/Plugin/Block/ComponentBlock.php index 7c7b0fcb80f01b78cce88fa8b9ccc2474e7d264f..7baf60309a980cc0d7805743019552d3ebc0933b 100644 --- a/modules/ui_patterns_blocks/src/Plugin/Block/ComponentBlock.php +++ b/modules/ui_patterns_blocks/src/Plugin/Block/ComponentBlock.php @@ -10,6 +10,8 @@ use Drupal\Core\Block\Attribute\Block; use Drupal\Core\Block\BlockBase; 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; @@ -150,6 +152,9 @@ class ComponentBlock extends BlockBase implements ContainerFactoryPluginInterfac 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() ?? ""); + } return $this->context; } diff --git a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentFormatter.php b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentFormatter.php index 86c46351adb63085ebd39fcfa9a952c6eb42ddb8..69896717c516caa54c96b8ba56c725063d0d4f71 100644 --- a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentFormatter.php +++ b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentFormatter.php @@ -7,6 +7,8 @@ namespace Drupal\ui_patterns_field_formatters\Plugin\Field\FieldFormatter; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\Plugin\Context\Context; +use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\ui_patterns\Plugin\Context\RequirementsContext; /** @@ -26,13 +28,16 @@ class ComponentFormatter extends ComponentFormatterBase { * {@inheritdoc} */ public function viewElements(FieldItemListInterface $items, $langcode) { - $this->setComponentSettingsExtra([ - 'lang_code' => $langcode, - 'items' => $items, - ]); - $build = []; - $build[] = $this->buildComponentRenderable($this->getComponentConfiguration()['component_id'], $this->getComponentSourceContexts($items)); - return $build; + $context = $this->getComponentSourceContexts($items); + $context['ui_patterns:lang_code'] = new Context(new ContextDefinition('any'), $langcode); + $context['ui_patterns:field:items'] = new Context(new ContextDefinition('any'), $items); + // If context 'ui_patterns:field:index' exists + // it will be kept. + if (isset($context['ui_patterns:field:index'])) { + unset($context['ui_patterns:field:index']); + } + // If not wrapped into an array, it won't be rendered as expected. + return [$this->buildComponentRenderable($this->getComponentConfiguration()['component_id'], $context)]; } /** diff --git a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentFormatterBase.php b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentFormatterBase.php index 1375952ee11ea691f565a66fe1dc7a056c23fab9..6a49cde76a98feda55758dbfae0fadf28124f40d 100644 --- a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentFormatterBase.php +++ b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentFormatterBase.php @@ -216,7 +216,7 @@ abstract class ComponentFormatterBase extends FormatterBase { */ protected function getComponentSourceContexts(?FieldItemListInterface $items = NULL): array { // @todo does this really makes sense to propagate the externally injected context - $contexts = $this->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. @@ -225,87 +225,42 @@ abstract class ComponentFormatterBase extends FormatterBase { $contexts = array_merge($contexts, $layoutBuilderContext["context"]); $entity = $layoutBuilderContext["entity"]; } - $contexts['field_name'] = new Context(ContextDefinition::create('string'), $field_definition->getName()); + $field_name = $field_definition->getName() ?? ""; + $contexts['field_name'] = new Context(ContextDefinition::create('string'), $field_name); $contexts = RequirementsContext::addToContext(["field_formatter"], $contexts); - $bundle = ($entity) ? $entity->bundle() : $field_definition->getTargetBundle(); - if (NULL === $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()); - } - $contexts['bundle'] = new Context(ContextDefinition::create('string'), $bundle); - if (!$entity) { + $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); } $contexts['entity'] = EntityContext::fromEntity($entity); - if (!$this->checkContextSanity($contexts, $entity_type_id, $bundle)) { - throw new \LogicException("The entity context is not properly set."); - } return $contexts; } /** - * Check the sanity of the context. + * Check if the entity is compatible with the field. * - * @param array<string, \Drupal\Core\Plugin\Context\ContextInterface> $contexts - * The contexts. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity. * @param string $entity_type_id * The entity type id. - * @param string $bundle - * The bundle. + * @param string $field_name + * The field name. * * @return bool - * TRUE if the context is sane. + * TRUE if the entity is compatible. */ - protected function checkContextSanity(array $contexts, string $entity_type_id, string $bundle) : bool { - if (!isset($contexts['entity']) || !($contexts['entity'] instanceof EntityContext)) { - return FALSE; - } - if ((NULL === $contexts['entity']->getContextValue()) || - !($contexts['entity']->getContextValue() instanceof EntityInterface) || - ($contexts['entity']->getContextValue()->getEntityTypeId() !== $entity_type_id) || - ($contexts['entity']->getContextValue()->bundle() !== $bundle) - ) { - return FALSE; - } - return TRUE; - } - - /** - * Store extra data in component configuration for each prop and slot source. - * - * @param array $specific_config - * Array of specific configuration to insert. - */ - protected function setComponentSettingsExtra(array $specific_config): void { - $configuration = $this->getComponentConfiguration(); - $somethingDone = FALSE; - // Loop on each ui patterns props and slots. - if (array_key_exists('props', $configuration)) { - foreach ($configuration['props'] as &$prop_configuration) { - $prop_configuration['source']['extra'] = $specific_config; - $somethingDone = TRUE; - } - unset($prop_configuration); - } - if (array_key_exists('slots', $configuration)) { - foreach ($configuration['slots'] as &$slot) { - if (empty($slot['sources']) || !is_array($slot['sources'])) { - continue; - } - foreach ($slot['sources'] as &$source_configuration) { - $source_configuration['source']['extra'] = $specific_config; - $somethingDone = TRUE; - } - unset($source_configuration); - } - unset($slot); - } - if ($somethingDone) { - $this->setComponentConfiguration($configuration); - } + 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)); } /** diff --git a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentPerItemFormatter.php b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentPerItemFormatter.php index 6e58561a0a87f928a38f57f56d32b3b6154f7058..27f6211129cbd73ca42113f242ee454189b7565a 100644 --- a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentPerItemFormatter.php +++ b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentPerItemFormatter.php @@ -5,6 +5,8 @@ declare(strict_types=1); namespace Drupal\ui_patterns_field_formatters\Plugin\Field\FieldFormatter; use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Plugin\Context\Context; +use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\ui_patterns\Plugin\Context\RequirementsContext; /** @@ -25,16 +27,12 @@ class ComponentPerItemFormatter extends ComponentFormatterBase { */ public function viewElements(FieldItemListInterface $items, $langcode) { $build = []; - $injected_context = $this->getComponentSourceContexts($items); + $context = $this->getComponentSourceContexts($items); + $context['ui_patterns:lang_code'] = new Context(new ContextDefinition('any'), $langcode); + $context['ui_patterns:field:items'] = new Context(new ContextDefinition('any'), $items); for ($field_item_index = 0; $field_item_index < $items->count(); $field_item_index++) { - // Set the field index and lang code for the component, - // we keep the items untouched. - $this->setComponentSettingsExtra([ - 'field_index' => $field_item_index, - 'lang_code' => $langcode, - 'items' => $items, - ]); - $build[] = $this->buildComponentRenderable($this->getComponentConfiguration()['component_id'], $injected_context); + $build[] = $this->buildComponentRenderable($this->getComponentConfiguration()['component_id'], + array_merge($context, ['ui_patterns:field:index' => new Context(new ContextDefinition('integer'), $field_item_index)])); } return $build; diff --git a/modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldFormatterFormTrait.php b/modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldFormatterFormTrait.php index fa984c1c6cd9a32a0407a274f237988a36c209fc..08f5bf4c4a9881572b073a8ed6c29057b205975f 100644 --- a/modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldFormatterFormTrait.php +++ b/modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldFormatterFormTrait.php @@ -58,7 +58,9 @@ trait FieldFormatterFormTrait { array_pop($subformKeys); $subformKeys[] = 'settings'; // Return the subform: - return NestedArray::getValue($form, $subformKeys); + $subform = NestedArray::getValue($form, $subformKeys); + // $form_state->setRebuild(TRUE); + return $subform ?? []; } return []; } diff --git a/modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldFormatterSource.php b/modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldFormatterSource.php index 0d3ab6e7c2ce71074b6ca0d21d1a773d2e8f8692..5bbcf5a7e61f1546dbd9b93faeeb7960d7f37e2b 100644 --- a/modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldFormatterSource.php +++ b/modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldFormatterSource.php @@ -6,8 +6,8 @@ namespace Drupal\ui_patterns_field_formatters\Plugin\UiPatterns\Source; use Drupal\Component\Plugin\Definition\PluginDefinitionInterface; use Drupal\Component\Utility\Html; -use Drupal\Component\Utility\NestedArray; use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldItemInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Field\FieldTypePluginManagerInterface; @@ -17,6 +17,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\SubformState; use Drupal\Core\Logger\LoggerChannelTrait; use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\Core\TypedData\Exception\MissingDataException; use Drupal\ui_patterns\Attribute\Source; use Drupal\ui_patterns\Plugin\UiPatterns\Source\FieldValueSourceBase; use Drupal\ui_patterns\SourcePluginBase; @@ -294,35 +295,6 @@ class FieldFormatterSource extends FieldValueSourceBase { return $options; } - /** - * Ajax callback for fields with AJAX callback to update form substructure. - * - * @param array $form - * The form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The form state. - * - * @return array - * The replaced form substructure. - */ - public static function onFormatterTypeChange(array $form, FormStateInterface $form_state): array { - $triggeringElement = $form_state->getTriggeringElement(); - // Dynamically return the dependent ajax for elements based on the - // triggering element. This shouldn't be done statically because - // settings forms may be different, e.g. for layout builder, core, ... - if (!empty($triggeringElement['#array_parents'])) { - $subformKeys = $triggeringElement['#array_parents']; - // Remove the triggering element itself and add the 'settings' below key. - array_pop($subformKeys); - $subformKeys[] = 'settings'; - // Return the subform: - $subform = NestedArray::getValue($form, $subformKeys); - $form_state->setRebuild(TRUE); - return $subform; - } - return []; - } - /** * Render field item(s) with the field formatter. * @@ -341,26 +313,35 @@ class FieldFormatterSource extends FieldValueSourceBase { // No formatter has been configured. return $returned; } - for ($delta = 0; $delta < $items->count(); $delta++) { - /** @var \Drupal\Core\Field\FieldItemInterface $item */ - $item = $items->get($delta); - if ($field_delta !== NULL) { - if ($delta !== $field_delta) { + // We use third_party_settings to propagate context to the formatter. + // Only our formatter will know how to use it. + $formatter_config = [ + 'type' => $configuration['settings']['type'], + 'settings' => $configuration['settings']['settings'] ?? [], + 'third_party_settings' => [ + 'ui_patterns' => [ + 'context' => $this->context, + ], + ], + ]; + if ($field_delta === NULL) { + $rendered_field = $items->view($formatter_config); + for ($field_index = 0; $field_index < $items->count(); $field_index++) { + if (!isset($rendered_field[$field_index])) { continue; } - - $returned[] = $item->view([ - 'type' => $configuration['settings']['type'], - 'settings' => $configuration['settings']['settings'] ?? [], - ]); - break; + $returned[] = $rendered_field[$field_index]; } - $returned[] = $item->view([ - 'type' => $configuration['settings']['type'], - 'settings' => $configuration['settings']['settings'] ?? [], - ]); + return $returned; + } + try { + /** @var \Drupal\Core\Field\FieldItemInterface $item */ + $item = $items->get($field_delta); + return ($item instanceof FieldItemInterface) ? [$item->view($formatter_config)] : []; + } + catch (MissingDataException) { + return []; } - return $returned; } /** @@ -371,7 +352,7 @@ class FieldFormatterSource extends FieldValueSourceBase { if (!$items instanceof FieldItemListInterface) { return []; } - $field_index = $this->getConfiguredSettingsExtra('field_index') ?? NULL; + $field_index = (isset($this->context['ui_patterns:field:index'])) ? $this->getContextValue('ui_patterns:field:index') : NULL; return $this->viewFieldItems($items, $field_index); } diff --git a/modules/ui_patterns_field_formatters/tests/src/Kernel/RenderTest.php b/modules/ui_patterns_field_formatters/tests/src/Kernel/RenderTest.php index 1d39a0c0775507139c18ae0f1ffd70d4959d3a99..0d47b910569854872d26687472678557f54a64fa 100644 --- a/modules/ui_patterns_field_formatters/tests/src/Kernel/RenderTest.php +++ b/modules/ui_patterns_field_formatters/tests/src/Kernel/RenderTest.php @@ -203,13 +203,13 @@ class RenderTest extends UIPatternsFieldFormattersTestBase { // Create node page with body. $ui_patterns_settings = $this->setUpFieldDisplay($field_name, $is_slot, $type, $component_id, $key_map, $source_settings, $field_property); $node = $this->drupalCreateNode( - [ + array_merge([ 'type' => $this->entityType->id(), - $field_name => $field_items, - ] + ], $field_items) ); - $node->set($field_name, $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); + } $entity_display = $this->displayRepository->getViewDisplay($this->entityType->getEntityType() ->getBundleOf(), (string) $this->entityType->id()); $build = $entity_display->build($node); @@ -311,9 +311,10 @@ class RenderTest extends UIPatternsFieldFormattersTestBase { foreach ($one_data['field_items'] as $field_item) { $casted_field_value = "" . $field_item; $field_value = '<div data-component-id="ui_patterns_field_formatters_test:multi_prop_type_test" class="prop-' . $prop_type . '">' . $casted_field_value . '</div>'; + $field_name = sprintf('field_%s', $one_data['field_type']); $tests_per_prop_type[] = [ - 'field_name' => sprintf('field_%s', $one_data['field_type']), - 'field_items' => [$field_item], + 'field_name' => $field_name, + 'field_items' => [$field_name => [$field_item]], 'is_slot' => FALSE, 'type' => 'ui_patterns_component_per_item', 'component_id' => 'ui_patterns_field_formatters_test:multi_prop_type_test', @@ -324,12 +325,12 @@ class RenderTest extends UIPatternsFieldFormattersTestBase { ]; } } - // Add tests with a body field. - $tests = array_merge($tests_per_prop_type, [ + // Add tests with a body field and field formatters. + $tests_formatters = [ // ui_patterns_component_per_item / multiple items. [ 'field_name' => 'field_text_with_summary', - 'field_items' => ['this is first body', 'this is second body'], + 'field_items' => ['field_text_with_summary' => ['this is first body', 'this is second body']], 'is_slot' => TRUE, 'type' => 'ui_patterns_component_per_item', 'component_id' => 'ui_patterns_field_formatters_test:slot_test', @@ -354,7 +355,7 @@ class RenderTest extends UIPatternsFieldFormattersTestBase { // Test 2. [ 'field_name' => 'field_text_with_summary', - 'field_items' => ['this is first body', 'this is second body'], + 'field_items' => ['field_text_with_summary' => ['this is first body2', 'this is second body2']], 'is_slot' => TRUE, 'type' => 'ui_patterns_component', 'component_id' => 'ui_patterns_field_formatters_test:slot_test', @@ -363,8 +364,8 @@ class RenderTest extends UIPatternsFieldFormattersTestBase { 'field_property' => '', 'expected' => <<<EXPECTED <div> - <div><div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"><p>this is first body</p> -<p>this is second body</p> + <div><div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"><p>this is first body2</p> +<p>this is second body2</p> </div></div> </div> EXPECTED, @@ -372,7 +373,7 @@ EXPECTED, // Test 4. [ 'field_name' => 'field_text_with_summary', - 'field_items' => ['this is first body', 'this is second body'], + 'field_items' => ['field_text_with_summary' => ['this is first body3', 'this is second body3']], 'is_slot' => FALSE, 'type' => 'ui_patterns_component_per_item', 'component_id' => 'ui_patterns_field_formatters_test:string_prop_type_test', @@ -381,15 +382,15 @@ EXPECTED, 'field_property' => 'value', 'expected' => <<<EXPECTED <div> - <div><div data-component-id="ui_patterns_field_formatters_test:string_prop_type_test" class="prop-test">this is first body</div></div> - <div><div data-component-id="ui_patterns_field_formatters_test:string_prop_type_test" class="prop-test">this is second body</div></div> + <div><div data-component-id="ui_patterns_field_formatters_test:string_prop_type_test" class="prop-test">this is first body3</div></div> + <div><div data-component-id="ui_patterns_field_formatters_test:string_prop_type_test" class="prop-test">this is second body3</div></div> </div> EXPECTED, ], // Edge case, this should not be possible in theory. [ 'field_name' => 'field_text_with_summary', - 'field_items' => ['this is first body', 'this is second body'], + 'field_items' => ['field_text_with_summary' => ['this is first body4', 'this is second body4']], 'is_slot' => FALSE, 'type' => 'ui_patterns_component', 'component_id' => 'ui_patterns_field_formatters_test:string_prop_type_test', @@ -402,8 +403,250 @@ EXPECTED, </div> EXPECTED, ], - ]); - return $tests; + ]; + + $tests_cardinality = [ + [ + 'field_name' => 'field_text_1', + 'field_items' => ['field_text_1' => ['single value']], + 'is_slot' => TRUE, + 'type' => 'ui_patterns_component_per_item', + 'component_id' => 'ui_patterns_field_formatters_test:slot_test', + 'key_map' => 'my_slot', + 'source_settings' => ["type" => "text_default"], + 'field_property' => '', + 'expected' => <<<EXPECTED + <div> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + <p>single value</p> + </div> + </div> + EXPECTED, + ], + // This should not work, so default formatter is used... + [ + 'field_name' => 'field_text_1', + 'field_items' => ['field_text_1' => ['single value']], + 'is_slot' => TRUE, + 'type' => 'ui_patterns_component', + 'component_id' => 'ui_patterns_field_formatters_test:slot_test', + 'key_map' => 'my_slot', + 'source_settings' => ["type" => "text_default"], + 'field_property' => '', + 'expected' => <<<EXPECTED + <div><p>single value</p></div> + EXPECTED, + ], + ]; + + $tests_nesting = [ + // Field granularity propagation to ui_patterns_component formatter. + [ + 'field_name' => 'field_text', + 'field_items' => ['field_text' => ['1', '2', '3']], + 'is_slot' => TRUE, + 'type' => 'ui_patterns_component_per_item', + 'component_id' => 'ui_patterns_field_formatters_test:slot_test', + 'key_map' => 'my_slot', + 'source_settings' => [ + "type" => "ui_patterns_component", + "settings" => [ + "ui_patterns" => [ + "component_id" => "ui_patterns_field_formatters_test:slot_test", + "variant_id" => NULL, + "props" => [], + "slots" => [ + "my_slot" => [ + "sources" => [ + [ + "source" => [ + "type" => "text_default", + ], + "source_id" => "field_property:node:field_text:value", + ], + ], + ], + ], + ], + ], + ], + 'field_property' => '', + 'expected' => <<<EXPECTED + <div> + <div> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + 1 + </div> + </div> + </div> + <div> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + 2 + </div> + </div> + </div> + <div> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + 3 + </div> + </div> + </div> + </div> + EXPECTED, + ], + [ + 'field_name' => 'field_text', + 'field_items' => ['field_text' => ['1', '2', '3']], + 'is_slot' => TRUE, + 'type' => 'ui_patterns_component_per_item', + 'component_id' => 'ui_patterns_field_formatters_test:slot_test', + 'key_map' => 'my_slot', + 'source_settings' => [ + "type" => "ui_patterns_component_per_item", + "settings" => [ + "ui_patterns" => [ + "component_id" => "ui_patterns_field_formatters_test:slot_test", + "variant_id" => NULL, + "props" => [], + "slots" => [ + "my_slot" => [ + "sources" => [ + [ + "source" => [ + "type" => "text_default", + ], + "source_id" => "field_property:node:field_text:value", + ], + ], + ], + ], + ], + ], + ], + 'field_property' => '', + 'expected' => <<<EXPECTED + <div> + <div> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + 1 + </div> + </div> + </div> + <div> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + 2 + </div> + </div> + </div> + <div> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + 3 + </div> + </div> + </div> + </div> + EXPECTED, + ], + [ + 'field_name' => 'field_text', + 'field_items' => ['field_text' => ['1', '2', '3']], + 'is_slot' => TRUE, + 'type' => 'ui_patterns_component', + 'component_id' => 'ui_patterns_field_formatters_test:slot_test', + 'key_map' => 'my_slot', + 'source_settings' => [ + "type" => "ui_patterns_component_per_item", + "settings" => [ + "ui_patterns" => [ + "component_id" => "ui_patterns_field_formatters_test:slot_test", + "variant_id" => NULL, + "props" => [], + "slots" => [ + "my_slot" => [ + "sources" => [ + [ + "source" => [ + "type" => "text_default", + ], + "source_id" => "field_property:node:field_text:value", + ], + ], + ], + ], + ], + ], + ], + 'field_property' => '', + 'expected' => <<<EXPECTED + <div> + <div> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + 1 + </div> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + 2 + </div> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + 3 + </div> + </div> + </div> + </div> + EXPECTED, + ], + // field_property can't work here. + [ + 'field_name' => 'field_text', + 'field_items' => ['field_text' => ['1', '2', '3']], + 'is_slot' => TRUE, + 'type' => 'ui_patterns_component', + 'component_id' => 'ui_patterns_field_formatters_test:slot_test', + 'key_map' => 'my_slot', + 'source_settings' => [ + "type" => "ui_patterns_component", + "settings" => [ + "ui_patterns" => [ + "component_id" => "ui_patterns_field_formatters_test:slot_test", + "variant_id" => NULL, + "props" => [], + "slots" => [ + "my_slot" => [ + "sources" => [ + [ + "source" => [ + "type" => "text_default", + ], + "source_id" => "field_property:node:field_text:value", + ], + ], + ], + ], + ], + ], + ], + 'field_property' => '', + 'expected' => <<<EXPECTED + <div> + <div> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + <div data-component-id="ui_patterns_field_formatters_test:slot_test" class="test-class"> + + </div> + </div> + </div> + </div> + EXPECTED, + ], + ]; + + return array_merge($tests_nesting, $tests_cardinality, $tests_per_prop_type, $tests_formatters); } } diff --git a/modules/ui_patterns_field_formatters/tests/src/Kernel/SourcesDeriverTest.php b/modules/ui_patterns_field_formatters/tests/src/Kernel/SourcesDeriverTest.php index 9728fc0cc37c98dd9c33776f6a684dc2c74799c1..2e49a999f104b61e50ee7c0b35ea6868ba37e049 100644 --- a/modules/ui_patterns_field_formatters/tests/src/Kernel/SourcesDeriverTest.php +++ b/modules/ui_patterns_field_formatters/tests/src/Kernel/SourcesDeriverTest.php @@ -77,7 +77,7 @@ class SourcesDeriverTest extends UIPatternsFieldFormattersTestBase { $this->assertInstanceOf(ContextDefinition::class, $entity_context); $constraints = $bundle_context->getConstraints(); $this->assertArrayHasKey('AllowedValues', $constraints); - $this->assertContains(($plugin == $plugin_bundle) ? $this->bundle : "", $constraints['AllowedValues']); + $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()); diff --git a/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewFieldSource.php b/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewFieldSource.php index a7b423d29a1caf31c5239239a42c70f9021d0404..306a019ea2ab6db3e3969b2fad8372418aab0a25 100644 --- a/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewFieldSource.php +++ b/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewFieldSource.php @@ -4,12 +4,10 @@ declare(strict_types=1); namespace Drupal\ui_patterns_views\Plugin\UiPatterns\Source; -use Drupal\Component\Utility\NestedArray; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\Context\EntityContextDefinition; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\ui_patterns\Attribute\Source; -use Drupal\views\Plugin\views\field\FieldPluginBase; use Drupal\views\ViewExecutable; /** @@ -33,77 +31,38 @@ class ViewFieldSource extends ViewsSourceBase { */ public function getPropValue(): mixed { // Get field name inside the configuration. - $parents = ['settings', 'ui_patterns_views_field']; - $configuration = $this->getConfiguration(); - $field_name = NestedArray::getValue($configuration, $parents); - $view = $this->getViewExecutable($this->getContextValue("ui_patterns_views:row:view")); + $field_name = $this->getSetting('ui_patterns_views_field') ?? ""; + $view = $this->getView(); + $options = $this->getViewsFieldOptions($view); // Get row index inside the configuration. - $row_index = $this->getContextValue("ui_patterns_views:row:index") ?? 0; - if (empty($field_name) || empty($view) || (empty($row_index) && $row_index !== 0) || !$view instanceof ViewExecutable) { + $row_index = isset($this->context["ui_patterns_views:row:index"]) ? $this->getContextValue("ui_patterns_views:row:index") : 0; + if (empty($field_name) || !$view || !($view instanceof ViewExecutable) || !is_array($options) || !array_key_exists($field_name, $options)) { return ['#markup' => '']; } // Get the output of the field. $field_output = $view->style_plugin->getField($row_index, $field_name); // Remove field if need it. - if ($this->removeEmptyField($field_name, $field_output, $view)) { - return ['#markup' => '']; + if ($this->isViewFieldHidden($field_name, $field_output, $view)) { + $field_output = NULL; } return $this->renderOutput($field_output); } - /** - * Test if field must be deleted from render. - * - * @param string $field_name - * The field name. - * @param mixed $field_output - * The render of field. - * @param \Drupal\views\ViewExecutable $view - * The current view. - * - * @return bool - * Return TRUE if the field need to be removed from render. - */ - private function removeEmptyField(string $field_name, mixed $field_output, ViewExecutable $view): bool { - if (empty($field_name)) { - return FALSE; - } - if (empty($view->field) || !is_array($view->field) || empty($view->field[$field_name]) || !($view->field[$field_name] instanceof FieldPluginBase)) { - return TRUE; - } - $field = $view->field[$field_name]; - // Exclude field. - if ($field->options['exclude']) { - return TRUE; - } - $options = $this->getContextValue("ui_patterns_views:row:options") ?? []; - $empty_value = $field->isValueEmpty($field_output, $field->options['empty_zero']); - // Remove field if empty. - return $empty_value && ($field->options['hide_empty'] == TRUE || $options['hide_empty'] == TRUE); - } - /** * {@inheritdoc} */ public function settingsForm(array $form, FormStateInterface $form_state): array { $form = parent::settingsForm($form, $form_state); - $view = $this->getViewExecutable($this->getContextValue('entity')); - $current_display = $this->getContextValue('display'); - $options = []; - if ($view) { - $display = $view->getDisplay(); - if ($display->getPluginId() == $current_display && is_array($display->options) && is_array($display->options['fields'])) { - foreach ($display->options['fields'] as $field_id => $field) { - $options[$field_id] = sprintf("%s (%s)", !empty($field['label']) ? $field['label'] : $field['field'], $field_id); - } - } + $options = $this->getViewsFieldOptions($this->getView()); + if (!is_array($options) || count($options) < 1) { + return $form; } $form['ui_patterns_views_field'] = [ '#type' => 'select', '#title' => $this->t('Field'), - '#description' => $this->t('Select field to insert in this slot.'), + '#description' => $this->t('Select view field to insert in this slot.'), '#options' => $options, - '#default_value' => $this->getSetting('ui_patterns_views_field') ?? [], + '#default_value' => $this->getSetting('ui_patterns_views_field') ?? "", '#required' => TRUE, ]; return $form; diff --git a/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewRowsSource.php b/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewRowsSource.php index 001496032ff57ff87389bd7317e7ac87226ace36..e890bba6492e9bdfa3261dfd847c2eb40f6c8266 100644 --- a/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewRowsSource.php +++ b/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewRowsSource.php @@ -4,9 +4,11 @@ declare(strict_types=1); namespace Drupal\ui_patterns_views\Plugin\UiPatterns\Source; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\Context\EntityContextDefinition; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\ui_patterns\Attribute\Source; +use Drupal\views\ViewExecutable; /** * Plugin implementation of the source_provider. @@ -27,8 +29,71 @@ class ViewRowsSource extends ViewsSourceBase { * {@inheritdoc} */ public function getPropValue(): mixed { - $rows = $this->getContextValue("ui_patterns_views:style:rows"); - return $this->renderOutput($rows ?? []); + $view = $this->getView(); + $rows = $this->getContextValue("ui_patterns_views:rows"); + if (!$view || !is_array($rows) || count($rows) < 1) { + return []; + } + // If a field is selected, render only that field in rows. + $field_name = $this->getSetting('ui_patterns_views_field') ?? ""; + $field_options = $this->getViewsFieldOptions($view); + if ($field_name && isset($field_options[$field_name])) { + return $this->renderRowsWithField($view, $rows, $field_name); + } + // Return the view rows. + return $this->renderOutput($rows); + + } + + /** + * Render rows with a specific field. + * + * @param \Drupal\views\ViewExecutable $view + * The view. + * @param array $rows + * The rows. + * @param string $field_name + * The field name. + * + * @return array + * The rendered rows. + * + * @throws \Drupal\Component\Plugin\Exception\ContextException + */ + protected function renderRowsWithField(ViewExecutable $view, array $rows, string $field_name): array { + $returned = []; + $view_style_plugin = $view->style_plugin; + if ($view_style_plugin) { + foreach ($rows as $row_index => $row) { + $index = isset($row["#row"], $row["#row"]->index) ? $row["#row"]->index : $row_index; + $field_output = $view_style_plugin->getField($index, $field_name); + if ($this->isViewFieldHidden($field_name, $field_output, $view)) { + $field_output = NULL; + } + $returned[] = $this->renderOutput($field_output ?? []); + } + } + return $returned; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state): array { + $form = parent::settingsForm($form, $form_state); + $field_options = $this->getViewsFieldOptions($this->getView()); + if (is_array($field_options)) { + $form['ui_patterns_views_field'] = [ + '#type' => 'select', + '#title' => $this->t('Fields rendered in rows'), + '#description' => $this->t('Render only this field in the rows.'), + '#options' => $field_options, + '#default_value' => $this->getSetting('ui_patterns_views_field') ?? "", + '#required' => FALSE, + '#empty_option' => $this->t('All'), + ]; + } + return $form; } } diff --git a/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewsSourceBase.php b/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewsSourceBase.php index 9b82d0ac4853474ca3c2c1cd630f600ecf1a5774..245f118444a8f268bcd2aff9ddc1605b7f5e7904 100644 --- a/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewsSourceBase.php +++ b/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewsSourceBase.php @@ -11,6 +11,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\TempStore\SharedTempStore; use Drupal\ui_patterns\SourcePluginBase; use Drupal\views\Entity\View; +use Drupal\views\Plugin\views\field\FieldPluginBase; use Drupal\views\ViewExecutable; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -63,6 +64,9 @@ abstract class ViewsSourceBase extends SourcePluginBase { */ protected function getView() : ?ViewExecutable { try { + if (isset($this->context["ui_patterns_views:view"])) { + return $this->getContextValue("ui_patterns_views:view"); + } return $this->getViewExecutable($this->getContextValue("entity")); } catch (ContextException) { @@ -86,6 +90,91 @@ abstract class ViewsSourceBase extends SourcePluginBase { return ($view instanceof View) ? $view->getExecutable() : $view; } + /** + * Returns the views field options or NULL if not applicable. + * + * @param \Drupal\views\ViewExecutable|null $view + * The view executable. + * + * @return array|null + * The options or NULL if not applicable. + */ + protected function getViewsFieldOptions(?ViewExecutable $view = NULL) : ?array { + if (!$view) { + return NULL; + } + $row_options = $view->display_handler->getOption("row") ?? []; + $fields_options = $view->display_handler->getOption("fields") ?? []; + if (!isset($row_options["type"]) || ($row_options["type"] !== "fields") || !is_array($fields_options) || count($fields_options) < 1) { + return NULL; + } + $options = []; + foreach ($fields_options as $field_id => $field) { + $options[$field_id] = empty($field['label']) ? $field_id : sprintf("%s (%s)", $field['label'], $field_id); + } + return $options; + } + + /** + * Test if field must be deleted from render. + * + * @param string $field_name + * The field name. + * @param mixed $field_output + * The render of field. + * @param \Drupal\views\ViewExecutable|null $view + * The current view. + * + * @return bool + * Return TRUE if the field need to be removed from render. + * + * @throws \Drupal\Component\Plugin\Exception\ContextException + */ + protected function isViewFieldHidden(string $field_name, mixed $field_output, ?ViewExecutable $view = NULL): bool { + $field = $this->getViewField($field_name, $view); + // Exclude field. + if (!$field || !is_array($field->options) || $field->options['exclude']) { + return TRUE; + } + $options = $this->getViewPluginOptions(); + $empty_value = $field->isValueEmpty($field_output, $field->options['empty_zero']); + // Remove field if empty. + return $empty_value && ($field->options['hide_empty'] === TRUE || $options['hide_empty'] === TRUE); + } + + /** + * Returns a view field plugin. + * + * @param string $field_name + * The field name. + * @param \Drupal\views\ViewExecutable|null $view + * The view executable or null to get from context. + * + * @return \Drupal\views\Plugin\views\field\FieldPluginBase|null + * The field plugin or NULL if not found. + */ + protected function getViewField(string $field_name, ?ViewExecutable $view) : ?FieldPluginBase { + if (!$view) { + $view = $this->getView(); + } + if (!$view || !is_array($view->field) || !isset($view->field[$field_name]) || !($view->field[$field_name] instanceof FieldPluginBase)) { + return NULL; + } + return $view->field[$field_name]; + } + + /** + * Returns the view row options. + * + * @return array + * The view row options. + * + * @throws \Drupal\Component\Plugin\Exception\ContextException + */ + protected function getViewPluginOptions() : array { + return isset($this->context["ui_patterns_views:plugin:options"]) ? $this->getContextValue("ui_patterns_views:plugin:options") : []; + } + /** * {@inheritdoc} */ diff --git a/modules/ui_patterns_views/src/Plugin/views/row/ComponentRow.php b/modules/ui_patterns_views/src/Plugin/views/row/ComponentRow.php index a74dea9efc81f589b6225f39be2db4dd3790a9b4..6b530f46449940ae45e6d339167977f7c6ab1e4c 100644 --- a/modules/ui_patterns_views/src/Plugin/views/row/ComponentRow.php +++ b/modules/ui_patterns_views/src/Plugin/views/row/ComponentRow.php @@ -10,8 +10,7 @@ use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Render\Element; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Url; -use Drupal\ui_patterns\Form\ComponentSettingsFormBuilderTrait; -use Drupal\ui_patterns\SourcePluginBase; +use Drupal\ui_patterns_views\ViewsPluginUiPatternsTrait; use Drupal\views\Attribute\ViewsRow; use Drupal\views\Plugin\views\row\Fields; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -31,14 +30,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; )] class ComponentRow extends Fields { - use ComponentSettingsFormBuilderTrait; - - /** - * The context repository. - * - * @var \Drupal\ui_patterns_views\UiPatternsViewsManager - */ - protected $uiPatternsViewsManager; + use ViewsPluginUiPatternsTrait; /** * {@inheritdoc} @@ -49,7 +41,7 @@ class ComponentRow extends Fields { $plugin_id, $plugin_definition, ); - $instance->uiPatternsViewsManager = $container->get('ui_patterns_views.manager'); + $instance->initialize($container); return $instance; } @@ -57,7 +49,7 @@ class ComponentRow extends Fields { * {@inheritdoc} */ protected function getAjaxUrl(FormStateInterface $form_state): ?Url { - return $this->uiPatternsViewsManager->getViewsUiBuildFormUrl($form_state); + return $this->getViewsUiBuildFormUrl($form_state); } /** @@ -91,7 +83,7 @@ class ComponentRow extends Fields { } } // Build ui patterns component form. - $ui_patterns_form = $this->componentSettingsForm($form, $form_state, $this->uiPatternsViewsManager->getComponentSourceContexts($this->view, $this)); + $ui_patterns_form = $this->componentSettingsForm($form, $form_state, $this->getComponentSourceContexts()); $form['ui_patterns'] = $ui_patterns_form['ui_patterns']; } @@ -104,9 +96,7 @@ class ComponentRow extends Fields { * {@inheritdoc} */ public function render($row) { - $context = $this->uiPatternsViewsManager->getComponentSourceContexts($this->view, $this); - $context['ui_patterns_views:row:view'] = new Context(new ContextDefinition('any'), $this->view); - $context['ui_patterns_views:row:options'] = new Context(new ContextDefinition('any'), $this->options); + $context = $this->getComponentSourceContexts(); $context['ui_patterns_views:row:index'] = new Context(new ContextDefinition('integer'), $row->index ?? 0); return $this->buildComponentRenderable($this->getComponentConfiguration()['component_id'], $context); } @@ -115,16 +105,7 @@ class ComponentRow extends Fields { * {@inheritdoc} */ public function calculateDependencies() { - $dependencies = parent::calculateDependencies(); - $component_settings = $this->getComponentSettings(); - $component_id = $component_settings["ui_patterns"]["component_id"] ?? NULL; - if (!$component_id) { - return $dependencies; - } - $component_dependencies = $this->calculateComponentDependencies($component_id, $this->uiPatternsViewsManager->getComponentSourceContexts($this->view, $this)); - SourcePluginBase::mergeConfigDependencies($dependencies, $component_dependencies); - SourcePluginBase::mergeConfigDependencies($dependencies, ["module" => ["ui_patterns_views"]]); - return $dependencies; + return $this->addDependencies(parent::calculateDependencies()); } } diff --git a/modules/ui_patterns_views/src/Plugin/views/style/ComponentStyle.php b/modules/ui_patterns_views/src/Plugin/views/style/ComponentStyle.php index 5e779e747ce5fbea80a0e010dcc7ba10a6e94e8e..0d4431457b283c4548c687a5eb383ffab4e47a96 100755 --- a/modules/ui_patterns_views/src/Plugin/views/style/ComponentStyle.php +++ b/modules/ui_patterns_views/src/Plugin/views/style/ComponentStyle.php @@ -10,8 +10,7 @@ use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Url; -use Drupal\ui_patterns\Form\ComponentSettingsFormBuilderTrait; -use Drupal\ui_patterns\SourcePluginBase; +use Drupal\ui_patterns_views\ViewsPluginUiPatternsTrait; use Drupal\views\Attribute\ViewsStyle; use Drupal\views\Plugin\views\style\StylePluginBase; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -31,7 +30,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; )] class ComponentStyle extends StylePluginBase { - use ComponentSettingsFormBuilderTrait; + use ViewsPluginUiPatternsTrait; /** * {@inheritdoc} @@ -39,11 +38,14 @@ class ComponentStyle extends StylePluginBase { protected $usesGrouping = FALSE; /** - * The context repository. - * - * @var \Drupal\ui_patterns_views\UiPatternsViewsManager + * {@inheritdoc} */ - protected $uiPatternsViewsManager; + protected $usesRowPlugin = TRUE; + + /** + * {@inheritdoc} + */ + protected $usesOptions = TRUE; /** * {@inheritdoc} @@ -54,20 +56,10 @@ class ComponentStyle extends StylePluginBase { $plugin_id, $plugin_definition, ); - $instance->uiPatternsViewsManager = $container->get('ui_patterns_views.manager'); + $instance->initialize($container); return $instance; } - /** - * {@inheritdoc} - */ - protected $usesRowPlugin = TRUE; - - /** - * {@inheritdoc} - */ - protected $usesOptions = TRUE; - /** * {@inheritdoc} */ @@ -88,7 +80,7 @@ class ComponentStyle extends StylePluginBase { parent::buildOptionsForm($form, $form_state); // Wrapper ajax id. $wrapper_id = ''; - $context = $this->uiPatternsViewsManager->getComponentSourceContexts($this->view, $this, []); + $context = $this->getComponentSourceContexts(); // Build ui patterns component form. $form['ui_patterns'] = []; $subform_state = SubformState::createForSubform($form['ui_patterns'], $form, $form_state); @@ -114,7 +106,7 @@ class ComponentStyle extends StylePluginBase { * {@inheritdoc} */ protected function getAjaxUrl(FormStateInterface $form_state): ?Url { - return $this->uiPatternsViewsManager->getViewsUiBuildFormUrl($form_state); + return $this->getViewsUiBuildFormUrl($form_state); } /** @@ -123,20 +115,11 @@ class ComponentStyle extends StylePluginBase { public function render(): array { $component_configuration = $this->getComponentSettings()['ui_patterns']; $component_id = $component_configuration["component_id"]; - // Get the style options, to check for a group title. - $context = $this->uiPatternsViewsManager->getComponentSourceContexts($this->view, $this, []); + $context = $this->getComponentSourceContexts(); $contextDefinitionRows = new ContextDefinition('any'); - // Insert views data inside each prop configuration. $rendered_output = parent::render(); foreach ($rendered_output as &$rendered_output_item) { - $context["ui_patterns_views:style:rows"] = new Context($contextDefinitionRows, $rendered_output_item["#rows"] ?? []); - if ($component_id) { - // Build the component. - $rendered_output_item = $this->buildComponentRenderable($component_id, $context); - } - else { - $rendered_output_item = []; - } + $rendered_output_item = ($component_id) ? $this->buildComponentRenderable($component_id, array_merge($context, ["ui_patterns_views:rows" => new Context($contextDefinitionRows, $rendered_output_item["#rows"] ?? [])])) : []; } unset($rendered_output_item); return $rendered_output; @@ -146,16 +129,7 @@ class ComponentStyle extends StylePluginBase { * {@inheritdoc} */ public function calculateDependencies() { - $dependencies = parent::calculateDependencies(); - $component_settings = $this->getComponentSettings(); - $component_id = $component_settings["ui_patterns"]["component_id"] ?? NULL; - if (!$component_id) { - return $dependencies; - } - $component_dependencies = $this->calculateComponentDependencies($component_id, $this->uiPatternsViewsManager->getComponentSourceContexts($this->view, $this)); - SourcePluginBase::mergeConfigDependencies($dependencies, $component_dependencies); - SourcePluginBase::mergeConfigDependencies($dependencies, ["module" => ["ui_patterns_views"]]); - return $dependencies; + return $this->addDependencies(parent::calculateDependencies()); } } diff --git a/modules/ui_patterns_views/src/UiPatternsViewsManager.php b/modules/ui_patterns_views/src/UiPatternsViewsManager.php deleted file mode 100644 index 6f5b79903ab0ebb87c2d73ff4d6a84a22a70b29e..0000000000000000000000000000000000000000 --- a/modules/ui_patterns_views/src/UiPatternsViewsManager.php +++ /dev/null @@ -1,113 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Drupal\ui_patterns_views; - -use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Plugin\Context\Context; -use Drupal\Core\Plugin\Context\ContextDefinition; -use Drupal\Core\Plugin\Context\ContextInterface; -use Drupal\Core\Plugin\Context\EntityContext; -use Drupal\Core\Plugin\PluginBase; -use Drupal\ui_patterns\Plugin\Context\RequirementsContext; -use Drupal\views\ViewExecutable; - -/** - * Helper manager of views plugin. - */ -class UiPatternsViewsManager { - - /** - * Constructs the object. - */ - public function __construct(protected EntityTypeManagerInterface $entityTypeManager, protected ModuleHandlerInterface $moduleHandler) {} - - /** - * Set the context of field and entity (override the method trait). - * - * @param \Drupal\views\ViewExecutable $view - * The view executable object. - * @param \Drupal\Core\Plugin\PluginBase $plugin - * The view plugin. - * @param array<string>|null $more_context_requirements - * Some more context requirements. - * - * @return array - * Source contexts. - * - * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException - * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException - */ - public function getComponentSourceContexts(ViewExecutable $view, PluginBase $plugin, ?array $more_context_requirements = NULL): array { - $plugin_definition = $plugin->getPluginDefinition(); - if (!is_array($plugin_definition) || empty($plugin_definition['plugin_type']) || empty($plugin_definition['provider']) || $plugin_definition['provider'] !== 'ui_patterns_views') { - return []; - } - $context_source = []; - $context_requirements = ['views:' . $plugin_definition['plugin_type']]; - if (is_array($more_context_requirements) && (count($more_context_requirements) > 0)) { - $context_requirements = array_merge($context_requirements, $more_context_requirements); - } - $context_source = RequirementsContext::addToContext($context_requirements, $context_source); - // Build context views id. - $context_id = ContextDefinition::create('string'); - $context_id->setLabel("views_id"); - $context_source['views_id'] = new Context($context_id, $view->id()); - // Build context display. - $context_source['display'] = $this->getViewsDisplayContext($view); - // Build view entity context. - $view_entity = $this->entityTypeManager->getStorage('view') - ->load($view->id()); - if (!empty($view_entity)) { - $context_source['entity'] = EntityContext::fromEntity($view_entity); - } - return $context_source; - } - - /** - * Return the views display context. - * - * @param \Drupal\views\ViewExecutable $view - * The view executable object. - * - * @return \Drupal\Core\Plugin\Context\ContextInterface - * The context. - */ - protected function getViewsDisplayContext(ViewExecutable $view): ContextInterface { - $context_display = ContextDefinition::create('string'); - $context_display->setLabel("display"); - // Build views display context. - $display = $view->getDisplay(); - $display_id = $display->getPluginId(); - // Display has no override of field. - if (!isset($display->display['display_options']['fields'])) { - $display_id = 'default'; - if (!empty($display->default_display)) { - /** @var \Drupal\views\Plugin\views\display\DefaultDisplay $default_display */ - $default_display = $display->default_display; - $display_id = $default_display->getPluginId(); - } - } - return new Context($context_display, $display_id); - } - - /** - * Return the views ui ajax form url. - * - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current form state. - * - * @return \Drupal\Core\Url|null - * The views ajax url. - */ - public function getViewsUiBuildFormUrl(FormStateInterface $form_state) { - if ($this->moduleHandler->moduleExists('views_ui') && function_exists('views_ui_build_form_url')) { - return views_ui_build_form_url($form_state); - } - return NULL; - } - -} diff --git a/modules/ui_patterns_views/src/ViewsPluginUiPatternsTrait.php b/modules/ui_patterns_views/src/ViewsPluginUiPatternsTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..7fcc495b6a5d3e6cc99cba4eda2f3aac817e6ce0 --- /dev/null +++ b/modules/ui_patterns_views/src/ViewsPluginUiPatternsTrait.php @@ -0,0 +1,119 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\ui_patterns_views; + +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\ui_patterns\Form\ComponentSettingsFormBuilderTrait; +use Drupal\ui_patterns\Plugin\Context\RequirementsContext; +use Drupal\ui_patterns\SourcePluginBase; +use Drupal\views\ViewEntityInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Views plugin UI patterns trait. + */ +trait ViewsPluginUiPatternsTrait { + + use ComponentSettingsFormBuilderTrait; + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * Initialize the trait. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The container. + */ + public function initialize(ContainerInterface $container) : void { + $this->entityTypeManager = $container->get('entity_type.manager'); + $this->moduleHandler = $container->get('module_handler'); + } + + /** + * Get the source contexts for the component. + * + * @return array + * Source contexts. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + protected function getComponentSourceContexts() : array { + $context = []; + $plugin_definition = $this->getPluginDefinition(); + if (is_array($plugin_definition) && isset($plugin_definition['plugin_type'])) { + $context = RequirementsContext::addToContext(['views:' . $plugin_definition['plugin_type']], $context); + } + $view = $this->view; + // Add view to context. + $context['ui_patterns_views:view'] = new Context(new ContextDefinition('any'), $view); + // Add plugin options to context. + $context['ui_patterns_views:plugin:options'] = new Context(new ContextDefinition('any'), $this->options); + // Add plugin options to context. + $context['ui_patterns_views:plugin:display_handler'] = new Context(new ContextDefinition('any'), $this->displayHandler); + // Build view entity context. + $view_entity = $this->entityTypeManager->getStorage('view')->load($view->id()); + if ($view_entity instanceof ViewEntityInterface) { + $context['entity'] = EntityContext::fromEntity($view_entity); + } + return $context; + } + + /** + * Add dependencies to the plugin. + * + * @param array<string, mixed> $dependencies + * Initial dependencies. + * + * @return array<string, mixed> + * The dependencies. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + protected function addDependencies(array $dependencies) { + $component_settings = $this->getComponentSettings(); + $component_id = $component_settings["ui_patterns"]["component_id"] ?? NULL; + if (!$component_id) { + return $dependencies; + } + $component_dependencies = $this->calculateComponentDependencies($component_id, $this->getComponentSourceContexts()); + SourcePluginBase::mergeConfigDependencies($dependencies, $component_dependencies); + SourcePluginBase::mergeConfigDependencies($dependencies, ["module" => ["ui_patterns_views"]]); + return $dependencies; + } + + /** + * Return the views ui ajax form url. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current form state. + * + * @return \Drupal\Core\Url|null + * The views ajax url. + */ + protected function getViewsUiBuildFormUrl(FormStateInterface $form_state) { + if ($this->moduleHandler->moduleExists('views_ui') && function_exists('views_ui_build_form_url')) { + return views_ui_build_form_url($form_state); + } + return NULL; + } + +} diff --git a/modules/ui_patterns_views/ui_patterns_views.services.yml b/modules/ui_patterns_views/ui_patterns_views.services.yml deleted file mode 100644 index cd54321afafdc1905f58bbed3ac63e8318ff6d52..0000000000000000000000000000000000000000 --- a/modules/ui_patterns_views/ui_patterns_views.services.yml +++ /dev/null @@ -1,4 +0,0 @@ -services: - ui_patterns_views.manager: - class: Drupal\ui_patterns_views\UiPatternsViewsManager - arguments: ["@entity_type.manager", "@module_handler"] diff --git a/src/Plugin/Context/RequirementsContext.php b/src/Plugin/Context/RequirementsContext.php index 8bf8dbdc66fc3a7ff1bbda8af11a7b7c6fee61b8..1c2dbe1c1fed0403b641edcd0448720b370470c8 100644 --- a/src/Plugin/Context/RequirementsContext.php +++ b/src/Plugin/Context/RequirementsContext.php @@ -58,4 +58,24 @@ class RequirementsContext extends Context { return $contexts; } + /** + * Remove values from the context_requirements context. + * + * @param array<string> $values + * The values to add to context_requirements context. + * @param array<\Drupal\Core\Plugin\Context\ContextInterface> $contexts + * The contexts. + * + * @return array<\Drupal\Core\Plugin\Context\ContextInterface> + * The contexts. + */ + public static function removeFromContext(array $values, array $contexts): array { + if (array_key_exists("context_requirements", $contexts) && ($contexts["context_requirements"] instanceof RequirementsContext)) { + $contexts["context_requirements"] = static::fromValues(array_filter((array) $contexts["context_requirements"]->getContextValue(), function ($value) use ($values) { + return !in_array($value, $values, TRUE); + })); + } + return $contexts; + } + } diff --git a/src/Plugin/UiPatterns/DerivableContext/EntityFieldDerivableContext.php b/src/Plugin/UiPatterns/DerivableContext/EntityFieldDerivableContext.php index 2fb3867d126183a39452d04f7830ad3f5eb3a4b0..6c7553b6b979f9d922ed507c958121109f679cab 100644 --- a/src/Plugin/UiPatterns/DerivableContext/EntityFieldDerivableContext.php +++ b/src/Plugin/UiPatterns/DerivableContext/EntityFieldDerivableContext.php @@ -33,7 +33,11 @@ class EntityFieldDerivableContext extends DerivableContextPluginBase { $field_name = array_pop($split_plugin_id); $field_name_context_definition = new ContextDefinition("string", "Field Name"); $contexts['field_name'] = new Context($field_name_context_definition, $field_name); + if (isset($contexts['ui_patterns:field:index'])) { + unset($contexts['ui_patterns:field:index']); + } $plugin_definition = $this->getPluginDefinition(); + $contexts = RequirementsContext::removeFromContext(["field_granularity:item"], $contexts); if (is_array($plugin_definition) && isset($plugin_definition["metadata"]["field"]["cardinality"])) { $field_cardinality = $plugin_definition["metadata"]["field"]["cardinality"]; if ($field_cardinality === 1) { diff --git a/src/Plugin/UiPatterns/PropType/BooleanPropType.php b/src/Plugin/UiPatterns/PropType/BooleanPropType.php index 158878e1e301654a312ae7e93697da961ba2496e..99037e586848d4ff37462b90aadf7a9e491f67b6 100644 --- a/src/Plugin/UiPatterns/PropType/BooleanPropType.php +++ b/src/Plugin/UiPatterns/PropType/BooleanPropType.php @@ -21,5 +21,4 @@ use Drupal\ui_patterns\PropTypePluginBase; typed_data: ['boolean'] )] class BooleanPropType extends PropTypePluginBase { - } diff --git a/src/Plugin/UiPatterns/Source/FieldPropertySource.php b/src/Plugin/UiPatterns/Source/FieldPropertySource.php index 1137af3031f4e34afd52fbf06aab6dcd3e9740c6..d68a9227bba7a659db02d2885537bb3c358ca794 100644 --- a/src/Plugin/UiPatterns/Source/FieldPropertySource.php +++ b/src/Plugin/UiPatterns/Source/FieldPropertySource.php @@ -24,8 +24,8 @@ class FieldPropertySource extends FieldValueSourceBase { */ public function getPropValue(): mixed { $items = $this->getEntityFieldItemList(); - $delta = $this->getConfiguredSettingsExtra("field_index") ?? 0; - $lang_code = $this->getConfiguredSettingsExtra("lang_code") ?? "und"; + $delta = (isset($this->context['ui_patterns:field:index'])) ? $this->getContextValue('ui_patterns:field:index') : 0; + $lang_code = (isset($this->context['ui_patterns:lang_code'])) ? ($this->getContextValue("ui_patterns:lang_code") ?? "und") : "und"; $property = $this->getCustomPluginMetadata('property'); if (empty($property)) { return NULL; @@ -36,7 +36,8 @@ class FieldPropertySource extends FieldValueSourceBase { return NULL; } $property_value = $this->extractPropertyValue($field_item_at_delta, $property, $lang_code); - return $this->transTypeProp($property_value, $this->propId); + $prop_typ_types = (isset($this->propDefinition['type']) && is_array($this->propDefinition['type'])) ? $this->propDefinition['type'] : []; + return $this->transTypeProp($property_value, $prop_typ_types); } /** @@ -44,21 +45,27 @@ class FieldPropertySource extends FieldValueSourceBase { * * @param mixed $value * The value to trans-type. - * @param string $prop_id - * The prop drupal id. + * @param array<string> $prop_types + * The prop types. * * @return bool|float|int * The value converted. * * @todo this part will be move into UI patterns. */ - private function transTypeProp(mixed $value, string $prop_id): mixed { - return match ($prop_id) { - 'decimal', 'integer', 'duration' => (int) $value, - 'float' => (float) $value, - 'boolean' => (boolean) $value, - default => $value, - }; + private function transTypeProp(mixed $value, array $prop_types): mixed { + foreach ($prop_types as $prop_type) { + $converted = match ($prop_type) { + 'decimal', 'integer', 'duration' => is_int($value) ? $value : (int) $value, + 'float' => is_float($value) ? $value : (float) $value, + 'boolean' => is_bool($value) ? $value : (boolean) $value, + default => NULL, + }; + if ($converted !== NULL) { + return $converted; + } + } + return $value; } } diff --git a/src/Plugin/UiPatterns/Source/FieldSourceBase.php b/src/Plugin/UiPatterns/Source/FieldSourceBase.php index b34ead1f3ec4738f909da7fdd3487a064348e083..db81e04d3dc88b663a2182303be03ad88d3a4a20 100644 --- a/src/Plugin/UiPatterns/Source/FieldSourceBase.php +++ b/src/Plugin/UiPatterns/Source/FieldSourceBase.php @@ -117,11 +117,11 @@ abstract class FieldSourceBase extends SourcePluginBase implements SourceInterfa * The field definition. */ protected function getFieldDefinition(): ?FieldDefinitionInterface { - $entity_type_id = $this->getEntityTypeId(); - if (!$entity_type_id) { + $entity = $this->getEntity(); + if (!$entity) { return NULL; } - $field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $this->getBundle()); + $field_definitions = $this->entityFieldManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()); $field_name = $this->getFieldName(); if (!array_key_exists($field_name, $field_definitions)) { return NULL; diff --git a/src/Plugin/UiPatterns/Source/FieldValueSourceBase.php b/src/Plugin/UiPatterns/Source/FieldValueSourceBase.php index 7c90dbcde75c838495cb27094bd5b3f3b78d26bc..2c62bdd3371ca73693011c2bc67dfa9fde3fbbb1 100644 --- a/src/Plugin/UiPatterns/Source/FieldValueSourceBase.php +++ b/src/Plugin/UiPatterns/Source/FieldValueSourceBase.php @@ -64,13 +64,10 @@ abstract class FieldValueSourceBase extends FieldSourceBase implements SourceInt if ($entity instanceof EntityInterface) { return $entity; } - // This allows to get the entity from the extra settings, - // if it is not in the context. - // useful in the context of views, - // where the call of setComponentSettingsExtra() has saved the items. - $itemFromExtraSettings = $this->getConfiguredSettingsExtra("items"); - if ($itemFromExtraSettings instanceof FieldItemListInterface) { - return $itemFromExtraSettings->getEntity(); + // Useful in the context of views. + $field_items = $this->getContextValue("ui_patterns:field:items"); + if ($field_items instanceof FieldItemListInterface) { + return $field_items->getEntity(); } return $this->sampleEntityGenerator->get($this->getEntityTypeId(), $this->getBundle()); } @@ -82,22 +79,27 @@ abstract class FieldValueSourceBase extends FieldSourceBase implements SourceInt * Return the field items of entity. */ protected function getEntityFieldItemList():mixed { - $itemsFromExtra = $this->getConfiguredSettingsExtra('items'); - if ($itemsFromExtra instanceof FieldItemListInterface) { - return $itemsFromExtra; - } $field_name = $this->getCustomPluginMetadata('field_name'); if (empty($field_name)) { return NULL; } - /** @var \Drupal\Core\Entity\ContentEntityBase $entity */ $entity = $this->getEntity(); + if (!$entity) { + $field_items = $this->getContextValue('ui_patterns:field:items'); + if ($field_items instanceof FieldItemListInterface) { + if ($field_items->getFieldDefinition()->getName() == $field_name) { + return $field_items; + } + $entity = $field_items->getEntity(); + } + } if (!$entity) { $this->getLogger('ui_patterns') ->error('Entity not found in context'); return NULL; } + if (!$entity->hasField($field_name)) { $this->getLogger('ui_patterns') ->error('Entity %entity_type %bundle has no field %field_name', [ @@ -184,28 +186,6 @@ abstract class FieldValueSourceBase extends FieldSourceBase implements SourceInt return $value; } - /** - * Returns part of the plugin config specific to UI patterns field formatter. - * - * It stores some data, typically overridden by field formatter, - * like to get the field index. - * - * @param string|null $key - * Optional key to retrieve. - * - * @return mixed|null - * Value. - * - * @see setComponentSettingsExtra() - */ - protected function getConfiguredSettingsExtra(?string $key = NULL): mixed { - $parents = ['settings', 'extra']; - if ($key) { - $parents[] = $key; - } - return $this->getSettingsFromConfiguration($parents); - } - /** * Get the settings from the plugin configuration. *