Initial Drupal 11 with DDEV setup
This commit is contained in:
87
web/core/modules/options/src/Hook/OptionsHooks.php
Normal file
87
web/core/modules/options/src/Hook/OptionsHooks.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\options\Hook;
|
||||
|
||||
use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\field\FieldStorageConfigInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for options.
|
||||
*/
|
||||
class OptionsHooks {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
#[Hook('help')]
|
||||
public function help($route_name, RouteMatchInterface $route_match): ?string {
|
||||
switch ($route_name) {
|
||||
case 'help.page.options':
|
||||
$output = '';
|
||||
$output .= '<h2>' . $this->t('About') . '</h2>';
|
||||
$output .= '<p>' . $this->t('The Options module allows you to create fields where data values are selected from a fixed list of options. Usually these items are entered through a select list, checkboxes, or radio buttons. See the <a href=":field">Field module help</a> and the <a href=":field_ui">Field UI help</a> pages for general information on fields and how to create and manage them. For more information, see the <a href=":options_do">online documentation for the Options module</a>.', [
|
||||
':field' => Url::fromRoute('help.page', [
|
||||
'name' => 'field',
|
||||
])->toString(),
|
||||
':field_ui' => \Drupal::moduleHandler()->moduleExists('field_ui') ? Url::fromRoute('help.page', [
|
||||
'name' => 'field_ui',
|
||||
])->toString() : '#',
|
||||
':options_do' => 'https://www.drupal.org/documentation/modules/options',
|
||||
]) . '</p>';
|
||||
$output .= '<h2>' . $this->t('Uses') . '</h2>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<dt>' . $this->t('Managing and displaying list fields') . '</dt>';
|
||||
$output .= '<dd>' . $this->t('The <em>settings</em> and the <em>display</em> of the list fields can be configured separately. See the <a href=":field_ui">Field UI help</a> for more information on how to manage fields and their display.', [
|
||||
':field_ui' => \Drupal::moduleHandler()->moduleExists('field_ui') ? Url::fromRoute('help.page', [
|
||||
'name' => 'field_ui',
|
||||
])->toString() : '#',
|
||||
]) . '</dd>';
|
||||
$output .= '<dt>' . $this->t('Defining option keys and labels') . '</dt>';
|
||||
$output .= '<dd>' . $this->t('When you define the list options you can define a key and a label for each option in the list. The label will be shown to the users while the key gets stored in the database.') . '</dd>';
|
||||
$output .= '<dt>' . $this->t('Choosing list field type') . '</dt>';
|
||||
$output .= '<dd>' . $this->t('There are three types of list fields, which store different types of data: <em>float</em>, <em>integer</em> or, <em>text</em>. The <em>float</em> type allows storing approximate decimal values. The <em>integer</em> type allows storing whole numbers, such as years (for example, 2012) or values (for example, 1, 2, 5, 305). The <em>text</em> list field type allows storing text values. No matter which type of list field you choose, you can define whatever labels you wish for data entry.') . '</dd>';
|
||||
$output .= '</dl>';
|
||||
return $output;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_update() for 'field_storage_config'.
|
||||
*/
|
||||
#[Hook('field_storage_config_update')]
|
||||
public function fieldStorageConfigUpdate(FieldStorageConfigInterface $field_storage): void {
|
||||
drupal_static_reset('options_allowed_values');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_delete() for 'field_storage_config'.
|
||||
*/
|
||||
#[Hook('field_storage_config_delete')]
|
||||
public function fieldStorageConfigDelete(FieldStorageConfigInterface $field_storage): void {
|
||||
drupal_static_reset('options_allowed_values');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_storage_config_update_forbid().
|
||||
*/
|
||||
#[Hook('field_storage_config_update_forbid')]
|
||||
public function fieldStorageConfigUpdateForbid(FieldStorageConfigInterface $field_storage, FieldStorageConfigInterface $prior_field_storage): void {
|
||||
if ($field_storage->getTypeProvider() == 'options' && $field_storage->hasData()) {
|
||||
// Forbid any update that removes allowed values with actual data.
|
||||
$allowed_values = $field_storage->getSetting('allowed_values');
|
||||
$prior_allowed_values = $prior_field_storage->getSetting('allowed_values');
|
||||
$lost_keys = array_keys(array_diff_key($prior_allowed_values, $allowed_values));
|
||||
if (_options_values_in_use($field_storage->getTargetEntityTypeId(), $field_storage->getName(), $lost_keys)) {
|
||||
throw new FieldStorageDefinitionUpdateForbiddenException("A list field '{$field_storage->getName()}' with existing data cannot have its keys changed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
41
web/core/modules/options/src/Hook/OptionsViewsHooks.php
Normal file
41
web/core/modules/options/src/Hook/OptionsViewsHooks.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\options\Hook;
|
||||
|
||||
use Drupal\field\FieldStorageConfigInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for options.
|
||||
*/
|
||||
class OptionsViewsHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_field_views_data().
|
||||
*
|
||||
* Views integration for list fields. Have a different filter handler and
|
||||
* argument handlers for list fields. This should allow to select values of
|
||||
* the list.
|
||||
*/
|
||||
#[Hook('field_views_data')]
|
||||
public function fieldViewsData(FieldStorageConfigInterface $field): array {
|
||||
$data = \Drupal::service('views.field_data_provider')->defaultFieldImplementation($field);
|
||||
foreach ($data as $table_name => $table_data) {
|
||||
foreach ($table_data as $field_name => $field_data) {
|
||||
if (isset($field_data['filter']) && $field_name != 'delta') {
|
||||
$data[$table_name][$field_name]['filter']['id'] = 'list_field';
|
||||
}
|
||||
if (isset($field_data['argument']) && $field_name != 'delta') {
|
||||
if ($field->getType() == 'list_string') {
|
||||
$data[$table_name][$field_name]['argument']['id'] = 'string_list_field';
|
||||
}
|
||||
else {
|
||||
$data[$table_name][$field_name]['argument']['id'] = 'number_list_field';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\options\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldFormatter;
|
||||
use Drupal\Core\Field\FieldFilteredMarkup;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Form\OptGroup;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'list_default' formatter.
|
||||
*/
|
||||
#[FieldFormatter(
|
||||
id: 'list_default',
|
||||
label: new TranslatableMarkup('Default'),
|
||||
field_types: [
|
||||
'list_integer',
|
||||
'list_float',
|
||||
'list_string',
|
||||
],
|
||||
)]
|
||||
class OptionsDefaultFormatter extends FormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
|
||||
// Only collect allowed options if there are actually items to display.
|
||||
if ($items->count()) {
|
||||
$provider = $items->getFieldDefinition()
|
||||
->getFieldStorageDefinition()
|
||||
->getOptionsProvider('value', $items->getEntity());
|
||||
// Flatten the possible options, to support opt groups.
|
||||
$options = OptGroup::flattenOptions($provider->getPossibleOptions());
|
||||
|
||||
foreach ($items as $delta => $item) {
|
||||
$value = $item->value;
|
||||
// If the stored value is in the current set of allowed values, display
|
||||
// the associated label, otherwise just display the raw value.
|
||||
$output = $options[$value] ?? $value;
|
||||
$elements[$delta] = [
|
||||
'#markup' => $output,
|
||||
'#allowed_tags' => FieldFilteredMarkup::allowedTags(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\options\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldFormatter;
|
||||
use Drupal\Core\Field\FieldFilteredMarkup;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'list_key' formatter.
|
||||
*/
|
||||
#[FieldFormatter(
|
||||
id: 'list_key',
|
||||
label: new TranslatableMarkup('Key'),
|
||||
field_types: [
|
||||
'list_integer',
|
||||
'list_float',
|
||||
'list_string',
|
||||
],
|
||||
)]
|
||||
class OptionsKeyFormatter extends FormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
|
||||
foreach ($items as $delta => $item) {
|
||||
$elements[$delta] = [
|
||||
'#markup' => $item->value,
|
||||
'#allowed_tags' => FieldFilteredMarkup::allowedTags(),
|
||||
];
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\options\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldType;
|
||||
use Drupal\Core\Field\FieldFilteredMarkup;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\TypedData\DataDefinition;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'list_float' field type.
|
||||
*/
|
||||
#[FieldType(
|
||||
id: "list_float",
|
||||
label: new TranslatableMarkup("List (float)"),
|
||||
description: [
|
||||
new TranslatableMarkup("Values stored are floating-point numbers"),
|
||||
new TranslatableMarkup("For example, 'Fraction': 0 => 0, .25 => 1/4, .75 => 3/4, 1 => 1"),
|
||||
],
|
||||
category: "selection_list",
|
||||
weight: -10,
|
||||
no_ui: TRUE,
|
||||
default_widget: "options_select",
|
||||
default_formatter: "list_default",
|
||||
)]
|
||||
class ListFloatItem extends ListItemBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
|
||||
$properties['value'] = DataDefinition::create('float')
|
||||
->setLabel(new TranslatableMarkup('Float value'))
|
||||
->setRequired(TRUE);
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function schema(FieldStorageDefinitionInterface $field_definition) {
|
||||
return [
|
||||
'columns' => [
|
||||
'value' => [
|
||||
'type' => 'float',
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
'value' => ['value'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function allowedValuesDescription() {
|
||||
$description = '<p>' . $this->t('The name will be used in displayed options and edit forms. The value is the stored value, and must be numeric.') . '</p>';
|
||||
$description .= '<p>' . $this->t('Allowed HTML tags in labels: @tags', ['@tags' => FieldFilteredMarkup::displayAllowedTags()]) . '</p>';
|
||||
return $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function extractAllowedValues($string, $has_data) {
|
||||
$values = parent::extractAllowedValues($string, $has_data);
|
||||
if ($values) {
|
||||
$keys = array_keys($values);
|
||||
$labels = array_values($values);
|
||||
$keys = array_map(function ($key) {
|
||||
// Float keys are represented as strings and need to be disambiguated
|
||||
// ('.5' is '0.5').
|
||||
return is_numeric($key) ? (string) (float) $key : $key;
|
||||
}, $keys);
|
||||
|
||||
return array_combine($keys, $labels);
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function validateAllowedValue($option) {
|
||||
if (!is_numeric($option)) {
|
||||
return new TranslatableMarkup('Allowed values list: each key must be a valid integer or decimal.');
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function simplifyAllowedValues(array $structured_values) {
|
||||
$values = [];
|
||||
foreach ($structured_values as $item) {
|
||||
// Nested elements are embedded in the label.
|
||||
if (is_array($item['label'])) {
|
||||
$item['label'] = static::simplifyAllowedValues($item['label']);
|
||||
}
|
||||
// Cast the value to a float first so that .5 and 0.5 are the same value
|
||||
// and then cast to a string so that values like 0.5 can be used as array
|
||||
// keys.
|
||||
// @see http://php.net/manual/language.types.array.php
|
||||
$values[(string) (float) $item['value']] = $item['label'];
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function castAllowedValue($value) {
|
||||
return (float) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
|
||||
$element = parent::storageSettingsForm($form, $form_state, $has_data);
|
||||
|
||||
foreach (Element::children($element['allowed_values']['table']) as $delta => $row) {
|
||||
// @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number
|
||||
// @see \Drupal\Core\Field\Plugin\Field\FieldWidget\NumberWidget::formElement()
|
||||
$element['allowed_values']['table'][$delta]['item']['key']['#step'] = 'any';
|
||||
$element['allowed_values']['table'][$delta]['item']['key']['#type'] = 'number';
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\options\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldType;
|
||||
use Drupal\Core\Field\FieldFilteredMarkup;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\TypedData\DataDefinition;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'list_integer' field type.
|
||||
*/
|
||||
#[FieldType(
|
||||
id: "list_integer",
|
||||
label: new TranslatableMarkup("List (integer)"),
|
||||
description: [
|
||||
new TranslatableMarkup("Values stored are numbers without decimals"),
|
||||
new TranslatableMarkup("For example, 'Lifetime in days': 1 => 1 day, 7 => 1 week, 31 => 1 month"),
|
||||
],
|
||||
category: "selection_list",
|
||||
weight: -30,
|
||||
default_widget: "options_select",
|
||||
default_formatter: "list_default",
|
||||
)]
|
||||
class ListIntegerItem extends ListItemBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
|
||||
$properties['value'] = DataDefinition::create('integer')
|
||||
->setLabel(new TranslatableMarkup('Integer value'))
|
||||
->setRequired(TRUE);
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function schema(FieldStorageDefinitionInterface $field_definition) {
|
||||
return [
|
||||
'columns' => [
|
||||
'value' => [
|
||||
'type' => 'int',
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
'value' => ['value'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function allowedValuesDescription() {
|
||||
$description = '<p>' . $this->t('The name will be used in displayed options and edit forms. The value is the stored value, and must be numeric.') . '</p>';
|
||||
$description .= '<p>' . $this->t('Allowed HTML tags in labels: @tags', ['@tags' => FieldFilteredMarkup::displayAllowedTags()]) . '</p>';
|
||||
return $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function validateAllowedValue($option) {
|
||||
if (!preg_match('/^-?\d+$/', $option)) {
|
||||
return new TranslatableMarkup('Allowed values list: keys must be integers.');
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function castAllowedValue($value) {
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
|
||||
$element = parent::storageSettingsForm($form, $form_state, $has_data);
|
||||
|
||||
foreach (Element::children($element['allowed_values']['table']) as $delta => $row) {
|
||||
// @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number
|
||||
// @see \Drupal\Core\Field\Plugin\Field\FieldWidget\NumberWidget::formElement()
|
||||
$element['allowed_values']['table'][$delta]['item']['key']['#type'] = 'number';
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,576 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\options\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Ajax\FocusFirstCommand;
|
||||
use Drupal\Core\Ajax\InsertCommand;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\OptGroup;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\TypedData\OptionsProviderInterface;
|
||||
|
||||
/**
|
||||
* Plugin base class inherited by the options field types.
|
||||
*/
|
||||
abstract class ListItemBase extends FieldItemBase implements OptionsProviderInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultStorageSettings() {
|
||||
return [
|
||||
'allowed_values' => [],
|
||||
'allowed_values_function' => '',
|
||||
] + parent::defaultStorageSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPossibleValues(?AccountInterface $account = NULL) {
|
||||
// Flatten options firstly, because Possible Options may contain group
|
||||
// arrays.
|
||||
$flatten_options = OptGroup::flattenOptions($this->getPossibleOptions($account));
|
||||
return array_keys($flatten_options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPossibleOptions(?AccountInterface $account = NULL) {
|
||||
return $this->getSettableOptions($account);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSettableValues(?AccountInterface $account = NULL) {
|
||||
// Flatten options firstly, because Settable Options may contain group
|
||||
// arrays.
|
||||
$flatten_options = OptGroup::flattenOptions($this->getSettableOptions($account));
|
||||
return array_keys($flatten_options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSettableOptions(?AccountInterface $account = NULL) {
|
||||
$allowed_options = options_allowed_values($this->getFieldDefinition()->getFieldStorageDefinition(), $this->getEntity());
|
||||
return $allowed_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
|
||||
$allowed_options = options_allowed_values($field_definition->getFieldStorageDefinition());
|
||||
if (empty($allowed_options)) {
|
||||
$values['value'] = NULL;
|
||||
return $values;
|
||||
}
|
||||
$values['value'] = array_rand($allowed_options);
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEmpty() {
|
||||
$value = $this->get('value')->getValue();
|
||||
|
||||
return empty($value) && (string) $value !== '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
|
||||
if (!array_key_exists('allowed_values', $form_state->getStorage())) {
|
||||
$form_state->set('allowed_values', $this->getFieldDefinition()->getSetting('allowed_values'));
|
||||
}
|
||||
$form['field_storage_submit']['#submit'][] = [static::class, 'submitFieldStorageUpdate'];
|
||||
$form['field_storage_submit']['#limit_validation_errors'] = [];
|
||||
|
||||
$allowed_values = $form_state->getStorage()['allowed_values'];
|
||||
$allowed_values_function = $this->getSetting('allowed_values_function');
|
||||
|
||||
if (!$form_state->get('items_count')) {
|
||||
$form_state->set('items_count', max(count($allowed_values), 0));
|
||||
}
|
||||
|
||||
$wrapper_id = Html::getUniqueId('allowed-values-wrapper');
|
||||
$element['allowed_values'] = [
|
||||
'#element_validate' => [[static::class, 'validateAllowedValues']],
|
||||
'#field_has_data' => $has_data,
|
||||
'#allowed_values' => $allowed_values,
|
||||
'#required' => TRUE,
|
||||
'#prefix' => '<div id="' . $wrapper_id . '">',
|
||||
'#suffix' => '</div>',
|
||||
'#access' => empty($allowed_values_function),
|
||||
'help_text' => ['#markup' => $this->allowedValuesDescription()],
|
||||
];
|
||||
$element['allowed_values']['table'] = [
|
||||
'#type' => 'table',
|
||||
'#header' => [
|
||||
$this->t('Allowed values'),
|
||||
$this->t('Delete'),
|
||||
$this->t('Weight'),
|
||||
],
|
||||
'#attributes' => [
|
||||
'id' => 'allowed-values-order',
|
||||
'data-field-list-table' => TRUE,
|
||||
'class' => ['allowed-values-table'],
|
||||
],
|
||||
'#tabledrag' => [
|
||||
[
|
||||
'action' => 'order',
|
||||
'relationship' => 'sibling',
|
||||
'group' => 'weight',
|
||||
],
|
||||
],
|
||||
'#attached' => [
|
||||
'library' => [
|
||||
'core/drupal.fieldListKeyboardNavigation',
|
||||
'field_ui/drupal.field_ui',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$max = $form_state->get('items_count');
|
||||
$entity_type_id = $this->getFieldDefinition()->getTargetEntityTypeId();
|
||||
$field_name = $this->getFieldDefinition()->getName();
|
||||
$current_keys = array_keys($allowed_values);
|
||||
for ($delta = 0; $delta <= $max; $delta++) {
|
||||
$element['allowed_values']['table'][$delta] = [
|
||||
'#attributes' => [
|
||||
'class' => ['draggable'],
|
||||
],
|
||||
'#weight' => $delta,
|
||||
];
|
||||
$element['allowed_values']['table'][$delta]['item'] = [
|
||||
'label' => [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Name'),
|
||||
'#weight' => -30,
|
||||
'#default_value' => isset($current_keys[$delta]) ? $allowed_values[$current_keys[$delta]] : '',
|
||||
'#required' => $delta === 0,
|
||||
],
|
||||
'key' => [
|
||||
'#type' => 'textfield',
|
||||
'#maxlength' => 255,
|
||||
'#title' => $this->t('Value'),
|
||||
'#default_value' => $current_keys[$delta] ?? '',
|
||||
'#weight' => -20,
|
||||
'#required' => $delta === 0,
|
||||
],
|
||||
];
|
||||
$element['allowed_values']['table'][$delta]['delete'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Remove'),
|
||||
'#name' => "remove_row_button__$delta",
|
||||
'#id' => "remove_row_button__$delta",
|
||||
'#delta' => $delta,
|
||||
'#submit' => [[static::class, 'deleteSubmit']],
|
||||
'#limit_validation_errors' => [],
|
||||
'#ajax' => [
|
||||
'callback' => [static::class, 'deleteAjax'],
|
||||
'wrapper' => $wrapper_id,
|
||||
'effect' => 'fade',
|
||||
],
|
||||
];
|
||||
$element['allowed_values']['table'][$delta]['weight'] = [
|
||||
'#type' => 'weight',
|
||||
'#title' => $this->t('Weight for row @number', ['@number' => $delta + 1]),
|
||||
'#title_display' => 'invisible',
|
||||
'#delta' => 50,
|
||||
'#default_value' => 0,
|
||||
'#attributes' => ['class' => ['weight']],
|
||||
];
|
||||
// Disable the remove button if there is only one row in the table.
|
||||
if ($max === 0) {
|
||||
$element['allowed_values']['table'][0]['delete']['#attributes']['disabled'] = 'disabled';
|
||||
}
|
||||
if ($delta < count($allowed_values)) {
|
||||
$query = \Drupal::entityQuery($entity_type_id)
|
||||
->accessCheck(FALSE)
|
||||
->condition($field_name, $current_keys[$delta]);
|
||||
$entity_ids = $query->execute();
|
||||
if (!empty($entity_ids)) {
|
||||
$element['allowed_values']['table'][$delta]['item']['key']['#attributes']['disabled'] = 'disabled';
|
||||
$element['allowed_values']['table'][$delta]['delete']['#attributes']['disabled'] = 'disabled';
|
||||
$element['allowed_values']['table'][$delta]['delete'] += [
|
||||
'message' => [
|
||||
'#type' => 'item',
|
||||
'#markup' => $this->t('Cannot be removed: option in use.'),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
$element['allowed_values']['table']['#max_delta'] = $max;
|
||||
$element['allowed_values']['add_more_allowed_values'] = [
|
||||
'#type' => 'submit',
|
||||
'#name' => 'add_more_allowed_values',
|
||||
'#value' => $this->t('Add another item'),
|
||||
'#attributes' => [
|
||||
'class' => ['field-add-more-submit'],
|
||||
'data-field-list-button' => TRUE,
|
||||
],
|
||||
// Allow users to add another row without requiring existing rows to have
|
||||
// values.
|
||||
'#limit_validation_errors' => [],
|
||||
'#submit' => [[static::class, 'addMoreSubmit']],
|
||||
'#ajax' => [
|
||||
'callback' => [static::class, 'addMoreAjax'],
|
||||
'wrapper' => $wrapper_id,
|
||||
'effect' => 'fade',
|
||||
'progress' => [
|
||||
'type' => 'throbber',
|
||||
'message' => $this->t('Adding a new item...'),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$element['allowed_values_function'] = [
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('Allowed values list'),
|
||||
'#markup' => $this->t('The value of this field is being determined by the %function function and may not be changed.', ['%function' => $allowed_values_function]),
|
||||
'#access' => !empty($allowed_values_function),
|
||||
'#value' => $allowed_values_function,
|
||||
];
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new option.
|
||||
*
|
||||
* @param array $form
|
||||
* The form array to add elements to.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
public static function addMoreSubmit(array $form, FormStateInterface $form_state) {
|
||||
$form_state->set('items_count', $form_state->get('items_count') + 1);
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax callback for the "Add another item" button.
|
||||
*/
|
||||
public static function addMoreAjax(array $form, FormStateInterface $form_state) {
|
||||
$button = $form_state->getTriggeringElement();
|
||||
|
||||
// Go one level up in the form.
|
||||
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
|
||||
$delta = $element['table']['#max_delta'];
|
||||
$element['table'][$delta]['item']['#prefix'] = '<div class="ajax-new-content" data-drupal-selector="field-list-add-more-focus-target">' . ($element['table'][$delta]['item']['#prefix'] ?? '');
|
||||
$element['table'][$delta]['item']['#suffix'] = ($element['table'][$delta]['item']['#suffix'] ?? '') . '</div>';
|
||||
// Enable the remove button for the first row if there are more rows.
|
||||
if ($delta > 0 && isset($element['table'][0]['delete']['#attributes']['disabled']) && !isset($element['table'][0]['item']['key']['#attributes']['disabled'])) {
|
||||
unset($element['table'][0]['delete']['#attributes']['disabled']);
|
||||
}
|
||||
|
||||
$response = new AjaxResponse();
|
||||
$response->addCommand(new InsertCommand(NULL, $element));
|
||||
$response->addCommand(new FocusFirstCommand('[data-drupal-selector="field-list-add-more-focus-target"]'));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a row/option.
|
||||
*
|
||||
* @param array $form
|
||||
* The form array to add elements to.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
public static function deleteSubmit(array $form, FormStateInterface $form_state) {
|
||||
$allowed_values = $form_state->getStorage()['allowed_values'];
|
||||
$button = $form_state->getTriggeringElement();
|
||||
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
|
||||
$item_to_be_removed = $element['item']['label']['#default_value'];
|
||||
$remaining_allowed_values = array_diff($allowed_values, [$item_to_be_removed]);
|
||||
$form_state->set('allowed_values', $remaining_allowed_values);
|
||||
|
||||
// The user input is directly modified to preserve the rest of the data on
|
||||
// the page as it cannot be rebuilt from a fresh form state.
|
||||
$user_input = $form_state->getUserInput();
|
||||
NestedArray::unsetValue($user_input, $element['#parents']);
|
||||
|
||||
// Reset the keys in the array.
|
||||
$table_parents = $element['#parents'];
|
||||
array_pop($table_parents);
|
||||
$new_values = array_values(NestedArray::getValue($user_input, $table_parents));
|
||||
NestedArray::setValue($user_input, $table_parents, $new_values);
|
||||
|
||||
$form_state->setUserInput($user_input);
|
||||
$form_state->set('items_count', $form_state->get('items_count') - 1);
|
||||
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax callback for per row delete button.
|
||||
*/
|
||||
public static function deleteAjax(array $form, FormStateInterface $form_state) {
|
||||
$button = $form_state->getTriggeringElement();
|
||||
|
||||
return NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -3));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the field type specific allowed values form element #description.
|
||||
*
|
||||
* @return string
|
||||
* The field type allowed values form specific description.
|
||||
*/
|
||||
abstract protected function allowedValuesDescription();
|
||||
|
||||
/**
|
||||
* Render API callback: Validates the allowed values of an options field.
|
||||
*
|
||||
* This function is assigned as a #element_validate callback.
|
||||
*
|
||||
* @param array $element
|
||||
* An associative array containing the properties and children of the
|
||||
* generic form element.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form for the form this element belongs to.
|
||||
*
|
||||
* @see \Drupal\Core\Render\Element\FormElementBase::processPattern()
|
||||
*/
|
||||
public static function validateAllowedValues($element, FormStateInterface $form_state) {
|
||||
$items = array_filter(array_map(function ($item) use ($element) {
|
||||
$current_element = $element['table'][$item];
|
||||
$key_has_input = isset($current_element['item']['key']['#value']) && $current_element['item']['key']['#value'] !== '';
|
||||
$label_has_input = isset($current_element['item']['label']['#value']) && $current_element['item']['label']['#value'] !== '';
|
||||
if ($key_has_input) {
|
||||
if ($label_has_input) {
|
||||
return $current_element['item']['key']['#value'] . '|' . $current_element['item']['label']['#value'];
|
||||
}
|
||||
|
||||
return $current_element['item']['key']['#value'];
|
||||
}
|
||||
|
||||
if ($label_has_input) {
|
||||
return $current_element['item']['label']['#value'];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}, Element::children($element['table'])), function ($item) {
|
||||
return $item;
|
||||
});
|
||||
if ($reordered_items = $form_state->getValue([...$element['#parents'], 'table'])) {
|
||||
uksort($items, function ($a, $b) use ($reordered_items) {
|
||||
$a_weight = $reordered_items[$a]['weight'] ?? 0;
|
||||
$b_weight = $reordered_items[$b]['weight'] ?? 0;
|
||||
return $a_weight <=> $b_weight;
|
||||
});
|
||||
}
|
||||
$values = static::extractAllowedValues($items, $element['#field_has_data']);
|
||||
|
||||
if (!is_array($values)) {
|
||||
$form_state->setError($element, new TranslatableMarkup('Allowed values list: invalid input.'));
|
||||
}
|
||||
else {
|
||||
// Check that keys are valid for the field type.
|
||||
foreach ($values as $key => $value) {
|
||||
if ($error = static::validateAllowedValue($key)) {
|
||||
$form_state->setError($element, $error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$form_state->setValueForElement($element, $values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the allowed values array from the allowed_values element.
|
||||
*
|
||||
* @param array $list
|
||||
* The raw string or array to extract values from.
|
||||
* @param bool $has_data
|
||||
* The current field already has data inserted or not.
|
||||
*
|
||||
* @return array|null
|
||||
* The array of extracted key/value pairs, or NULL if the string is invalid.
|
||||
*
|
||||
* @see \Drupal\options\Plugin\Field\FieldType\ListItemBase::allowedValuesString()
|
||||
*/
|
||||
protected static function extractAllowedValues(array $list, bool $has_data) {
|
||||
$values = [];
|
||||
|
||||
$generated_keys = $explicit_keys = FALSE;
|
||||
foreach ($list as $position => $text) {
|
||||
// Check for an explicit key.
|
||||
$matches = [];
|
||||
if (preg_match('/(.*)\|(.*)/', $text, $matches)) {
|
||||
// Trim key and value to avoid unwanted spaces issues.
|
||||
$key = trim($matches[1]);
|
||||
$value = trim($matches[2]);
|
||||
$explicit_keys = TRUE;
|
||||
}
|
||||
// Otherwise see if we can use the value as the key.
|
||||
elseif (!static::validateAllowedValue($text)) {
|
||||
$key = $value = $text;
|
||||
$explicit_keys = TRUE;
|
||||
}
|
||||
// Otherwise see if we can generate a key from the position.
|
||||
elseif (!$has_data) {
|
||||
$key = (string) $position;
|
||||
$value = $text;
|
||||
$generated_keys = TRUE;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
$values[$key] = $value;
|
||||
}
|
||||
|
||||
// We generate keys only if the list contains no explicit key at all.
|
||||
if ($explicit_keys && $generated_keys) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a candidate allowed value is valid.
|
||||
*
|
||||
* @param string $option
|
||||
* The option value entered by the user.
|
||||
*
|
||||
* @return \Drupal\Core\StringTranslation\TranslatableMarkup|string|null
|
||||
* The error message if the specified value is invalid, NULL otherwise.
|
||||
*/
|
||||
protected static function validateAllowedValue($option) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string representation of an array of 'allowed values'.
|
||||
*
|
||||
* This string format is suitable for edition in a textarea.
|
||||
*
|
||||
* @param array $values
|
||||
* An array of values, where array keys are values and array values are
|
||||
* labels.
|
||||
*
|
||||
* @return string
|
||||
* The string representation of the $values array:
|
||||
* - Values are separated by a carriage return.
|
||||
* - Each value is in the format "value|label" or "value".
|
||||
*/
|
||||
protected function allowedValuesString($values) {
|
||||
$lines = [];
|
||||
foreach ($values as $key => $value) {
|
||||
$lines[] = "$key|$value";
|
||||
}
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function storageSettingsToConfigData(array $settings) {
|
||||
if (isset($settings['allowed_values'])) {
|
||||
$settings['allowed_values'] = static::structureAllowedValues($settings['allowed_values']);
|
||||
}
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function storageSettingsFromConfigData(array $settings) {
|
||||
if (isset($settings['allowed_values'])) {
|
||||
$settings['allowed_values'] = static::simplifyAllowedValues($settings['allowed_values']);
|
||||
}
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplifies allowed values to a key-value array from the structured array.
|
||||
*
|
||||
* @param array $structured_values
|
||||
* Array of items with a 'value' and 'label' key each for the allowed
|
||||
* values.
|
||||
*
|
||||
* @return array
|
||||
* Allowed values were the array key is the 'value' value, the value is
|
||||
* the 'label' value.
|
||||
*
|
||||
* @see \Drupal\options\Plugin\Field\FieldType\ListItemBase::structureAllowedValues()
|
||||
*/
|
||||
protected static function simplifyAllowedValues(array $structured_values) {
|
||||
$values = [];
|
||||
foreach ($structured_values as $item) {
|
||||
if (is_array($item['label'])) {
|
||||
// Nested elements are embedded in the label.
|
||||
$item['label'] = static::simplifyAllowedValues($item['label']);
|
||||
}
|
||||
$values[$item['value']] = $item['label'];
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a structured array of allowed values from a key-value array.
|
||||
*
|
||||
* @param array $values
|
||||
* Allowed values were the array key is the 'value' value, the value is
|
||||
* the 'label' value.
|
||||
*
|
||||
* @return array
|
||||
* Array of items with a 'value' and 'label' key each for the allowed
|
||||
* values.
|
||||
*
|
||||
* @see \Drupal\options\Plugin\Field\FieldType\ListItemBase::simplifyAllowedValues()
|
||||
*/
|
||||
protected static function structureAllowedValues(array $values) {
|
||||
$structured_values = [];
|
||||
foreach ($values as $value => $label) {
|
||||
if (is_array($label)) {
|
||||
$label = static::structureAllowedValues($label);
|
||||
}
|
||||
$structured_values[] = [
|
||||
'value' => static::castAllowedValue($value),
|
||||
'label' => $label,
|
||||
];
|
||||
}
|
||||
return $structured_values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a value to the correct type.
|
||||
*
|
||||
* @param mixed $value
|
||||
* The value to cast.
|
||||
*
|
||||
* @return mixed
|
||||
* The casted value.
|
||||
*/
|
||||
protected static function castAllowedValue($value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the static variable on field storage update.
|
||||
*/
|
||||
public static function submitFieldStorageUpdate() {
|
||||
drupal_static_reset('options_allowed_values');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\options\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldType;
|
||||
use Drupal\Core\Field\FieldFilteredMarkup;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\TypedData\DataDefinition;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'list_string' field type.
|
||||
*/
|
||||
#[FieldType(
|
||||
id: "list_string",
|
||||
label: new TranslatableMarkup("List (text)"),
|
||||
description: [
|
||||
new TranslatableMarkup("Values stored are text values"),
|
||||
new TranslatableMarkup("For example, 'US States': IL => Illinois, IA => Iowa, IN => Indiana"),
|
||||
],
|
||||
category: "selection_list",
|
||||
weight: -50,
|
||||
default_widget: "options_select",
|
||||
default_formatter: "list_default",
|
||||
)]
|
||||
class ListStringItem extends ListItemBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
|
||||
$properties['value'] = DataDefinition::create('string')
|
||||
->setLabel(new TranslatableMarkup('Text value'))
|
||||
->addConstraint('Length', ['max' => 255])
|
||||
->setRequired(TRUE);
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function schema(FieldStorageDefinitionInterface $field_definition) {
|
||||
return [
|
||||
'columns' => [
|
||||
'value' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
'value' => ['value'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function allowedValuesDescription() {
|
||||
$description = '<p>' . $this->t('The name will be used in displayed options and edit forms.');
|
||||
$description .= '<br/>' . $this->t('The value is automatically generated machine name of the name provided and will be the stored value.');
|
||||
$description .= '</p>';
|
||||
$description .= '<p>' . $this->t('Allowed HTML tags in labels: @tags', ['@tags' => FieldFilteredMarkup::displayAllowedTags()]) . '</p>';
|
||||
return $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function validateAllowedValue($option) {
|
||||
if (mb_strlen($option) > 255) {
|
||||
return new TranslatableMarkup('Allowed values list: each key must be a string at most 255 characters long.');
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function castAllowedValue($value) {
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
|
||||
$element = parent::storageSettingsForm($form, $form_state, $has_data);
|
||||
|
||||
// Improve user experience by using an automatically generated machine name.
|
||||
foreach (Element::children($element['allowed_values']['table']) as $delta => $row) {
|
||||
$element['allowed_values']['table'][$delta]['item']['key']['#type'] = 'machine_name';
|
||||
// ListItemBase::storageSettingsForm() will set the default value to an
|
||||
// integer if the key is a decimal integer string, so cast it back here.
|
||||
$element['allowed_values']['table'][$delta]['item']['key']['#default_value'] = (string) $element['allowed_values']['table'][$delta]['item']['key']['#default_value'];
|
||||
$element['allowed_values']['table'][$delta]['item']['key']['#machine_name'] = [
|
||||
'exists' => [static::class, 'exists'],
|
||||
];
|
||||
$element['allowed_values']['table'][$delta]['item']['key']['#process'] = array_merge(
|
||||
[[static::class, 'processAllowedValuesKey']],
|
||||
// Workaround for https://drupal.org/i/1300290#comment-12873635.
|
||||
\Drupal::service('plugin.manager.element_info')->getInfoProperty('machine_name', '#process', []),
|
||||
);
|
||||
// Remove #element_validate from the machine name so that any value can be
|
||||
// used as a key, while keeping the widget's behavior for generating
|
||||
// defaults the same.
|
||||
$element['allowed_values']['table'][$delta]['item']['key']['#element_validate'] = [];
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the machine name source to be the label.
|
||||
*/
|
||||
public static function processAllowedValuesKey(array &$element): array {
|
||||
$parents = $element['#parents'];
|
||||
array_pop($parents);
|
||||
$parents[] = 'label';
|
||||
$element['#machine_name']['source'] = $parents;
|
||||
|
||||
// Override the default description which is not applicable to this use of
|
||||
// the machine name element given that it allows users to manually enter
|
||||
// characters usually not allowed in machine names.
|
||||
if (!isset($element['#description'])) {
|
||||
$element['#description'] = '';
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for existing keys for allowed values.
|
||||
*/
|
||||
public static function exists(): bool {
|
||||
// Without access to the current form state, we cannot know if a given key
|
||||
// is in use. Return FALSE in all cases.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\options\Plugin\migrate\field\d6;
|
||||
|
||||
use Drupal\migrate_drupal\Attribute\MigrateField;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
|
||||
|
||||
// cspell:ignore optionwidgets
|
||||
/**
|
||||
* MigrateField Plugin for Drupal 6 options fields.
|
||||
*/
|
||||
#[MigrateField(
|
||||
id: 'optionwidgets',
|
||||
core: [6],
|
||||
source_module: 'optionwidgets',
|
||||
destination_module: 'options',
|
||||
)]
|
||||
class OptionWidgetsField extends FieldPluginBase {}
|
||||
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\options\Plugin\migrate\field\d7;
|
||||
|
||||
use Drupal\migrate_drupal\Attribute\MigrateField;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
|
||||
|
||||
/**
|
||||
* Migrate field plugin for Drupal 7 list fields.
|
||||
*/
|
||||
#[MigrateField(
|
||||
id: 'list',
|
||||
core: [7],
|
||||
type_map: [
|
||||
'list_boolean' => 'boolean',
|
||||
'list_integer' => 'list_integer',
|
||||
'list_text' => 'list_string',
|
||||
'list_float' => 'list_float',
|
||||
],
|
||||
source_module: 'list',
|
||||
destination_module: 'options',
|
||||
)]
|
||||
class ListField extends FieldPluginBase {}
|
||||
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\options\Plugin\migrate\field\d7;
|
||||
|
||||
use Drupal\migrate_drupal\Attribute\MigrateField;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
|
||||
|
||||
/**
|
||||
* Migrate field plugin for Drupal 7 options fields.
|
||||
*/
|
||||
#[MigrateField(
|
||||
id: 'options',
|
||||
core: [7],
|
||||
source_module: 'options',
|
||||
destination_module: 'options',
|
||||
)]
|
||||
class OptionsField extends FieldPluginBase {}
|
||||
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\options\Plugin\views\argument;
|
||||
|
||||
use Drupal\Core\Field\FieldFilteredMarkup;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\views\Attribute\ViewsArgument;
|
||||
use Drupal\views\FieldAPIHandlerTrait;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Plugin\views\argument\NumericArgument;
|
||||
|
||||
/**
|
||||
* Argument handler for list field to show human readable name in the summary.
|
||||
*
|
||||
* @ingroup views_argument_handlers
|
||||
*/
|
||||
#[ViewsArgument(
|
||||
id: 'number_list_field',
|
||||
)]
|
||||
class NumberListField extends NumericArgument {
|
||||
|
||||
use FieldAPIHandlerTrait;
|
||||
|
||||
/**
|
||||
* Stores the allowed values of this field.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $allowedValues = NULL;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$options = NULL) {
|
||||
parent::init($view, $display, $options);
|
||||
|
||||
$field_storage = $this->getFieldStorageDefinition();
|
||||
$this->allowedValues = options_allowed_values($field_storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
$options['summary']['contains']['human'] = ['default' => FALSE];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
|
||||
$form['summary']['human'] = [
|
||||
'#title' => $this->t('Display list value as human readable'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => $this->options['summary']['human'],
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="options[default_action]"]' => ['value' => 'summary'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function summaryName($data) {
|
||||
$value = $data->{$this->name_alias};
|
||||
// If the list element has a human readable name show it.
|
||||
if (isset($this->allowedValues[$value]) && !empty($this->options['summary']['human'])) {
|
||||
return FieldFilteredMarkup::create($this->allowedValues[$value]);
|
||||
}
|
||||
// Else, fallback to the key.
|
||||
else {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\options\Plugin\views\argument;
|
||||
|
||||
use Drupal\Core\Field\FieldFilteredMarkup;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\views\Attribute\ViewsArgument;
|
||||
use Drupal\views\FieldAPIHandlerTrait;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Plugin\views\argument\StringArgument;
|
||||
|
||||
/**
|
||||
* Argument handler for list field to show the human readable name in summary.
|
||||
*
|
||||
* @ingroup views_argument_handlers
|
||||
*/
|
||||
#[ViewsArgument(
|
||||
id: 'string_list_field',
|
||||
)]
|
||||
class StringListField extends StringArgument {
|
||||
|
||||
use FieldAPIHandlerTrait;
|
||||
|
||||
/**
|
||||
* Stores the allowed values of this field.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $allowedValues = NULL;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$options = NULL) {
|
||||
parent::init($view, $display, $options);
|
||||
|
||||
$field_storage = $this->getFieldStorageDefinition();
|
||||
$this->allowedValues = options_allowed_values($field_storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
|
||||
$options['summary']['contains']['human'] = ['default' => FALSE];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
|
||||
$form['summary']['human'] = [
|
||||
'#title' => $this->t('Display list value as human readable'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => $this->options['summary']['human'],
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="options[default_action]"]' => ['value' => 'summary'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function summaryName($data) {
|
||||
$value = $data->{$this->name_alias};
|
||||
// If the list element has a human readable name show it.
|
||||
if (isset($this->allowedValues[$value]) && !empty($this->options['summary']['human'])) {
|
||||
$value = $this->allowedValues[$value];
|
||||
}
|
||||
return FieldFilteredMarkup::create($this->caseTransform($value, $this->options['case']));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\options\Plugin\views\filter;
|
||||
|
||||
use Drupal\views\Attribute\ViewsFilter;
|
||||
use Drupal\views\FieldAPIHandlerTrait;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Plugin\views\filter\ManyToOne;
|
||||
use Drupal\views\ViewExecutable;
|
||||
|
||||
/**
|
||||
* Filter handler which uses list-fields as options.
|
||||
*
|
||||
* @ingroup views_filter_handlers
|
||||
*/
|
||||
#[ViewsFilter("list_field")]
|
||||
class ListField extends ManyToOne {
|
||||
|
||||
use FieldAPIHandlerTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$options = NULL) {
|
||||
parent::init($view, $display, $options);
|
||||
|
||||
$field_storage = $this->getFieldStorageDefinition();
|
||||
// Set valueOptions here so getValueOptions() will just return it.
|
||||
$this->valueOptions = options_allowed_values($field_storage);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user