diff --git a/modules/ui_patterns_legacy/src/PropConverter.php b/modules/ui_patterns_legacy/src/PropConverter.php index 2691949ad03e7c541ce270615e2fc0a3c21c8252..cbf76ef9878d0dcea175d9e413020688d0a6d016 100644 --- a/modules/ui_patterns_legacy/src/PropConverter.php +++ b/modules/ui_patterns_legacy/src/PropConverter.php @@ -51,6 +51,7 @@ class PropConverter { $labels = \array_values($setting['options']); $prop = [ 'type' => 'array', + 'uniqueItems' => TRUE, 'items' => [ 'type' => $this->getEnumType($values), 'enum' => $values, diff --git a/src/EnumTrait.php b/src/EnumTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..3d8bbfb16de4a82812a8f41a6afaed6c364ff7d2 --- /dev/null +++ b/src/EnumTrait.php @@ -0,0 +1,61 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\ui_patterns; + +/** + * Trait for plugins (sources and prop types) handling enum values. + */ +trait EnumTrait { + + /** + * Get form element options from enumeration. + */ + protected function getEnumOptions(array $definition): array { + $values = array_combine($definition['enum'], $definition['enum']); + foreach ($values as $key => $label) { + if (is_string($label)) { + $values[$key] = ucwords($label); + } + } + if (!isset($definition['meta:enum'])) { + return array_values($values); + } + $meta = $definition['meta:enum']; + // Remove meta:enum items not found in options. + $meta = array_intersect_key($meta, $values); + foreach ($meta as $value => $label) { + $values[$value] = $label; + } + return $values; + } + + /** + * Get allowed values from enumeration. + */ + protected function getAllowedValues(array $definition): array { + return array_values($this->getEnumOptions($definition)); + } + + /** + * Converts a source value type to enum data type. + * + * @param string $value + * The stored. + * @param array $enum + * The defined enums. + * + * @return float|int|mixed + * The converted value. + */ + protected function convertValueToEnumType(string $value, array $enum) { + return match (TRUE) { + in_array($value, $enum, TRUE) => $value, + in_array((int) $value, $enum, TRUE) => (int) $value, + in_array((float) $value, $enum, TRUE) => (float) $value, + default => $value, + }; + } + +} diff --git a/src/Plugin/UiPatterns/PropType/EnumListPropType.php b/src/Plugin/UiPatterns/PropType/EnumListPropType.php index 4877f408453530cf514cab85943bc6cda42725ab..b639622eea0fa71433ca6b366da47e15022dac5e 100644 --- a/src/Plugin/UiPatterns/PropType/EnumListPropType.php +++ b/src/Plugin/UiPatterns/PropType/EnumListPropType.php @@ -6,6 +6,7 @@ namespace Drupal\ui_patterns\Plugin\UiPatterns\PropType; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\ui_patterns\Attribute\PropType; +use Drupal\ui_patterns\EnumTrait; use Drupal\ui_patterns\PropTypePluginBase; /** @@ -16,28 +17,27 @@ use Drupal\ui_patterns\PropTypePluginBase; label: new TranslatableMarkup('List of enums'), description: new TranslatableMarkup('Ordered list of predefined string or number items.'), default_source: 'checkboxes', - schema: ['type' => 'array', 'items' => ['type' => ['string', 'number', 'integer'], 'enum' => []]] + schema: ['type' => 'array', 'items' => ['type' => ['string', 'number', 'integer'], 'enum' => []]], + priority: 1 )] class EnumListPropType extends PropTypePluginBase { + use EnumTrait; + /** * {@inheritdoc} */ public function getSummary(array $definition): array { $summary = parent::getSummary($definition); - if (isset($definition['items']['enum']) && !isset($definition['items']['meta:enum'])) { - $values = implode(", ", $definition['items']['enum']); - $summary[] = $this->t("Values: @values", ["@values" => $values]); - } - if (isset($definition['items']['enum']) && isset($definition['items']['meta:enum'])) { - $values = implode(", ", $definition['items']['meta:enum']); - $summary[] = $this->t("Values: @values", ["@values" => $values]); + if (isset($definition['items']['enum'])) { + $values = implode(", ", $this->getAllowedValues($definition['items'])); + $summary[] = $this->t("Allowed values: @values", ["@values" => $values]); } - if (isset($definition['items']['minItems'])) { - $summary[] = $this->t("Min items: @length", ["@length" => $definition['items']['minItems']]); + if (isset($definition['minItems'])) { + $summary[] = $this->t("Min items: @length", ["@length" => $definition['minItems']]); } - if (isset($definition['items']['maxItems'])) { - $summary[] = $this->t("Max items: @length", ["@length" => $definition['items']['maxItems']]); + if (isset($definition['maxItems'])) { + $summary[] = $this->t("Max items: @length", ["@length" => $definition['maxItems']]); } return $summary; } diff --git a/src/Plugin/UiPatterns/PropType/EnumPropType.php b/src/Plugin/UiPatterns/PropType/EnumPropType.php index 8e64c2b6553cbee081bf907f7112f3e187053593..3c143449a844c0d13114fb00b5402952b28958ef 100644 --- a/src/Plugin/UiPatterns/PropType/EnumPropType.php +++ b/src/Plugin/UiPatterns/PropType/EnumPropType.php @@ -6,6 +6,7 @@ namespace Drupal\ui_patterns\Plugin\UiPatterns\PropType; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\ui_patterns\Attribute\PropType; +use Drupal\ui_patterns\EnumTrait; use Drupal\ui_patterns\PropTypePluginBase; /** @@ -21,16 +22,16 @@ use Drupal\ui_patterns\PropTypePluginBase; )] class EnumPropType extends PropTypePluginBase { + use EnumTrait; + /** * {@inheritdoc} */ public function getSummary(array $definition): array { $summary = parent::getSummary($definition); - if (isset($definition['enum']) && !isset($definition['meta:enum'])) { - $summary[] = $this->t("Values: @values", ["@values" => implode(", ", $definition['enum'])]); - } - if (isset($definition['enum']) && isset($definition['meta:enum'])) { - $summary[] = $this->t("Values: @values", ["@values" => implode(", ", $definition['meta:enum'])]); + if (isset($definition['enum'])) { + $values = implode(", ", $this->getAllowedValues($definition)); + $summary[] = $this->t("Allowed values: @values", ["@values" => $values]); } return $summary; } diff --git a/src/Plugin/UiPatterns/PropType/EnumSetPropType.php b/src/Plugin/UiPatterns/PropType/EnumSetPropType.php new file mode 100644 index 0000000000000000000000000000000000000000..58d158be486e29db8ba98abf13bbba6bc5330c98 --- /dev/null +++ b/src/Plugin/UiPatterns/PropType/EnumSetPropType.php @@ -0,0 +1,52 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\ui_patterns\Plugin\UiPatterns\PropType; + +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\ui_patterns\Attribute\PropType; +use Drupal\ui_patterns\EnumTrait; +use Drupal\ui_patterns\PropTypePluginBase; + +/** + * Provides a 'enum_set' PropType. + */ +#[PropType( + id: 'enum_set', + label: new TranslatableMarkup('Set of enums'), + description: new TranslatableMarkup('Set of unique predefined string or number items.'), + default_source: 'checkboxes', + schema: [ + 'type' => 'array', + 'uniqueItems' => TRUE, + 'items' => [ + 'type' => ['string', 'number', 'integer'], + 'enum' => [], + ], + ], + priority: 10 +)] +class EnumSetPropType extends PropTypePluginBase { + + use EnumTrait; + + /** + * {@inheritdoc} + */ + public function getSummary(array $definition): array { + $summary = parent::getSummary($definition); + if (isset($definition['items']['enum'])) { + $values = implode(", ", $this->getAllowedValues($definition['items'])); + $summary[] = $this->t("Allowed values: @values", ["@values" => $values]); + } + if (isset($definition['minItems'])) { + $summary[] = $this->t("Min items: @length", ["@length" => $definition['minItems']]); + } + if (isset($definition['maxItems'])) { + $summary[] = $this->t("Max items: @length", ["@length" => $definition['maxItems']]); + } + return $summary; + } + +} diff --git a/src/Plugin/UiPatterns/Source/CheckboxesWidget.php b/src/Plugin/UiPatterns/Source/CheckboxesWidget.php index 8c5527466d5cb94effd8684fda25ddd9c5b43169..3f6fee4b0d7a660dbfc549072f075b46bd246dc0 100644 --- a/src/Plugin/UiPatterns/Source/CheckboxesWidget.php +++ b/src/Plugin/UiPatterns/Source/CheckboxesWidget.php @@ -7,6 +7,7 @@ namespace Drupal\ui_patterns\Plugin\UiPatterns\Source; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\ui_patterns\Attribute\Source; +use Drupal\ui_patterns\EnumTrait; use Drupal\ui_patterns\SourcePluginPropValue; /** @@ -16,11 +17,13 @@ use Drupal\ui_patterns\SourcePluginPropValue; id: 'checkboxes', label: new TranslatableMarkup('Checkboxes'), description: new TranslatableMarkup('A set of checkboxes.'), - prop_types: ['enum_list'], + prop_types: ['enum_set'], tags: ['widget'] )] class CheckboxesWidget extends SourcePluginPropValue { + use EnumTrait; + /** * {@inheritdoc} */ @@ -38,28 +41,10 @@ class CheckboxesWidget extends SourcePluginPropValue { $form['value'] = [ '#type' => 'checkboxes', '#default_value' => $this->getSetting('value') ?? [], - "#options" => $this->getOptions(), + "#options" => $this->getEnumOptions($this->propDefinition['items']), ]; $this->addRequired($form['value']); return $form; } - /** - * Get checkboxes options. - */ - protected function getOptions(): array { - $options = array_combine($this->propDefinition['items']['enum'], $this->propDefinition['items']['enum']); - foreach ($options as $key => $label) { - if (is_string($label)) { - $options[$key] = ucwords($label); - } - } - if (!isset($this->propDefinition['items']['meta:enum'])) { - return $options; - } - $meta = $this->propDefinition['items']['meta:enum']; - // Remove meta:enum items not found in options. - return array_intersect_key($meta, $options); - } - } diff --git a/src/Plugin/UiPatterns/Source/EnumSourceTrait.php b/src/Plugin/UiPatterns/Source/EnumSourceTrait.php deleted file mode 100644 index 8f1a03a0f53cb9cb79f3011ec8139996aa30abe8..0000000000000000000000000000000000000000 --- a/src/Plugin/UiPatterns/Source/EnumSourceTrait.php +++ /dev/null @@ -1,32 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Drupal\ui_patterns\Plugin\UiPatterns\Source; - -/** - * Trait for sources handling enum values. - */ -trait EnumSourceTrait { - - /** - * Converts a source value type to enum data type. - * - * @param string $value - * The stored. - * @param array $enum - * The defined enums. - * - * @return float|int|mixed - * The converted value. - */ - protected function convertValueToEnumType(string $value, array $enum) { - return match (TRUE) { - in_array($value, $enum, TRUE) => $value, - in_array((int) $value, $enum, TRUE) => (int) $value, - in_array((float) $value, $enum, TRUE) => (float) $value, - default => $value, - }; - } - -} diff --git a/src/Plugin/UiPatterns/Source/SelectWidget.php b/src/Plugin/UiPatterns/Source/SelectWidget.php index 717a286efb9c61184da809004a91a15440dd5cc1..e593bdfcbfbc60fff2b221dcc16c7698cdac1bfb 100644 --- a/src/Plugin/UiPatterns/Source/SelectWidget.php +++ b/src/Plugin/UiPatterns/Source/SelectWidget.php @@ -7,6 +7,7 @@ namespace Drupal\ui_patterns\Plugin\UiPatterns\Source; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\ui_patterns\Attribute\Source; +use Drupal\ui_patterns\EnumTrait; use Drupal\ui_patterns\SourcePluginPropValue; /** @@ -21,7 +22,7 @@ use Drupal\ui_patterns\SourcePluginPropValue; )] class SelectWidget extends SourcePluginPropValue { - use EnumSourceTrait; + use EnumTrait; /** * {@inheritdoc} @@ -31,7 +32,7 @@ class SelectWidget extends SourcePluginPropValue { $form['value'] = [ '#type' => 'select', '#default_value' => $this->getSetting('value'), - "#options" => $this->getOptions(), + "#options" => $this->getEnumOptions($this->propDefinition), "#empty_option" => $this->t("- Select -"), ]; $this->addRequired($form['value']); @@ -41,24 +42,6 @@ class SelectWidget extends SourcePluginPropValue { return $form; } - /** - * Get select options. - */ - protected function getOptions(): array { - $options = array_combine($this->propDefinition['enum'], $this->propDefinition['enum']); - foreach ($options as $key => $label) { - if (is_string($label)) { - $options[$key] = ucwords($label); - } - } - if (!isset($this->propDefinition['meta:enum'])) { - return $options; - } - $meta = $this->propDefinition['meta:enum']; - // Remove meta:enum items not found in options. - return array_intersect_key($meta, $options); - } - /** * {@inheritdoc} */ diff --git a/src/Plugin/UiPatterns/Source/SelectsWidget.php b/src/Plugin/UiPatterns/Source/SelectsWidget.php new file mode 100644 index 0000000000000000000000000000000000000000..3543aae8dcef478565a5b223c2ef3764807dceed --- /dev/null +++ b/src/Plugin/UiPatterns/Source/SelectsWidget.php @@ -0,0 +1,56 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\ui_patterns\Plugin\UiPatterns\Source; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\ui_patterns\Attribute\Source; +use Drupal\ui_patterns\EnumTrait; +use Drupal\ui_patterns\SourcePluginPropValue; + +/** + * Plugin implementation of the source. + */ +#[Source( + id: 'selects', + label: new TranslatableMarkup('Selects'), + description: new TranslatableMarkup('A set of select.'), + prop_types: ['enum_list'], + tags: ['widget'] +)] +class SelectsWidget extends SourcePluginPropValue { + + use EnumTrait; + + /** + * {@inheritdoc} + */ + public function getPropValue(): mixed { + $value = parent::getPropValue() ?? []; + $value = is_scalar($value) ? [$value] : $value; + return array_values($value); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state): array { + $form = parent::settingsForm($form, $form_state); + $min = $this->propDefinition['minItems'] ?? 0; + $max = $this->propDefinition['maxItems'] ?? 1; + foreach (range(0, $max - 1) as $index) { + $form['value'][$index] = [ + '#type' => 'select', + '#default_value' => $this->getSetting('value')[$index] ?? NULL, + '#options' => $this->getEnumOptions($this->propDefinition['items']), + '#title' => '#' . ($index + 1), + '#required' => ($index < $min), + '#empty_value' => "", + ]; + } + return $form; + } + +} diff --git a/src/PropTypePluginBase.php b/src/PropTypePluginBase.php index a28f610a6c9edb3f9f0478a2fcb7527d8aaf6f16..0a5f9582bc67902c55b603d9f31043f85be27c69 100644 --- a/src/PropTypePluginBase.php +++ b/src/PropTypePluginBase.php @@ -52,11 +52,8 @@ abstract class PropTypePluginBase extends PluginBase implements PropTypeInterfac if (isset($definition['description'])) { $summary[] = $definition['description']; } - if (isset($definition['default']) && is_scalar($definition['default'])) { - $summary[] = $this->t("Default: @default", ["@default" => $definition['default']]); - } - if (isset($definition['default']) && is_array($definition['default'])) { - $summary[] = $this->t("Default: @default", ["@default" => implode(", ", $definition['default'])]); + if (isset($definition['default'])) { + $summary[] = $this->t("Default: @default", ["@default" => json_encode($definition['default'])]); } if (isset($definition['ui_patterns']['required']) && $definition['ui_patterns']['required']) { $summary[] = $this->t("Required"); diff --git a/src/SchemaManager/CompatibilityChecker.php b/src/SchemaManager/CompatibilityChecker.php index 1293d95d793e3160a890d5835f2deef5b5c64d33..5bb53cca9bb8c56f5e852892a68c320521a9a154 100644 --- a/src/SchemaManager/CompatibilityChecker.php +++ b/src/SchemaManager/CompatibilityChecker.php @@ -159,14 +159,17 @@ class CompatibilityChecker { if (!isset($checked_schema["items"]) && isset($reference_schema["items"])) { return FALSE; } + if (($reference_schema["uniqueItems"] ?? FALSE) && (!isset($checked_schema["uniqueItems"]) || !$checked_schema["uniqueItems"])) { + return FALSE; + } // https://json-schema.org/understanding-json-schema/reference/array#items if (isset($checked_schema["items"]) && isset($reference_schema["items"])) { if (!$this->isCompatible($checked_schema["items"], $reference_schema["items"])) { return FALSE; } } - // contains, mincontains, maxcontains, length and uniqueness are not managed - // yet. + // minItems, maxItems, contains, mincontains, maxcontains and length are + // not managed yet. return TRUE; }