Initial Drupal 11 with DDEV setup
This commit is contained in:
151
web/core/modules/options/config/schema/options.schema.yml
Normal file
151
web/core/modules/options/config/schema/options.schema.yml
Normal file
@ -0,0 +1,151 @@
|
||||
# Schema for the configuration files of the Options module.
|
||||
|
||||
# This field type has no field instance settings, so no specific config schema type.
|
||||
# @see `type: field.field_settings.*`
|
||||
field.storage_settings.list_integer:
|
||||
type: mapping
|
||||
label: 'List (integer) settings'
|
||||
mapping:
|
||||
allowed_values:
|
||||
type: sequence
|
||||
label: 'Allowed values list'
|
||||
sequence:
|
||||
type: mapping
|
||||
label: 'Allowed value with label'
|
||||
mapping:
|
||||
value:
|
||||
type: integer
|
||||
label: 'Value'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
allowed_values_function:
|
||||
type: string
|
||||
label: 'Allowed values function'
|
||||
|
||||
field.value.list_integer:
|
||||
type: mapping
|
||||
label: 'Default value'
|
||||
mapping:
|
||||
value:
|
||||
type: integer
|
||||
label: 'Value'
|
||||
|
||||
# This field type has no field instance settings, so no specific config schema type.
|
||||
# @see `type: field.field_settings.*`
|
||||
field.storage_settings.list_float:
|
||||
type: mapping
|
||||
label: 'List (float) settings'
|
||||
mapping:
|
||||
allowed_values:
|
||||
type: sequence
|
||||
label: 'Allowed values list'
|
||||
sequence:
|
||||
type: mapping
|
||||
label: 'Allowed value with label'
|
||||
mapping:
|
||||
value:
|
||||
type: float
|
||||
label: 'Value'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
allowed_values_function:
|
||||
type: string
|
||||
label: 'Allowed values function'
|
||||
|
||||
field.value.list_float:
|
||||
type: mapping
|
||||
label: 'Default value'
|
||||
mapping:
|
||||
value:
|
||||
type: string
|
||||
label: 'Value'
|
||||
|
||||
# This field type has no field instance settings, so no specific config schema type.
|
||||
# @see `type: field.field_settings.*`
|
||||
field.storage_settings.list_string:
|
||||
type: mapping
|
||||
label: 'List (text) settings'
|
||||
mapping:
|
||||
allowed_values:
|
||||
type: sequence
|
||||
label: 'Allowed values list'
|
||||
sequence:
|
||||
type: mapping
|
||||
label: 'Allowed value with label'
|
||||
mapping:
|
||||
value:
|
||||
type: string
|
||||
label: 'Value'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
allowed_values_function:
|
||||
type: string
|
||||
label: 'Allowed values function'
|
||||
|
||||
field.value.list_string:
|
||||
type: mapping
|
||||
label: 'Default value'
|
||||
mapping:
|
||||
value:
|
||||
type: string
|
||||
label: 'Value'
|
||||
|
||||
field.formatter.settings.list_default:
|
||||
type: mapping
|
||||
label: 'Options list default display settings'
|
||||
|
||||
field.formatter.settings.list_key:
|
||||
type: mapping
|
||||
label: 'Key format settings'
|
||||
|
||||
field.widget.settings.options_buttons:
|
||||
type: mapping
|
||||
label: 'Check boxes/radio buttons format settings'
|
||||
|
||||
field.widget.settings.options_select:
|
||||
type: mapping
|
||||
label: 'Select list format settings'
|
||||
|
||||
views.argument.number_list_field:
|
||||
type: views.argument.numeric
|
||||
mapping:
|
||||
summary:
|
||||
type: mapping
|
||||
label: 'Display a summary'
|
||||
mapping:
|
||||
sort_order:
|
||||
type: string
|
||||
label: 'Sort order'
|
||||
number_of_records:
|
||||
type: integer
|
||||
label: 'Sort by'
|
||||
format:
|
||||
type: string
|
||||
label: 'Format'
|
||||
human:
|
||||
type: boolean
|
||||
|
||||
views.argument.string_list_field:
|
||||
type: views.argument.string
|
||||
mapping:
|
||||
summary:
|
||||
type: mapping
|
||||
label: 'Display a summary'
|
||||
mapping:
|
||||
sort_order:
|
||||
type: string
|
||||
label: 'Sort order'
|
||||
number_of_records:
|
||||
type: integer
|
||||
label: 'Sort by'
|
||||
format:
|
||||
type: string
|
||||
label: 'Format'
|
||||
human:
|
||||
type: boolean
|
||||
|
||||
views.filter.list_field:
|
||||
type: views.filter.many_to_one
|
||||
9
web/core/modules/options/css/options.icon.theme.css
Normal file
9
web/core/modules/options/css/options.icon.theme.css
Normal file
@ -0,0 +1,9 @@
|
||||
/*
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/3084859
|
||||
* @preserve
|
||||
*/
|
||||
.field-icon-selection_list {
|
||||
background-image: url("data:image/svg+xml,%3csvg height='36' viewBox='0 0 36 36' width='36' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m4.98 9.51v2.49h5.04v-4.98h-5.04zm7.02 0v1.47h19.98v-2.94h-19.98zm-7.02 9v2.49h5.04v-4.98h-5.04zm7.02-.03v1.5h19.98v-3h-19.98zm-7.02 9.03v2.49h5.04v-4.98h-5.04zm7.02 0v1.47h19.98v-2.94h-19.98z' fill='%2355565b'/%3e%3c/svg%3e");
|
||||
}
|
||||
3
web/core/modules/options/css/options.icon.theme.pcss.css
Normal file
3
web/core/modules/options/css/options.icon.theme.pcss.css
Normal file
@ -0,0 +1,3 @@
|
||||
.field-icon-selection_list {
|
||||
background-image: url(../../../misc/icons/55565b/selection_list.svg);
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
# cspell:ignore optionwidgets
|
||||
finished:
|
||||
6:
|
||||
optionwidgets: options
|
||||
7:
|
||||
options: options
|
||||
list: options
|
||||
103
web/core/modules/options/options.api.php
Normal file
103
web/core/modules/options/options.api.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks provided by the Options module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Alters the list of options to be displayed for a field.
|
||||
*
|
||||
* This hook can notably be used to change the label of the empty option.
|
||||
*
|
||||
* @param array $options
|
||||
* The array of options for the field, as returned by
|
||||
* \Drupal\Core\TypedData\OptionsProviderInterface::getSettableOptions(). An
|
||||
* empty option (_none) might have been added, depending on the field
|
||||
* properties.
|
||||
* @param array $context
|
||||
* An associative array containing:
|
||||
* - fieldDefinition: The field definition
|
||||
* (\Drupal\Core\Field\FieldDefinitionInterface).
|
||||
* - entity: The entity object the field is attached to
|
||||
* (\Drupal\Core\Entity\EntityInterface).
|
||||
* - widget: The widget object (\Drupal\Core\Field\WidgetInterface).
|
||||
*
|
||||
* @ingroup hooks
|
||||
* @see hook_options_list()
|
||||
*/
|
||||
function hook_options_list_alter(array &$options, array $context) {
|
||||
// Check if this is the field we want to change.
|
||||
if ($context['fieldDefinition']->getName() == 'field_option') {
|
||||
// Change the label of the empty option.
|
||||
$options['_none'] = t('== Empty ==');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the allowed values for a 'list_*' field.
|
||||
*
|
||||
* Callback for options_allowed_values().
|
||||
*
|
||||
* 'list_*' fields can specify a callback to define the set of their allowed
|
||||
* values using the 'allowed_values_function' storage setting.
|
||||
*
|
||||
* That function will be called:
|
||||
* - either in the context of a specific entity, which is then provided as the
|
||||
* $entity parameter,
|
||||
* - or for the field generally without the context of any specific entity or
|
||||
* entity bundle (typically, Views needing a list of values for an exposed
|
||||
* filter), in which case the $entity parameter is NULL.
|
||||
* This lets the callback restrict the set of allowed values or adjust the
|
||||
* labels depending on some conditions on the containing entity.
|
||||
*
|
||||
* For consistency, the set of values returned when an $entity is provided
|
||||
* should be a subset of the values returned when no $entity is provided.
|
||||
*
|
||||
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition
|
||||
* The field storage definition.
|
||||
* @param \Drupal\Core\Entity\FieldableEntityInterface|null $entity
|
||||
* (optional) The entity context if known, or NULL if the allowed values are
|
||||
* being collected without the context of a specific entity.
|
||||
* @param bool &$cacheable
|
||||
* (optional) If an $entity is provided, the $cacheable parameter should be
|
||||
* modified by reference and set to FALSE if the set of allowed values
|
||||
* returned was specifically adjusted for that entity and cannot not be reused
|
||||
* for other entities. Defaults to TRUE.
|
||||
*
|
||||
* @return array
|
||||
* The array of allowed values. Keys of the array are the raw stored values
|
||||
* (number or text), values of the array are the display labels. If $entity
|
||||
* is NULL, you should return the list of all the possible allowed values in
|
||||
* any context so that other code (e.g. Views filters) can support the allowed
|
||||
* values for all possible entities and bundles.
|
||||
*
|
||||
* @ingroup callbacks
|
||||
* @see options_allowed_values()
|
||||
* @see \Drupal\options_test\OptionsAllowedValues::simpleValues()
|
||||
* @see \Drupal\options_test\OptionsAllowedValues::dynamicValues()
|
||||
*/
|
||||
function callback_allowed_values_function(FieldStorageDefinitionInterface $definition, ?FieldableEntityInterface $entity = NULL, &$cacheable = TRUE) {
|
||||
if (isset($entity) && ($entity->bundle() == 'not_a_programmer')) {
|
||||
$values = [
|
||||
1 => 'One',
|
||||
2 => 'Two',
|
||||
];
|
||||
}
|
||||
else {
|
||||
$values = [
|
||||
'Group 1' => [
|
||||
0 => 'Zero',
|
||||
1 => 'One',
|
||||
],
|
||||
'Group 2' => [
|
||||
2 => 'Two',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
selection_list:
|
||||
label: 'Selection list'
|
||||
description: 'Field to select from predefined options.'
|
||||
weight: -15
|
||||
libraries:
|
||||
- options/drupal.options-icon
|
||||
8
web/core/modules/options/options.info.yml
Normal file
8
web/core/modules/options/options.info.yml
Normal file
@ -0,0 +1,8 @@
|
||||
name: Options
|
||||
type: module
|
||||
description: 'Defines field types with select lists, checkboxes, and radio buttons to select values from fixed lists of options.'
|
||||
package: Field types
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:field
|
||||
- drupal:text
|
||||
7
web/core/modules/options/options.libraries.yml
Normal file
7
web/core/modules/options/options.libraries.yml
Normal file
@ -0,0 +1,7 @@
|
||||
drupal.options-icon:
|
||||
version: VERSION
|
||||
css:
|
||||
theme:
|
||||
css/options.icon.theme.css: {}
|
||||
dependencies:
|
||||
- field_ui/drupal.field_ui.manage_fields
|
||||
82
web/core/modules/options/options.module
Normal file
82
web/core/modules/options/options.module
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Returns the array of allowed values for a list field.
|
||||
*
|
||||
* The strings are not safe for output. Keys and values of the array should be
|
||||
* sanitized through \Drupal\Core\Field\FieldFilteredMarkup before being
|
||||
* displayed.
|
||||
*
|
||||
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition
|
||||
* The field storage definition.
|
||||
* @param \Drupal\Core\Entity\FieldableEntityInterface|null $entity
|
||||
* (optional) The specific entity when this function is called from the
|
||||
* context of a specific field on a specific entity. This allows custom
|
||||
* 'allowed_values_function' callbacks to either restrict the values or
|
||||
* customize the labels for particular bundles and entities. NULL when
|
||||
* there is not a specific entity available, such as for Views filters.
|
||||
*
|
||||
* @return array
|
||||
* The array of allowed values. Keys of the array are the raw stored values
|
||||
* (number or text), values of the array are the display labels.
|
||||
*
|
||||
* @see callback_allowed_values_function()
|
||||
*/
|
||||
function options_allowed_values(FieldStorageDefinitionInterface $definition, ?FieldableEntityInterface $entity = NULL) {
|
||||
$allowed_values = &drupal_static(__FUNCTION__, []);
|
||||
|
||||
$cache_keys = [$definition->getTargetEntityTypeId(), $definition->getName()];
|
||||
if ($entity) {
|
||||
$cache_keys[] = 'entity';
|
||||
}
|
||||
$cache_id = implode(':', $cache_keys);
|
||||
|
||||
if (!isset($allowed_values[$cache_id])) {
|
||||
$function = $definition->getSetting('allowed_values_function');
|
||||
// If $cacheable is FALSE, then the allowed values are not statically
|
||||
// cached. See OptionsAllowedValues::dynamicValues() for an example of
|
||||
// generating dynamic and uncached values.
|
||||
$cacheable = TRUE;
|
||||
if (!empty($function)) {
|
||||
$values = $function($definition, $entity, $cacheable);
|
||||
}
|
||||
else {
|
||||
$values = $definition->getSetting('allowed_values');
|
||||
}
|
||||
|
||||
if ($cacheable) {
|
||||
$allowed_values[$cache_id] = $values;
|
||||
}
|
||||
else {
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
|
||||
return $allowed_values[$cache_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a list of values are being used in actual field values.
|
||||
*/
|
||||
function _options_values_in_use($entity_type, $field_name, $values) {
|
||||
if ($values) {
|
||||
$result = \Drupal::entityQuery($entity_type)
|
||||
->condition($field_name . '.value', $values, 'IN')
|
||||
->count()
|
||||
->accessCheck(FALSE)
|
||||
->range(0, 1)
|
||||
->execute();
|
||||
if ($result) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
2
web/core/modules/options/options.services.yml
Normal file
2
web/core/modules/options/options.services.yml
Normal file
@ -0,0 +1,2 @@
|
||||
parameters:
|
||||
options.skip_procedural_hook_scan: true
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.field.node.options_install_test.body
|
||||
- node.type.options_install_test
|
||||
module:
|
||||
- text
|
||||
third_party_settings: { }
|
||||
id: node.options_install_test.default
|
||||
targetEntityType: node
|
||||
bundle: options_install_test
|
||||
mode: default
|
||||
content:
|
||||
title:
|
||||
type: string_textfield
|
||||
weight: -5
|
||||
region: content
|
||||
settings:
|
||||
size: 60
|
||||
placeholder: ''
|
||||
third_party_settings: { }
|
||||
uid:
|
||||
type: entity_reference_autocomplete
|
||||
weight: 5
|
||||
region: content
|
||||
settings:
|
||||
match_operator: CONTAINS
|
||||
match_limit: 10
|
||||
size: 60
|
||||
placeholder: ''
|
||||
third_party_settings: { }
|
||||
created:
|
||||
type: datetime_timestamp
|
||||
weight: 10
|
||||
region: content
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
promote:
|
||||
type: boolean_checkbox
|
||||
weight: 15
|
||||
region: content
|
||||
settings:
|
||||
display_label: true
|
||||
third_party_settings: { }
|
||||
sticky:
|
||||
type: boolean_checkbox
|
||||
weight: 16
|
||||
region: content
|
||||
settings:
|
||||
display_label: true
|
||||
third_party_settings: { }
|
||||
body:
|
||||
type: text_textarea_with_summary
|
||||
weight: 26
|
||||
region: content
|
||||
settings:
|
||||
rows: 9
|
||||
summary_rows: 3
|
||||
placeholder: ''
|
||||
show_summary: false
|
||||
third_party_settings: { }
|
||||
hidden: { }
|
||||
@ -0,0 +1,27 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.field.node.options_install_test.body
|
||||
- node.type.options_install_test
|
||||
module:
|
||||
- text
|
||||
- user
|
||||
third_party_settings: { }
|
||||
id: node.options_install_test.default
|
||||
targetEntityType: node
|
||||
bundle: options_install_test
|
||||
mode: default
|
||||
content:
|
||||
links:
|
||||
weight: 100
|
||||
region: content
|
||||
body:
|
||||
type: text_default
|
||||
label: hidden
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
weight: 101
|
||||
region: content
|
||||
hidden:
|
||||
langcode: true
|
||||
@ -0,0 +1,29 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- core.entity_view_mode.node.teaser
|
||||
- field.field.node.options_install_test.body
|
||||
- node.type.options_install_test
|
||||
module:
|
||||
- text
|
||||
- user
|
||||
third_party_settings: { }
|
||||
id: node.options_install_test.teaser
|
||||
targetEntityType: node
|
||||
bundle: options_install_test
|
||||
mode: teaser
|
||||
content:
|
||||
links:
|
||||
weight: 100
|
||||
region: content
|
||||
body:
|
||||
type: text_summary_or_trimmed
|
||||
label: hidden
|
||||
settings:
|
||||
trim_length: 600
|
||||
third_party_settings: { }
|
||||
weight: 101
|
||||
region: content
|
||||
hidden:
|
||||
langcode: true
|
||||
@ -0,0 +1,21 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.node.body
|
||||
- node.type.options_install_test
|
||||
third_party_settings: { }
|
||||
id: node.options_install_test.body
|
||||
field_name: body
|
||||
entity_type: node
|
||||
bundle: options_install_test
|
||||
label: Body
|
||||
description: ''
|
||||
required: false
|
||||
translatable: true
|
||||
default_value: { }
|
||||
default_value_callback: ''
|
||||
settings:
|
||||
display_summary: true
|
||||
allowed_formats: { }
|
||||
field_type: text_with_summary
|
||||
@ -0,0 +1,19 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.node.field_options_float
|
||||
- node.type.options_install_test
|
||||
third_party_settings: { }
|
||||
id: node.options_install_test.field_options_float
|
||||
field_name: field_options_float
|
||||
entity_type: node
|
||||
bundle: options_install_test
|
||||
label: field_options_float
|
||||
description: ''
|
||||
required: false
|
||||
translatable: true
|
||||
default_value: { }
|
||||
default_value_callback: ''
|
||||
settings: { }
|
||||
field_type: list_float
|
||||
@ -0,0 +1,26 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- options
|
||||
id: node.field_options_float
|
||||
field_name: field_options_float
|
||||
entity_type: node
|
||||
type: list_float
|
||||
settings:
|
||||
allowed_values:
|
||||
-
|
||||
value: !!float 0
|
||||
label: Zero
|
||||
-
|
||||
value: 0.5
|
||||
label: 'Point five'
|
||||
allowed_values_function: ''
|
||||
module: options
|
||||
locked: false
|
||||
cardinality: 1
|
||||
translatable: true
|
||||
indexes: { }
|
||||
persist_with_no_fields: false
|
||||
custom_storage: false
|
||||
@ -0,0 +1,11 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
third_party_settings: { }
|
||||
name: options_install_test
|
||||
type: options_install_test
|
||||
description: null
|
||||
help: null
|
||||
new_revision: false
|
||||
preview_mode: 1
|
||||
display_submitted: true
|
||||
@ -0,0 +1,8 @@
|
||||
name: 'Options config install test'
|
||||
type: module
|
||||
description: 'Support module for the Options module tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:node
|
||||
- drupal:options
|
||||
@ -0,0 +1,5 @@
|
||||
name: 'Options test'
|
||||
type: module
|
||||
description: 'Support module for the Options module tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\options_test\Hook;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for options_test.
|
||||
*/
|
||||
class OptionsTestHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
#[Hook('form_entity_test_entity_test_form_alter')]
|
||||
public function formEntityTestEntityTestFormAlter(&$form, FormStateInterface $form_state, $form_id) : void {
|
||||
if (\Drupal::state()->get('options_test.form_alter_enable', FALSE)) {
|
||||
$form['card_1']['widget']['#required_error'] = 'This is custom message for required field.';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_options_list_alter().
|
||||
*/
|
||||
#[Hook('options_list_alter')]
|
||||
public function optionsListAlter(array &$options, array $context): void {
|
||||
if ($context['fieldDefinition']->getName() === 'card_4' && $context['widget']->getPluginId() === 'options_select') {
|
||||
// Rename _none option.
|
||||
$options['_none'] = '- Select something -';
|
||||
}
|
||||
if ($context['fieldDefinition']->getName() === 'card_4' && $context['entity']->bundle() === 'entity_test') {
|
||||
// Remove 0 option.
|
||||
unset($options[0]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\options_test;
|
||||
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Provide allowed values callback.
|
||||
*/
|
||||
class OptionsAllowedValues {
|
||||
|
||||
/**
|
||||
* Implements callback_allowed_values_function().
|
||||
*
|
||||
* @see options_allowed_values()
|
||||
*/
|
||||
public static function simpleValues(FieldStorageDefinitionInterface $definition, ?FieldableEntityInterface $entity = NULL): array {
|
||||
return [
|
||||
'Group 1' => [
|
||||
0 => 'Zero',
|
||||
],
|
||||
1 => 'One',
|
||||
'Group 2' => [
|
||||
2 => 'Some <script>dangerous</script> & unescaped <strong>markup</strong>',
|
||||
],
|
||||
'More <script>dangerous</script> markup' => [
|
||||
3 => 'Three',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements callback_allowed_values_function().
|
||||
*
|
||||
* @todo This function violates the recommendation in options_allowed_values()
|
||||
* to return a list of all possible values in any context when $items is
|
||||
* NULL. Since this is not yet used for testing Views integration, that is
|
||||
* alright for now. Fix this in https://www.drupal.org/node/2012130.
|
||||
*
|
||||
* @see options_allowed_values()
|
||||
*/
|
||||
public static function dynamicValues(FieldStorageDefinitionInterface $definition, ?FieldableEntityInterface $entity = NULL, &$cacheable = NULL): array {
|
||||
$values = [];
|
||||
if (isset($entity)) {
|
||||
$cacheable = FALSE;
|
||||
$values = [
|
||||
$entity->label(),
|
||||
$entity->toUrl()->toString(),
|
||||
$entity->uuid(),
|
||||
$entity->bundle(),
|
||||
];
|
||||
}
|
||||
// We need the values of the entity as keys.
|
||||
return array_combine($values, $values);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
name: 'Options test views'
|
||||
type: module
|
||||
description: 'Provides default views for views options tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:options
|
||||
- drupal:views
|
||||
@ -0,0 +1,198 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- node.type.article
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: test_options_list_argument_numeric
|
||||
label: 'test options list argument (numeric)'
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: 1
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: some
|
||||
options:
|
||||
items_per_page: 5
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
label: ''
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
plugin_id: field
|
||||
filters:
|
||||
status:
|
||||
value: '1'
|
||||
table: node_field_data
|
||||
field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: ''
|
||||
group: 1
|
||||
plugin_id: boolean
|
||||
type:
|
||||
id: type
|
||||
table: node_field_data
|
||||
field: type
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value:
|
||||
article: article
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: '0'
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
plugin_id: bundle
|
||||
sorts:
|
||||
nid:
|
||||
id: nid
|
||||
table: nid
|
||||
field: nid
|
||||
order: DESC
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
plugin_id: standard
|
||||
title: 'test options list argument'
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments:
|
||||
field_test_list_integer_value:
|
||||
id: field_test_list_integer_value
|
||||
table: field_data_field_test_list_integer
|
||||
field: field_test_list_integer_value
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
default_action: empty
|
||||
exception:
|
||||
value: all
|
||||
title_enable: false
|
||||
title: All
|
||||
title_enable: false
|
||||
title: ''
|
||||
default_argument_type: fixed
|
||||
default_argument_options:
|
||||
argument: ''
|
||||
summary_options:
|
||||
base_path: ''
|
||||
items_per_page: 25
|
||||
count: false
|
||||
override: false
|
||||
summary:
|
||||
sort_order: asc
|
||||
number_of_records: 0
|
||||
format: default_summary
|
||||
human: true
|
||||
specify_validation: false
|
||||
validate:
|
||||
type: none
|
||||
fail: 'not found'
|
||||
validate_options: { }
|
||||
break_phrase: false
|
||||
not: false
|
||||
plugin_id: number_list_field
|
||||
display_extenders: { }
|
||||
block_1:
|
||||
display_plugin: block
|
||||
id: block_1
|
||||
display_title: Block
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
@ -0,0 +1,197 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- node.type.article
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: test_options_list_argument_string
|
||||
label: 'test options list argument (string)'
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: 1
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: some
|
||||
options:
|
||||
items_per_page: 5
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
label: ''
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
plugin_id: field
|
||||
filters:
|
||||
status:
|
||||
value: '1'
|
||||
table: node_field_data
|
||||
field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: ''
|
||||
group: 1
|
||||
plugin_id: boolean
|
||||
type:
|
||||
id: type
|
||||
table: node_field_data
|
||||
field: type
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value:
|
||||
article: article
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: '0'
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
plugin_id: bundle
|
||||
sorts:
|
||||
nid:
|
||||
id: nid
|
||||
table: nid
|
||||
field: nid
|
||||
order: DESC
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
plugin_id: standard
|
||||
title: 'test options list argument'
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments:
|
||||
field_test_list_string_value:
|
||||
id: field_test_list_string_value
|
||||
table: field_data_field_test_list_string
|
||||
field: field_test_list_string_value
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
default_action: empty
|
||||
exception:
|
||||
value: all
|
||||
title_enable: false
|
||||
title: All
|
||||
title_enable: false
|
||||
title: ''
|
||||
default_argument_type: fixed
|
||||
default_argument_options:
|
||||
argument: ''
|
||||
summary_options:
|
||||
base_path: ''
|
||||
items_per_page: 25
|
||||
count: false
|
||||
override: false
|
||||
summary:
|
||||
sort_order: asc
|
||||
number_of_records: 0
|
||||
format: default_summary
|
||||
human: true
|
||||
specify_validation: false
|
||||
validate:
|
||||
type: none
|
||||
fail: 'not found'
|
||||
validate_options: { }
|
||||
break_phrase: false
|
||||
plugin_id: string_list_field
|
||||
display_extenders: { }
|
||||
block_1:
|
||||
display_plugin: block
|
||||
id: block_1
|
||||
display_title: Block
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
@ -0,0 +1,203 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- node.type.article
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: test_options_list_filter
|
||||
label: test_options_list_filter
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: 1
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: some
|
||||
options:
|
||||
items_per_page: 5
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
label: ''
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
plugin_id: field
|
||||
filters:
|
||||
status:
|
||||
value: '1'
|
||||
table: node_field_data
|
||||
field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: ''
|
||||
group: 1
|
||||
plugin_id: boolean
|
||||
field_test_list_string_value:
|
||||
id: field_test_list_string_value
|
||||
table: field_data_field_test_list_string
|
||||
field: field_test_list_string_value
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: or
|
||||
value:
|
||||
man: man
|
||||
woman: woman
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
reduce_duplicates: false
|
||||
plugin_id: list_field
|
||||
type:
|
||||
id: type
|
||||
table: node_field_data
|
||||
field: type
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value:
|
||||
article: article
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
plugin_id: bundle
|
||||
sorts:
|
||||
nid:
|
||||
id: nid
|
||||
table: nid
|
||||
field: nid
|
||||
order: DESC
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
plugin_id: standard
|
||||
title: test_options_list_filter
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
block_1:
|
||||
display_plugin: block
|
||||
id: block_1
|
||||
display_title: Block
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
|
||||
|
||||
/**
|
||||
* Generic module test for options.
|
||||
*
|
||||
* @group options
|
||||
*/
|
||||
class GenericTest extends GenericModuleTestBase {}
|
||||
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Functional;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\entity_test\Entity\EntityTestRev;
|
||||
use Drupal\Tests\field\Functional\FieldTestBase;
|
||||
|
||||
/**
|
||||
* Base class for testing allowed values of options fields.
|
||||
*/
|
||||
abstract class OptionsDynamicValuesTestBase extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['options', 'entity_test', 'options_test'];
|
||||
|
||||
/**
|
||||
* The created entity.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* The field storage.
|
||||
*
|
||||
* @var \Drupal\Core\Field\FieldStorageDefinitionInterface
|
||||
*/
|
||||
protected $fieldStorage;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected int $field;
|
||||
|
||||
/**
|
||||
* Test data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $test;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$field_name = 'test_options';
|
||||
$this->fieldStorage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test_rev',
|
||||
'type' => 'list_string',
|
||||
'cardinality' => 1,
|
||||
'settings' => [
|
||||
'allowed_values_function' => '\Drupal\options_test\OptionsAllowedValues::dynamicValues',
|
||||
],
|
||||
]);
|
||||
$this->fieldStorage->save();
|
||||
|
||||
$this->field = FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test_rev',
|
||||
'bundle' => 'entity_test_rev',
|
||||
'required' => TRUE,
|
||||
])->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('entity_test_rev', 'entity_test_rev')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'options_select',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Create an entity and prepare test data that will be used by
|
||||
// \Drupal\options_test\OptionsAllowedValues::dynamicValues().
|
||||
$values = [
|
||||
'user_id' => mt_rand(1, 10),
|
||||
'name' => $this->randomMachineName(),
|
||||
];
|
||||
$this->entity = EntityTestRev::create($values);
|
||||
$this->entity->save();
|
||||
$this->test = [
|
||||
'label' => $this->entity->label(),
|
||||
'uuid' => $this->entity->uuid(),
|
||||
'bundle' => $this->entity->bundle(),
|
||||
'uri' => $this->entity->toUrl()->toString(),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,488 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Functional;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\field\Functional\FieldTestBase;
|
||||
|
||||
/**
|
||||
* Tests the Options field UI functionality.
|
||||
*
|
||||
* @group options
|
||||
* @group #slow
|
||||
*/
|
||||
class OptionsFieldUITest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'options',
|
||||
'field_test',
|
||||
'taxonomy',
|
||||
'field_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of the created content type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $typeName;
|
||||
|
||||
/**
|
||||
* Machine name of the created content type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* Name of the option field.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* Admin path to manage field storage settings.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $adminPath;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create test user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer taxonomy',
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
'administer content types',
|
||||
'administer nodes',
|
||||
'bypass node access',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Create content type, with underscores.
|
||||
$this->typeName = 'test_' . $this->randomMachineName();
|
||||
$type = $this->drupalCreateContentType(['name' => $this->typeName, 'type' => $this->typeName]);
|
||||
$this->type = $type->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Options (integer) : test 'allowed values' input.
|
||||
*/
|
||||
public function testOptionsAllowedValuesInteger(): void {
|
||||
$this->fieldName = 'field_options_integer';
|
||||
$this->createOptionsField('list_integer');
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// Explicit integer keys.
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => 0,
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][key]' => 2,
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'Two',
|
||||
];
|
||||
$array = [0 => 'Zero', 2 => 'Two'];
|
||||
$this->assertAllowedValuesInput($input, $array, 'Integer keys are accepted.');
|
||||
|
||||
// Non-integer keys.
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => 1.1,
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'One',
|
||||
];
|
||||
$this->assertAllowedValuesInput($input, 'keys must be integers', 'Non integer keys are rejected.');
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => 'abc',
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'abc',
|
||||
];
|
||||
$this->assertAllowedValuesInput($input, 'keys must be integers', 'Non integer keys are rejected.');
|
||||
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => 0,
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][key]' => 1,
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'One',
|
||||
];
|
||||
$array = [0 => 'Zero', 1 => 'One'];
|
||||
$this->assertAllowedValuesInput($input, $array, '');
|
||||
// Create a node with actual data for the field.
|
||||
$settings = [
|
||||
'type' => $this->type,
|
||||
$this->fieldName => [['value' => 1]],
|
||||
];
|
||||
$node = $this->drupalCreateNode($settings);
|
||||
|
||||
// Check that the values in use cannot be removed.
|
||||
$this->drupalGet($this->adminPath);
|
||||
$assert_session->elementExists('css', '#remove_row_button__1');
|
||||
$delete_button_1 = $page->findById('remove_row_button__1');
|
||||
$this->assertTrue($delete_button_1->hasAttribute('disabled'), 'Button is disabled');
|
||||
|
||||
// Delete the node, remove the value.
|
||||
$node->delete();
|
||||
$this->drupalGet($this->adminPath);
|
||||
$delete_button_1->click();
|
||||
$assert_session->pageTextNotContains('Processing...');
|
||||
$page->findById('edit-submit')->click();
|
||||
$field_storage = FieldStorageConfig::loadByName('node', $this->fieldName);
|
||||
$this->assertSame($field_storage->getSetting('allowed_values'), [0 => 'Zero']);
|
||||
|
||||
// Check that the same key can only be used once.
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => 0,
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][key]' => 0,
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'One',
|
||||
];
|
||||
$array = ['0' => 'One'];
|
||||
$this->assertAllowedValuesInput($input, $array, 'Same value cannot be used multiple times.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Options (float) : test 'allowed values' input.
|
||||
*/
|
||||
public function testOptionsAllowedValuesFloat(): void {
|
||||
$this->fieldName = 'field_options_float';
|
||||
$this->createOptionsField('list_float');
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// Explicit numeric keys.
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => 0,
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][key]' => .5,
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'Point five',
|
||||
];
|
||||
$array = ['0' => 'Zero', '0.5' => 'Point five'];
|
||||
$this->assertAllowedValuesInput($input, $array, 'Integer keys are accepted.');
|
||||
|
||||
// Check that values can be added.
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => 0,
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][key]' => .5,
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'Point five',
|
||||
'field_storage[subform][settings][allowed_values][table][2][item][key]' => 1,
|
||||
'field_storage[subform][settings][allowed_values][table][2][item][label]' => 'One',
|
||||
];
|
||||
$array = ['0' => 'Zero', '0.5' => 'Point five', '1' => 'One'];
|
||||
$this->assertAllowedValuesInput($input, $array, 'Values can be added.');
|
||||
// Non-numeric keys.
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => 'abc',
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'abc',
|
||||
];
|
||||
$this->assertAllowedValuesInput($input, 'each key must be a valid integer or decimal', 'Non numeric keys are rejected.');
|
||||
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => 0,
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][key]' => .5,
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'Point five',
|
||||
'field_storage[subform][settings][allowed_values][table][2][item][key]' => 2,
|
||||
'field_storage[subform][settings][allowed_values][table][2][item][label]' => 'Two',
|
||||
];
|
||||
$array = ['0' => 'Zero', '0.5' => 'Point five', '2' => 'Two'];
|
||||
$this->assertAllowedValuesInput($input, $array, '');
|
||||
// Create a node with actual data for the field.
|
||||
$settings = [
|
||||
'type' => $this->type,
|
||||
$this->fieldName => [['value' => .5]],
|
||||
];
|
||||
$node = $this->drupalCreateNode($settings);
|
||||
|
||||
// Check that the values in use cannot be removed.
|
||||
$this->drupalGet($this->adminPath);
|
||||
$assert_session->elementExists('css', '#remove_row_button__1');
|
||||
$delete_button_1 = $page->findById('remove_row_button__1');
|
||||
$this->assertTrue($delete_button_1->hasAttribute('disabled'), 'Button is disabled');
|
||||
|
||||
// Delete the node, remove the value.
|
||||
$node->delete();
|
||||
$this->drupalGet($this->adminPath);
|
||||
$delete_button_1->click();
|
||||
$assert_session->pageTextNotContains('Processing...');
|
||||
$page->findById('edit-submit')->click();
|
||||
$field_storage = FieldStorageConfig::loadByName('node', $this->fieldName);
|
||||
$this->assertSame($field_storage->getSetting('allowed_values'), [0 => 'Zero', 2 => 'Two']);
|
||||
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => .5,
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Point five',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][key]' => .5,
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'Half',
|
||||
];
|
||||
$array = ['0.5' => 'Half'];
|
||||
$this->assertAllowedValuesInput($input, $array, 'Same value cannot be used multiple times.');
|
||||
|
||||
// Check that different forms of the same float value cannot be used.
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => .5,
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Point five',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][key]' => 0.5,
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'Half',
|
||||
];
|
||||
$array = ['0.5' => 'Half'];
|
||||
$this->assertAllowedValuesInput($input, $array, 'Different forms of the same value cannot be used.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Options (text) : test 'allowed values' input.
|
||||
*/
|
||||
public function testOptionsAllowedValuesText(): void {
|
||||
$this->fieldName = 'field_options_text';
|
||||
$this->createOptionsField('list_string');
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// Explicit keys.
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => '_zero',
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][key]' => '_one',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'One',
|
||||
];
|
||||
$array = ['_zero' => 'Zero', '_one' => 'One'];
|
||||
$this->assertAllowedValuesInput($input, $array, 'Explicit keys are accepted.');
|
||||
|
||||
// Overly long keys.
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => 'zero',
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][key]' => $this->randomMachineName(256),
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'One',
|
||||
];
|
||||
$this->assertAllowedValuesInput($input, 'each key must be a string at most 255 characters long', 'Overly long keys are rejected.');
|
||||
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => 'zero',
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][key]' => 'one',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'One',
|
||||
];
|
||||
$array = ['zero' => 'Zero', 'one' => 'One'];
|
||||
$this->assertAllowedValuesInput($input, $array, '');
|
||||
// Create a node with actual data for the field.
|
||||
$settings = [
|
||||
'type' => $this->type,
|
||||
$this->fieldName => [['value' => 'one']],
|
||||
];
|
||||
$node = $this->drupalCreateNode($settings);
|
||||
|
||||
// Check that the values in use cannot be removed.
|
||||
$this->drupalGet($this->adminPath);
|
||||
$assert_session->elementExists('css', '#remove_row_button__1');
|
||||
$delete_button_1 = $page->findById('remove_row_button__1');
|
||||
$value_field_1 = $page->findField('field_storage[subform][settings][allowed_values][table][1][item][key]');
|
||||
$this->assertTrue($delete_button_1->hasAttribute('disabled'), 'Button is disabled');
|
||||
$this->assertTrue($value_field_1->hasAttribute('disabled'), 'Button is disabled');
|
||||
|
||||
// Delete the node, remove the value.
|
||||
$node->delete();
|
||||
$this->drupalGet($this->adminPath);
|
||||
$delete_button_1->click();
|
||||
$assert_session->pageTextNotContains('Processing...');
|
||||
$page->findById('edit-submit')->click();
|
||||
$field_storage = FieldStorageConfig::loadByName('node', $this->fieldName);
|
||||
$this->assertSame($field_storage->getSetting('allowed_values'), ['zero' => 'Zero']);
|
||||
|
||||
// Check that string values with special characters can be used.
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => 'zero',
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][key]' => '.example #example',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'Example',
|
||||
];
|
||||
$array = ['zero' => 'Zero', '.example #example' => 'Example'];
|
||||
$this->assertAllowedValuesInput($input, $array, '');
|
||||
|
||||
// Check that the same key can only be used once.
|
||||
$input = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => 'zero',
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][key]' => 'zero',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'One',
|
||||
];
|
||||
$array = ['zero' => 'One'];
|
||||
$this->assertAllowedValuesInput($input, $array, 'Same value cannot be used multiple times.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create list field of a given type.
|
||||
*
|
||||
* @param string $type
|
||||
* One of 'list_integer', 'list_float' or 'list_string'.
|
||||
*/
|
||||
protected function createOptionsField($type): void {
|
||||
// Create a field.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'node',
|
||||
'type' => $type,
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'node',
|
||||
'bundle' => $this->type,
|
||||
])->save();
|
||||
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('node', $this->type)
|
||||
->setComponent($this->fieldName)
|
||||
->save();
|
||||
|
||||
$this->adminPath = 'admin/structure/types/manage/' . $this->type . '/fields/node.' . $this->type . '.' . $this->fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests an input array for the 'allowed values' form element.
|
||||
*
|
||||
* @param array $input
|
||||
* The input array.
|
||||
* @param array|string $result
|
||||
* Either an expected resulting array in
|
||||
* $field->getSetting('allowed_values'), or an expected error message.
|
||||
* @param string $message
|
||||
* Message to display.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function assertAllowedValuesInput(array $input, $result, string $message): void {
|
||||
$this->drupalGet($this->adminPath);
|
||||
$page = $this->getSession()->getPage();
|
||||
$add_button = $page->findButton('Add another item');
|
||||
$add_button->click();
|
||||
$add_button->click();
|
||||
|
||||
$this->submitForm($input, 'Save');
|
||||
// Verify that the page does not have double escaped HTML tags.
|
||||
$this->assertSession()->responseNotContains('&lt;');
|
||||
|
||||
if (is_string($result)) {
|
||||
$this->assertSession()->pageTextContains($result);
|
||||
}
|
||||
else {
|
||||
$field_storage = FieldStorageConfig::loadByName('node', $this->fieldName);
|
||||
$this->assertSame($field_storage->getSetting('allowed_values'), $result, $message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests normal and key formatter display on node display.
|
||||
*/
|
||||
public function testNodeDisplay(): void {
|
||||
$this->fieldName = $this->randomMachineName();
|
||||
$this->createOptionsField('list_integer');
|
||||
$node = $this->drupalCreateNode(['type' => $this->type]);
|
||||
|
||||
$on = $this->randomMachineName();
|
||||
$off = $this->randomMachineName();
|
||||
$edit = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => 1,
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => $on,
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][key]' => 0,
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][label]' => $off,
|
||||
];
|
||||
|
||||
$this->drupalGet($this->adminPath);
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->findButton('Add another item')->click();
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Select a default value.
|
||||
$edit = [
|
||||
$this->fieldName => '1',
|
||||
];
|
||||
$this->drupalGet('node/' . $node->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Check the node page and see if the values are correct.
|
||||
$file_formatters = ['list_default', 'list_key'];
|
||||
foreach ($file_formatters as $formatter) {
|
||||
$edit = [
|
||||
"fields[$this->fieldName][type]" => $formatter,
|
||||
"fields[$this->fieldName][region]" => 'content',
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->typeName . '/display');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
|
||||
if ($formatter == 'list_default') {
|
||||
$output = $on;
|
||||
}
|
||||
else {
|
||||
$output = '1';
|
||||
}
|
||||
|
||||
// Verify that correct options are found.
|
||||
$this->assertSession()->elementsCount('xpath', '//div[text()="' . $output . '"]', 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms the allowed value list is a required field.
|
||||
*/
|
||||
public function testRequiredPropertyForAllowedValuesList(): void {
|
||||
$field_types = [
|
||||
'list_float',
|
||||
'list_string',
|
||||
'list_integer',
|
||||
];
|
||||
|
||||
foreach ($field_types as $field_type) {
|
||||
$this->fieldName = "field_options_$field_type";
|
||||
$this->createOptionsField($field_type);
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
$this->drupalGet($this->adminPath);
|
||||
// Assert that the delete button for a single row is disabled.
|
||||
$this->assertCount(1, $page->findAll('css', '#allowed-values-order tr.draggable'));
|
||||
$delete_button_0 = $page->findById('remove_row_button__0');
|
||||
$this->assertTrue($delete_button_0->hasAttribute('disabled'), 'Button is disabled');
|
||||
$page->findButton('Add another item')->click();
|
||||
// Assert that the delete button for the first row is enabled if there are
|
||||
// more that one rows.
|
||||
$this->assertCount(2, $page->findAll('css', '#allowed-values-order tr.draggable'));
|
||||
$this->assertFalse($delete_button_0->hasAttribute('disabled'), 'Button is enabled');
|
||||
// Delete a row.
|
||||
$delete_button_0->click();
|
||||
// Assert that the button is disabled again.
|
||||
$this->assertTrue($delete_button_0->hasAttribute('disabled'), 'Button is disabled');
|
||||
// Try to proceed without entering any value.
|
||||
$page->findButton('Save')->click();
|
||||
|
||||
if ($field_type == 'list_string') {
|
||||
// Asserting only name field as there is no value field for list_string.
|
||||
$this->assertSession()->pageTextContains('Name field is required.');
|
||||
}
|
||||
else {
|
||||
// Confirmation message that name and value are required fields for
|
||||
// list_float and list_integer.
|
||||
$this->assertSession()->pageTextContains('Name field is required.');
|
||||
$this->assertSession()->pageTextContains('Value field is required.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Functional;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\field\Functional\FieldTestBase;
|
||||
|
||||
/**
|
||||
* Tests option fields can be updated and created by config synchronization.
|
||||
*
|
||||
* @group options
|
||||
*/
|
||||
class OptionsFloatFieldImportTest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'options',
|
||||
'field_ui',
|
||||
'config',
|
||||
'options_config_install_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create test user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'synchronize configuration',
|
||||
'access content',
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
'administer content types',
|
||||
'administer nodes',
|
||||
'bypass node access',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that importing list_float fields works.
|
||||
*/
|
||||
public function testImport(): void {
|
||||
$field_name = 'field_options_float';
|
||||
$type = 'options_install_test';
|
||||
|
||||
// Test the results on installing options_config_install_test. All the
|
||||
// necessary configuration for this test is created by installing that
|
||||
// module.
|
||||
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
|
||||
$this->assertSame($array = ['0' => 'Zero', '0.5' => 'Point five'], $field_storage->getSetting('allowed_values'));
|
||||
|
||||
$admin_path = 'admin/structure/types/manage/' . $type . '/fields/node.' . $type . '.' . $field_name;
|
||||
|
||||
// Export active config to sync.
|
||||
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
|
||||
|
||||
// Set the active to not use dots in the allowed values key names.
|
||||
$edit = [
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][key]' => 0,
|
||||
'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero',
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][key]' => 1,
|
||||
'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'One',
|
||||
];
|
||||
$this->drupalGet($admin_path);
|
||||
$this->submitForm($edit, 'Save');
|
||||
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
|
||||
$this->assertSame($array = ['0' => 'Zero', '1' => 'One'], $field_storage->getSetting('allowed_values'));
|
||||
|
||||
// Import configuration with dots in the allowed values key names. This
|
||||
// tests \Drupal\Core\Config\Entity\ConfigEntityStorage::importUpdate().
|
||||
$this->drupalGet('admin/config/development/configuration');
|
||||
$this->submitForm([], 'Import all');
|
||||
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
|
||||
$this->assertSame($array = ['0' => 'Zero', '0.5' => 'Point five'], $field_storage->getSetting('allowed_values'));
|
||||
|
||||
// Delete field to test creation. This tests
|
||||
// \Drupal\Core\Config\Entity\ConfigEntityStorage::importCreate().
|
||||
FieldConfig::loadByName('node', $type, $field_name)->delete();
|
||||
|
||||
$this->drupalGet('admin/config/development/configuration');
|
||||
$this->submitForm([], 'Import all');
|
||||
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
|
||||
$this->assertSame($array = ['0' => 'Zero', '0.5' => 'Point five'], $field_storage->getSetting('allowed_values'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Functional;
|
||||
|
||||
/**
|
||||
* Tests an options select with a dynamic allowed values function.
|
||||
*
|
||||
* @group options
|
||||
*/
|
||||
class OptionsSelectDynamicValuesTest extends OptionsDynamicValuesTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the 'options_select' widget (single select).
|
||||
*/
|
||||
public function testSelectListDynamic(): void {
|
||||
// Create an entity.
|
||||
$this->entity->save();
|
||||
|
||||
// Create a web user.
|
||||
$web_user = $this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
]);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Display form.
|
||||
$this->drupalGet('entity_test_rev/manage/' . $this->entity->id() . '/edit');
|
||||
$options = $this->assertSession()->selectExists('edit-test-options')->findAll('css', 'option');
|
||||
$this->assertCount(count($this->test) + 1, $options);
|
||||
foreach ($options as $option) {
|
||||
$value = $option->getValue();
|
||||
if ($value != '_none') {
|
||||
$this->assertContains($value, $this->test);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,705 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Functional;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\field\Functional\FieldTestBase;
|
||||
|
||||
/**
|
||||
* Tests the Options widgets.
|
||||
*
|
||||
* @group options
|
||||
*/
|
||||
class OptionsWidgetsTest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'options',
|
||||
'entity_test',
|
||||
'options_test',
|
||||
'taxonomy',
|
||||
'field_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A field storage with cardinality 1 to use in this test class.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldStorageConfig
|
||||
*/
|
||||
protected $card1;
|
||||
|
||||
/**
|
||||
* A field storage with cardinality 2 to use in this test class.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldStorageConfig
|
||||
*/
|
||||
protected $card2;
|
||||
|
||||
/**
|
||||
* A field storage with float values to use in this test class.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldStorageConfig
|
||||
*/
|
||||
protected $float;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Field storage with cardinality 1.
|
||||
$this->card1 = FieldStorageConfig::create([
|
||||
'field_name' => 'card_1',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'list_integer',
|
||||
'cardinality' => 1,
|
||||
'settings' => [
|
||||
'allowed_values' => [
|
||||
// Make sure that 0 works as an option.
|
||||
0 => 'Zero',
|
||||
1 => 'One',
|
||||
// Make sure that option text is properly sanitized.
|
||||
2 => 'Some <script>dangerous</script> & unescaped <strong>markup</strong>',
|
||||
// Make sure that HTML entities in option text are not double-encoded.
|
||||
3 => 'Some HTML encoded markup with < & >',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->card1->save();
|
||||
|
||||
// Field storage with cardinality 2.
|
||||
$this->card2 = FieldStorageConfig::create([
|
||||
'field_name' => 'card_2',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'list_integer',
|
||||
'cardinality' => 2,
|
||||
'settings' => [
|
||||
'allowed_values' => [
|
||||
// Make sure that 0 works as an option.
|
||||
0 => 'Zero',
|
||||
1 => 'One',
|
||||
// Make sure that option text is properly sanitized.
|
||||
2 => 'Some <script>dangerous</script> & unescaped <strong>markup</strong>',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->card2->save();
|
||||
|
||||
// Field storage with list of float values.
|
||||
$this->float = FieldStorageConfig::create([
|
||||
'field_name' => 'float',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'list_float',
|
||||
'cardinality' => 1,
|
||||
'settings' => [
|
||||
'allowed_values' => [
|
||||
'0.0' => '0.0',
|
||||
'1.5' => '1.5',
|
||||
'2.0' => '2.0',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->float->save();
|
||||
|
||||
// Create a web user.
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the 'options_buttons' widget (single select).
|
||||
*/
|
||||
public function testRadioButtons(): void {
|
||||
// Create an instance of the 'single value' field.
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $this->card1,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($this->card1->getName(), [
|
||||
'type' => 'options_buttons',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Create an entity.
|
||||
$entity = EntityTest::create([
|
||||
'user_id' => 1,
|
||||
'name' => $this->randomMachineName(),
|
||||
]);
|
||||
$entity->save();
|
||||
$entity_init = clone $entity;
|
||||
|
||||
// With no field data, no buttons are checked.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertSession()->checkboxNotChecked('edit-card-1-0');
|
||||
$this->assertSession()->checkboxNotChecked('edit-card-1-1');
|
||||
$this->assertSession()->checkboxNotChecked('edit-card-1-2');
|
||||
$this->assertSession()->responseContains('Some dangerous & unescaped <strong>markup</strong>');
|
||||
$this->assertSession()->responseContains('Some HTML encoded markup with < & >');
|
||||
|
||||
// Select first option.
|
||||
$edit = ['card_1' => 0];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_init, 'card_1', [0]);
|
||||
|
||||
// Check that the selected button is checked.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertSession()->checkboxChecked('edit-card-1-0');
|
||||
$this->assertSession()->checkboxNotChecked('edit-card-1-1');
|
||||
$this->assertSession()->checkboxNotChecked('edit-card-1-2');
|
||||
|
||||
// Unselect option.
|
||||
$edit = ['card_1' => '_none'];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_init, 'card_1', []);
|
||||
|
||||
// Check that required radios with one option is auto-selected.
|
||||
$this->card1->setSetting('allowed_values', [99 => 'Only allowed value']);
|
||||
$this->card1->save();
|
||||
$field->setRequired(TRUE);
|
||||
$field->save();
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertSession()->checkboxChecked('edit-card-1-99');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the 'options_buttons' widget (multiple select).
|
||||
*/
|
||||
public function testCheckBoxes(): void {
|
||||
// Create an instance of the 'multiple values' field.
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $this->card2,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($this->card2->getName(), [
|
||||
'type' => 'options_buttons',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Create an entity.
|
||||
$entity = EntityTest::create([
|
||||
'user_id' => 1,
|
||||
'name' => $this->randomMachineName(),
|
||||
]);
|
||||
$entity->save();
|
||||
$entity_init = clone $entity;
|
||||
|
||||
// Display form: with no field data, nothing is checked.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertSession()->checkboxNotChecked('edit-card-2-0');
|
||||
$this->assertSession()->checkboxNotChecked('edit-card-2-1');
|
||||
$this->assertSession()->checkboxNotChecked('edit-card-2-2');
|
||||
$this->assertSession()->responseContains('Some dangerous & unescaped <strong>markup</strong>');
|
||||
|
||||
// Submit form: select first and third options.
|
||||
$edit = [
|
||||
'card_2[0]' => TRUE,
|
||||
'card_2[1]' => FALSE,
|
||||
'card_2[2]' => TRUE,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_init, 'card_2', [0, 2]);
|
||||
|
||||
// Display form: check that the right options are selected.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertSession()->checkboxChecked('edit-card-2-0');
|
||||
$this->assertSession()->checkboxNotChecked('edit-card-2-1');
|
||||
$this->assertSession()->checkboxChecked('edit-card-2-2');
|
||||
|
||||
// Submit form: select only first option.
|
||||
$edit = [
|
||||
'card_2[0]' => TRUE,
|
||||
'card_2[1]' => FALSE,
|
||||
'card_2[2]' => FALSE,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_init, 'card_2', [0]);
|
||||
|
||||
// Display form: check that the right options are selected.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertSession()->checkboxChecked('edit-card-2-0');
|
||||
$this->assertSession()->checkboxNotChecked('edit-card-2-1');
|
||||
$this->assertSession()->checkboxNotChecked('edit-card-2-2');
|
||||
|
||||
// Submit form: select the three options while the field accepts only 2.
|
||||
$edit = [
|
||||
'card_2[0]' => TRUE,
|
||||
'card_2[1]' => TRUE,
|
||||
'card_2[2]' => TRUE,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('this field cannot hold more than 2 values');
|
||||
|
||||
// Submit form: uncheck all options.
|
||||
$edit = [
|
||||
'card_2[0]' => FALSE,
|
||||
'card_2[1]' => FALSE,
|
||||
'card_2[2]' => FALSE,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
// Check that the value was saved.
|
||||
$this->assertFieldValues($entity_init, 'card_2', []);
|
||||
|
||||
// Required checkbox with one option is auto-selected.
|
||||
$this->card2->setSetting('allowed_values', [99 => 'Only allowed value']);
|
||||
$this->card2->save();
|
||||
$field->setRequired(TRUE);
|
||||
$field->save();
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertSession()->checkboxChecked('edit-card-2-99');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the 'options_select' widget (single select).
|
||||
*/
|
||||
public function testSelectListSingle(): void {
|
||||
// Create an instance of the 'single value' field.
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $this->card1,
|
||||
'bundle' => 'entity_test',
|
||||
'required' => TRUE,
|
||||
]);
|
||||
$field->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($this->card1->getName(), [
|
||||
'type' => 'options_select',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Create an entity.
|
||||
$entity = EntityTest::create([
|
||||
'user_id' => 1,
|
||||
'name' => $this->randomMachineName(),
|
||||
]);
|
||||
$entity->save();
|
||||
$entity_init = clone $entity;
|
||||
|
||||
// Display form.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
// A required field without any value has a "none" option.
|
||||
$option = $this->assertSession()->optionExists('edit-card-1', '_none');
|
||||
$this->assertSame('- Select a value -', $option->getText());
|
||||
|
||||
// With no field data, nothing is selected.
|
||||
$this->assertTrue($this->assertSession()->optionExists('card_1', '_none')->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_1', 0)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_1', 1)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_1', 2)->isSelected());
|
||||
$this->assertSession()->responseContains('Some dangerous & unescaped markup');
|
||||
|
||||
// Submit form: select invalid 'none' option.
|
||||
$edit = ['card_1' => '_none'];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field->getName()} field is required.");
|
||||
|
||||
// Submit form: select first option.
|
||||
$edit = ['card_1' => 0];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_init, 'card_1', [0]);
|
||||
|
||||
// Display form: check that the right options are selected.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
// A required field with a value has no 'none' option.
|
||||
$this->assertSession()->optionNotExists('edit-card-1', '_none');
|
||||
$this->assertTrue($this->assertSession()->optionExists('card_1', 0)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_1', 1)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_1', 2)->isSelected());
|
||||
|
||||
// Make the field non required.
|
||||
$field->setRequired(FALSE);
|
||||
$field->save();
|
||||
|
||||
// Display form.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
// A non-required field has a 'none' option.
|
||||
$option = $this->assertSession()->optionExists('edit-card-1', '_none');
|
||||
$this->assertSame('- None -', $option->getText());
|
||||
// Submit form: Unselect the option.
|
||||
$edit = ['card_1' => '_none'];
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_init, 'card_1', []);
|
||||
|
||||
// Test optgroups.
|
||||
|
||||
$this->card1->setSetting('allowed_values', []);
|
||||
$this->card1->setSetting('allowed_values_function', '\Drupal\options_test\OptionsAllowedValues::simpleValues');
|
||||
$this->card1->save();
|
||||
|
||||
// Display form: with no field data, nothing is selected
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_1', 0)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_1', 1)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_1', 2)->isSelected());
|
||||
$this->assertSession()->responseContains('Some dangerous & unescaped markup');
|
||||
$this->assertSession()->responseContains('More <script>dangerous</script> markup');
|
||||
$this->assertSession()->responseContains('Group 1');
|
||||
|
||||
// Submit form: select first option.
|
||||
$edit = ['card_1' => 0];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_init, 'card_1', [0]);
|
||||
|
||||
// Display form: check that the right options are selected.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertTrue($this->assertSession()->optionExists('card_1', 0)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_1', 1)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_1', 2)->isSelected());
|
||||
|
||||
// Submit form: Unselect the option.
|
||||
$edit = ['card_1' => '_none'];
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_init, 'card_1', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the '#required_error' attribute for the select list.
|
||||
*/
|
||||
public function testSelectListRequiredErrorAttribute(): void {
|
||||
// Enable form alter hook.
|
||||
\Drupal::state()->set('options_test.form_alter_enable', TRUE);
|
||||
// Create an instance of the 'single value' field.
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $this->card1,
|
||||
'bundle' => 'entity_test',
|
||||
'required' => TRUE,
|
||||
]);
|
||||
$field->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($this->card1->getName(), [
|
||||
'type' => 'options_select',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Create an entity.
|
||||
$entity = EntityTest::create([
|
||||
'user_id' => 1,
|
||||
'name' => $this->randomMachineName(),
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
// A required field without any value has a "none" option.
|
||||
$option = $this->assertSession()->optionExists('edit-card-1', '_none');
|
||||
$this->assertSame('- Select a value -', $option->getText());
|
||||
|
||||
// Submit form: select invalid 'none' option.
|
||||
$edit = ['card_1' => '_none'];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->responseContains('This is custom message for required field.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the 'options_select' widget (multiple select).
|
||||
*/
|
||||
public function testSelectListMultiple(): void {
|
||||
// Create an instance of the 'multiple values' field.
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $this->card2,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($this->card2->getName(), [
|
||||
'type' => 'options_select',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Create an entity.
|
||||
$entity = EntityTest::create([
|
||||
'user_id' => 1,
|
||||
'name' => $this->randomMachineName(),
|
||||
]);
|
||||
$entity->save();
|
||||
$entity_init = clone $entity;
|
||||
|
||||
// Display form: with no field data, nothing is selected.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertTrue($this->assertSession()->optionExists('card_2', '_none')->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_2', 0)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_2', 1)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_2', 2)->isSelected());
|
||||
$this->assertSession()->responseContains('Some dangerous & unescaped markup');
|
||||
|
||||
// Submit form: select first and third options.
|
||||
$edit = ['card_2[]' => [0 => 0, 2 => 2]];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_init, 'card_2', [0, 2]);
|
||||
|
||||
// Display form: check that the right options are selected.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertTrue($this->assertSession()->optionExists('card_2', 0)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_2', 1)->isSelected());
|
||||
$this->assertTrue($this->assertSession()->optionExists('card_2', 2)->isSelected());
|
||||
|
||||
// Submit form: select only first option.
|
||||
$edit = ['card_2[]' => [0 => 0]];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_init, 'card_2', [0]);
|
||||
|
||||
// Display form: check that the right options are selected.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertTrue($this->assertSession()->optionExists('card_2', 0)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_2', 1)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_2', 2)->isSelected());
|
||||
|
||||
// Submit form: select the three options while the field accepts only 2.
|
||||
$edit = ['card_2[]' => [0 => 0, 1 => 1, 2 => 2]];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('this field cannot hold more than 2 values');
|
||||
|
||||
// Submit form: uncheck all options.
|
||||
$edit = ['card_2[]' => []];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_init, 'card_2', []);
|
||||
|
||||
// Test the 'None' option.
|
||||
|
||||
// Check that the 'none' option has no effect if actual options are selected
|
||||
// as well.
|
||||
$edit = ['card_2[]' => ['_none' => '_none', 0 => 0]];
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_init, 'card_2', [0]);
|
||||
|
||||
// Check that selecting the 'none' option empties the field.
|
||||
$edit = ['card_2[]' => ['_none' => '_none']];
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_init, 'card_2', []);
|
||||
|
||||
// A required select list does not have an empty key.
|
||||
$field->setRequired(TRUE);
|
||||
$field->save();
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertEmpty($this->assertSession()->selectExists('edit-card-2')->find('xpath', 'option[@value=""]'));
|
||||
|
||||
// We do not have to test that a required select list with one option is
|
||||
// auto-selected because the browser does it for us.
|
||||
|
||||
// Test optgroups.
|
||||
|
||||
// Use a callback function defining optgroups.
|
||||
$this->card2->setSetting('allowed_values', []);
|
||||
$this->card2->setSetting('allowed_values_function', '\Drupal\options_test\OptionsAllowedValues::simpleValues');
|
||||
$this->card2->save();
|
||||
$field->setRequired(FALSE);
|
||||
$field->save();
|
||||
|
||||
// Display form: with no field data, nothing is selected.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_2', 0)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_2', 1)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_2', 2)->isSelected());
|
||||
$this->assertSession()->responseContains('Some dangerous & unescaped markup');
|
||||
$this->assertSession()->responseContains('More <script>dangerous</script> markup');
|
||||
$this->assertSession()->responseContains('Group 1');
|
||||
|
||||
// Submit form: select first option.
|
||||
$edit = ['card_2[]' => [0 => 0]];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_init, 'card_2', [0]);
|
||||
|
||||
// Display form: check that the right options are selected.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertTrue($this->assertSession()->optionExists('card_2', 0)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_2', 1)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('card_2', 2)->isSelected());
|
||||
|
||||
// Submit form: Unselect the option.
|
||||
$edit = ['card_2[]' => ['_none' => '_none']];
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_init, 'card_2', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the 'options_select' widget (float values).
|
||||
*/
|
||||
public function testSelectListFloat(): void {
|
||||
|
||||
// Create an instance of the 'float value' field.
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $this->float,
|
||||
'bundle' => 'entity_test',
|
||||
'required' => TRUE,
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
$this->container
|
||||
->get('entity_type.manager')
|
||||
->getStorage('entity_form_display')
|
||||
->load('entity_test.entity_test.default')
|
||||
->setComponent($this->float->getName(), ['type' => 'options_select'])
|
||||
->save();
|
||||
|
||||
// Create an entity.
|
||||
$entity = EntityTest::create([
|
||||
'user_id' => 1,
|
||||
'name' => $this->randomMachineName(),
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
// Display form.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
|
||||
// With no field data, nothing is selected.
|
||||
$this->assertFalse($this->assertSession()->optionExists('float', 0)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('float', 1.5)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('float', 2)->isSelected());
|
||||
|
||||
// Submit form.
|
||||
$edit = ['float' => 1.5];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity, 'float', [1.5]);
|
||||
|
||||
// Display form: check that the right options are selected.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$this->assertFalse($this->assertSession()->optionExists('float', 0)->isSelected());
|
||||
$this->assertTrue($this->assertSession()->optionExists('float', 1.5)->isSelected());
|
||||
$this->assertFalse($this->assertSession()->optionExists('float', 2)->isSelected());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the 'options_select' and 'options_button' widget for empty value.
|
||||
*/
|
||||
public function testEmptyValue(): void {
|
||||
// Create an instance of the 'single value' field.
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $this->card1,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Change it to the check boxes/radio buttons widget.
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($this->card1->getName(), [
|
||||
'type' => 'options_buttons',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Create an entity.
|
||||
$entity = EntityTest::create([
|
||||
'user_id' => 1,
|
||||
'name' => $this->randomMachineName(),
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
// Display form: check that _none options are present and has label.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
// Verify that a test radio button has a "None" choice.
|
||||
$this->assertSession()->elementExists('xpath', '//div[@id="edit-card-1"]//input[@value="_none"]');
|
||||
// Verify that a test radio button has a "N/A" choice..
|
||||
$this->assertSession()->elementExists('xpath', '//div[@id="edit-card-1"]//label[@for="edit-card-1-none"]');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//div[@id="edit-card-1"]//label[@for="edit-card-1-none"]', "N/A");
|
||||
|
||||
// Change it to the select widget.
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($this->card1->getName(), [
|
||||
'type' => 'options_select',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display form: check that _none options are present and has label.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
// A required field without any value has a "none" option.
|
||||
$option = $this->assertSession()->optionExists('edit-card-1', '_none');
|
||||
$this->assertSame('- None -', $option->getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hook_options_list_alter().
|
||||
*
|
||||
* @see options_test_options_list_alter()
|
||||
*/
|
||||
public function testOptionsListAlter(): void {
|
||||
$field1 = FieldConfig::create([
|
||||
'field_storage' => $this->card1,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field1->save();
|
||||
|
||||
// Create a new field that will be altered.
|
||||
$card4 = FieldStorageConfig::create([
|
||||
'field_name' => 'card_4',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'list_integer',
|
||||
'cardinality' => 1,
|
||||
'settings' => [
|
||||
'allowed_values' => [
|
||||
0 => 'Zero',
|
||||
1 => 'One',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$card4->save();
|
||||
|
||||
$field2 = FieldConfig::create([
|
||||
'field_storage' => $card4,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field2->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Change it to the check boxes/radio buttons widget.
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($this->card1->getName(), [
|
||||
'type' => 'options_select',
|
||||
])
|
||||
->setComponent($card4->getName(), [
|
||||
'type' => 'options_select',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Create an entity.
|
||||
$entity = EntityTest::create([
|
||||
'user_id' => 1,
|
||||
'name' => $this->randomMachineName(),
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
// Display form: check that _none options are present.
|
||||
$this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
|
||||
$xpath = '//select[@id=:id]//option[@value="_none" and text()=:label]';
|
||||
$xpath_args = [':id' => 'edit-card-1', ':label' => '- None -'];
|
||||
$this->assertSession()->elementExists('xpath', $this->assertSession()->buildXPathQuery($xpath, $xpath_args));
|
||||
$xpath_args = [':id' => 'edit-card-4', ':label' => '- Select something -'];
|
||||
$this->assertSession()->elementExists('xpath', $this->assertSession()->buildXPathQuery($xpath, $xpath_args));
|
||||
|
||||
// Display form: check that options are displayed correctly.
|
||||
$this->assertSession()->optionExists('card_1', 0);
|
||||
$this->assertSession()->optionExists('card_1', 1);
|
||||
$this->assertSession()->optionNotExists('card_4', 0);
|
||||
$this->assertSession()->optionExists('card_4', 1);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,386 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\FunctionalJavascript;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiJSTestTrait;
|
||||
|
||||
/**
|
||||
* Tests the Options field allowed values UI functionality.
|
||||
*
|
||||
* @group options
|
||||
* @group #slow
|
||||
*/
|
||||
class OptionsFieldUIAllowedValuesTest extends WebDriverTestBase {
|
||||
|
||||
use FieldUiJSTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'options',
|
||||
'field_ui',
|
||||
'block',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Machine name of the created content type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* Name of the option field.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* Admin path to manage field storage settings.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $adminPath;
|
||||
|
||||
/**
|
||||
* Node form path for created content type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $nodeFormPath;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
|
||||
// Create test user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'bypass node access',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$type = $this->drupalCreateContentType(['type' => 'plan']);
|
||||
$this->type = $type->id();
|
||||
$this->nodeFormPath = 'node/add/' . $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests option types allowed values.
|
||||
*
|
||||
* @dataProvider providerTestOptionsAllowedValues
|
||||
*/
|
||||
public function testOptionsAllowedValues($option_type, $options, $is_string_option, string $add_row_method): void {
|
||||
$assert = $this->assertSession();
|
||||
$this->fieldName = 'field_options_text';
|
||||
$this->createOptionsField($option_type);
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
$this->drupalGet($this->adminPath);
|
||||
|
||||
$i = 0;
|
||||
$expected_rows = 1;
|
||||
$this->assertAllowValuesRowCount(1);
|
||||
foreach ($options as $option_key => $option_label) {
|
||||
$enter_element_name = $label_element_name = "field_storage[subform][settings][allowed_values][table][$i][item][label]";
|
||||
$page->fillField($label_element_name, $option_label);
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$key_element_name = "field_storage[subform][settings][allowed_values][table][$i][item][key]";
|
||||
|
||||
// Add keys if not string option list.
|
||||
if (!$is_string_option) {
|
||||
$this->pressEnterOnElement("[name=\"$label_element_name\"]");
|
||||
// Assert that pressing enter on label field does not create the new
|
||||
// row if the key field is visible.
|
||||
$this->assertAllowValuesRowCount($expected_rows);
|
||||
$enter_element_name = $key_element_name;
|
||||
$this->assertHasFocusByAttribute('name', $key_element_name);
|
||||
$page->fillField($key_element_name, $option_key);
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
}
|
||||
else {
|
||||
$this->assertFalse($assert->fieldExists($key_element_name)->isVisible());
|
||||
}
|
||||
switch ($add_row_method) {
|
||||
case 'Press button':
|
||||
$page->pressButton('Add another item');
|
||||
break;
|
||||
|
||||
case 'Enter button':
|
||||
$button = $assert->buttonExists('Add another item');
|
||||
$this->pressEnterOnElement('[data-drupal-selector="' . $button->getAttribute('data-drupal-selector') . '"]');
|
||||
break;
|
||||
|
||||
case 'Enter element':
|
||||
// If testing using the "enter" key while focused on element there a
|
||||
// few different scenarios to test.
|
||||
switch ($i) {
|
||||
case 0:
|
||||
// For string options the machine name input can be exposed which
|
||||
// will mean the label input will no longer create the next row.
|
||||
if ($is_string_option) {
|
||||
$this->exposeOptionMachineName($expected_rows);
|
||||
$this->pressEnterOnElement("[name=\"$enter_element_name\"]");
|
||||
$this->assertHasFocusByAttribute('name', $key_element_name);
|
||||
// Ensure that pressing enter while focused on the label input
|
||||
// did not create a new row if the machine name field is
|
||||
// visible.
|
||||
$this->assertAllowValuesRowCount($expected_rows);
|
||||
$enter_element_name = $key_element_name;
|
||||
}
|
||||
break;
|
||||
}
|
||||
$this->pressEnterOnElement("[name=\"$enter_element_name\"]");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \UnexpectedValueException("Unknown method $add_row_method");
|
||||
}
|
||||
|
||||
$i++;
|
||||
$expected_rows++;
|
||||
$this->assertSession()->waitForElementVisible('css', "[name='field_storage[subform][settings][allowed_values][table][$i][item][label]']");
|
||||
$this->assertHasFocusByAttribute('name', "field_storage[subform][settings][allowed_values][table][$i][item][label]");
|
||||
$this->assertAllowValuesRowCount($expected_rows);
|
||||
|
||||
if ($is_string_option) {
|
||||
// Expose the key input for string options for the previous row to test
|
||||
// shifting focus from the label to key inputs on the previous row by
|
||||
// pressing enter.
|
||||
$this->exposeOptionMachineName($expected_rows - 1);
|
||||
}
|
||||
// Test that pressing enter on the label input on previous row will shift
|
||||
// focus to key input of that row.
|
||||
$this->pressEnterOnElement("[name=\"$label_element_name\"]");
|
||||
$this->assertHasFocusByAttribute('name', $key_element_name);
|
||||
$this->assertAllowValuesRowCount($expected_rows);
|
||||
}
|
||||
$page->pressButton('Save settings');
|
||||
$this->assertTrue($this->assertSession()->waitForText('Saved field_options_text configuration.'));
|
||||
|
||||
$option_labels = array_values($options);
|
||||
$this->assertCount(3, $option_labels);
|
||||
|
||||
// Test the order of the option list on node form.
|
||||
$this->drupalGet($this->nodeFormPath);
|
||||
$this->assertNodeFormOrder(['- None -', $option_labels[0], $option_labels[1], $option_labels[2]]);
|
||||
|
||||
// Test the order of the option list on admin path.
|
||||
$this->drupalGet($this->adminPath);
|
||||
$this->assertOrder([$option_labels[0], $option_labels[1], $option_labels[2], ''], $is_string_option);
|
||||
$drag_handle = $page->find('css', '[data-drupal-selector="edit-field-storage-subform-settings-allowed-values-table-0"] .tabledrag-handle');
|
||||
$target = $page->find('css', '[data-drupal-selector="edit-field-storage-subform-settings-allowed-values-table-2"]');
|
||||
|
||||
// Change the order the items appear.
|
||||
$drag_handle->dragTo($target);
|
||||
$this->assertOrder([$option_labels[1], $option_labels[2], $option_labels[0], ''], $is_string_option);
|
||||
$page->pressButton('Save settings');
|
||||
$this->assertTrue($this->assertSession()->waitForText('Saved field_options_text configuration.'));
|
||||
|
||||
$this->drupalGet($this->nodeFormPath);
|
||||
$this->assertNodeFormOrder(['- None -', $option_labels[1], $option_labels[2], $option_labels[0]]);
|
||||
|
||||
$this->drupalGet($this->adminPath);
|
||||
|
||||
// Confirm the change in order was saved.
|
||||
$this->assertOrder([$option_labels[1], $option_labels[2], $option_labels[0], ''], $is_string_option);
|
||||
|
||||
// Delete an item.
|
||||
$page->pressButton('remove_row_button__1');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertOrder([$option_labels[1], $option_labels[0], ''], $is_string_option);
|
||||
$page->pressButton('Save settings');
|
||||
$this->assertTrue($this->assertSession()->waitForText('Saved field_options_text configuration.'));
|
||||
|
||||
$this->drupalGet($this->nodeFormPath);
|
||||
$this->assertNodeFormOrder(['- None -', $option_labels[1], $option_labels[0]]);
|
||||
|
||||
$this->drupalGet($this->adminPath);
|
||||
|
||||
// Confirm the item removal was saved.
|
||||
$this->assertOrder([$option_labels[1], $option_labels[0], ''], $is_string_option);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the order of provided option list on admin path.
|
||||
*
|
||||
* @param array $expected
|
||||
* Expected order.
|
||||
* @param bool $is_string_option
|
||||
* Whether the request is for string option list.
|
||||
*/
|
||||
protected function assertOrder($expected, $is_string_option) {
|
||||
$page = $this->getSession()->getPage();
|
||||
if ($is_string_option) {
|
||||
$inputs = $page->findAll('css', '.draggable .form-text.machine-name-source');
|
||||
}
|
||||
else {
|
||||
$inputs = $page->findAll('css', '.draggable .form-text');
|
||||
}
|
||||
foreach ($expected as $step => $expected_input_value) {
|
||||
$value = $inputs[$step]->getValue();
|
||||
$this->assertSame($expected_input_value, $value, "Item $step should be $expected_input_value, but got $value");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the order of provided option list on node form.
|
||||
*
|
||||
* @param array $expected
|
||||
* Expected order.
|
||||
*/
|
||||
protected function assertNodeFormOrder($expected) {
|
||||
$elements = $this->assertSession()->selectExists('field_options_text')->findAll('css', 'option');
|
||||
$elements = array_map(function ($element) {
|
||||
return $element->getText();
|
||||
}, $elements);
|
||||
$this->assertSame($expected, $elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create list field of a given type.
|
||||
*
|
||||
* @param string $type
|
||||
* One of 'list_integer', 'list_float' or 'list_string'.
|
||||
*/
|
||||
protected function createOptionsField($type) {
|
||||
// Create a field.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'node',
|
||||
'type' => $type,
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'node',
|
||||
'bundle' => $this->type,
|
||||
])->save();
|
||||
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('node', $this->type)
|
||||
->setComponent($this->fieldName)
|
||||
->save();
|
||||
|
||||
$this->adminPath = 'admin/structure/types/manage/' . $this->type . '/fields/node.' . $this->type . '.' . $this->fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Presses "Enter" on the specified element.
|
||||
*
|
||||
* @param string $selector
|
||||
* Current element having focus.
|
||||
*/
|
||||
private function pressEnterOnElement(string $selector): void {
|
||||
$javascript = <<<JS
|
||||
const element = document.querySelector('$selector');
|
||||
const event = new KeyboardEvent('keypress', { key: 'Enter', keyCode: 13, bubbles: true });
|
||||
element.dispatchEvent(event);
|
||||
JS;
|
||||
$this->getSession()->executeScript($javascript);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testOptionsAllowedValues().
|
||||
*
|
||||
* @return array
|
||||
* Array of arrays with the following elements:
|
||||
* - Option type.
|
||||
* - Array of option type values.
|
||||
* - Whether option type is string type or not.
|
||||
* - The method which should be used to add another row to the table. The
|
||||
* possible values are 'Press button', 'Enter button' or 'Enter element'.
|
||||
*/
|
||||
public static function providerTestOptionsAllowedValues() {
|
||||
$type_cases = [
|
||||
'List integer' => [
|
||||
'list_integer',
|
||||
[1 => 'First', 2 => 'Second', 3 => 'Third'],
|
||||
FALSE,
|
||||
],
|
||||
'List float' => [
|
||||
'list_float',
|
||||
['0.1' => 'First', '0.2' => 'Second', '0.3' => 'Third'],
|
||||
FALSE,
|
||||
],
|
||||
'List string' => [
|
||||
'list_string',
|
||||
['0' => '0', '1' => '1', 'two' => 'two'],
|
||||
TRUE,
|
||||
],
|
||||
];
|
||||
// Test adding options for each option field type using several possible
|
||||
// methods that could be used for navigating the options list:
|
||||
// - Press button: add a new item by pressing the 'Add another item'
|
||||
// button using mouse.
|
||||
// - Enter button: add a new item by pressing the 'Add another item'
|
||||
// button using enter key on the keyboard.
|
||||
// - Enter element: add a new item by pressing enter on the last text
|
||||
// field inside the table.
|
||||
$test_cases = [];
|
||||
foreach ($type_cases as $key => $type_case) {
|
||||
foreach (['Press button', 'Enter button', 'Enter element'] as $add_more_method) {
|
||||
$test_cases["$key: $add_more_method"] = array_merge($type_case, [$add_more_method]);
|
||||
}
|
||||
}
|
||||
return $test_cases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the count of the allowed values rows.
|
||||
*
|
||||
* @param int $expected_count
|
||||
* The expected row count.
|
||||
*/
|
||||
private function assertAllowValuesRowCount(int $expected_count): void {
|
||||
$this->assertCount(
|
||||
$expected_count,
|
||||
$this->getSession()->getPage()->findAll('css', '#allowed-values-order tr.draggable')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes the machine name input for a row.
|
||||
*
|
||||
* @param int $row
|
||||
* The row number.
|
||||
*/
|
||||
private function exposeOptionMachineName(int $row): void {
|
||||
$index = $row - 1;
|
||||
$rows = $this->getSession()->getPage()->findAll('css', '#allowed-values-order tr.draggable');
|
||||
$this->assertSession()->buttonExists('Edit', $rows[$index])->click();
|
||||
$this->assertSession()->waitForElementVisible('css', "[name='field_storage[subform][settings][allowed_values][table][$index][item][key]']");
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts an element specified by an attribute value has focus.
|
||||
*
|
||||
* @param string $name
|
||||
* The attribute name.
|
||||
* @param string $value
|
||||
* The attribute value.
|
||||
*
|
||||
* @todo Replace with assertHasFocus() in https://drupal.org/i/3041768.
|
||||
*/
|
||||
private function assertHasFocusByAttribute(string $name, string $value): void {
|
||||
$active_element = $this->getSession()->evaluateScript('document.activeElement');
|
||||
$this->assertSame($value, $active_element->attribute($name));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\FunctionalJavascript;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiJSTestTrait;
|
||||
|
||||
/**
|
||||
* Tests the Options field UI functionality.
|
||||
*
|
||||
* @group options
|
||||
* @group #slow
|
||||
*/
|
||||
class OptionsFieldUITest extends WebDriverTestBase {
|
||||
|
||||
use FieldUiJSTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'options',
|
||||
'field_ui',
|
||||
'block',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Machine name of the created content type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* Name of the option field.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* Admin path to manage field storage settings.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $adminPath;
|
||||
|
||||
/**
|
||||
* Node form path for created content type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $nodeFormPath;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
|
||||
// Create test user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'bypass node access',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$type = $this->drupalCreateContentType(['type' => 'plan']);
|
||||
$this->type = $type->id();
|
||||
$this->nodeFormPath = 'node/add/' . $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the allowed options are available to the default value widget.
|
||||
*/
|
||||
public function testDefaultValueOptions(): void {
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
$bundle_path = 'admin/structure/types/manage/' . $this->type;
|
||||
// Create a field of type list:string.
|
||||
$this->fieldUIAddNewFieldJS($bundle_path, 'test_string_list', 'Test string list', 'list_string', FALSE);
|
||||
$page->findField('field_storage[subform][settings][allowed_values][table][0][item][label]')->setValue('first');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$page->findField('set_default_value')->setValue(TRUE);
|
||||
// Assert that the option added in the subform is available to the default
|
||||
// value field.
|
||||
$this->assertSession()->waitForElement('css', '[name="default_value_input[field_test_int_list]"] option:contains("first")');
|
||||
$page->pressButton('Add another item');
|
||||
$assert_session->waitForField('field_storage[subform][settings][allowed_values][table][1][item][label]');
|
||||
$field_input = $page->find('css', '[name="field_storage[subform][settings][allowed_values][table][1][item][label]"]');
|
||||
$field_input->setValue('second');
|
||||
$this->assertNotNull($assert_session->waitForElement('css', '[name="default_value_input[field_test_string_list]"] option:contains("second")'));
|
||||
$assert_session->optionExists('default_value_input[field_test_string_list]', 'second');
|
||||
$page->selectFieldOption('default_value_input[field_test_string_list]', 'second');
|
||||
$page->find('css', '.ui-dialog-buttonset button:contains("Save")')->press();
|
||||
$this->assertTrue($assert_session->waitForText('Saved Test string list configuration.'));
|
||||
|
||||
// Create a field of type list:integer.
|
||||
$this->fieldUIAddNewFieldJS($bundle_path, 'test_int_list', 'Test int list', 'list_integer', FALSE);
|
||||
$page->findField('field_storage[subform][settings][allowed_values][table][0][item][label]')->setValue('first');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$page->findField('set_default_value')->setValue(TRUE);
|
||||
$page->selectFieldOption('default_value_input[field_test_int_list]', 'first');
|
||||
// Assert that no validation is performed.
|
||||
$assert_session->statusMessageNotContains('Value field is required.');
|
||||
$field_input = $page->find('css', '[name="field_storage[subform][settings][allowed_values][table][0][item][key]"]');
|
||||
$field_input->setValue(1);
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$page->checkField('set_default_value');
|
||||
$this->assertSession()->waitForElement('css', '[name="default_value_input[field_test_int_list]"] option:contains("first")');
|
||||
// Assert that the option added in the subform is available to the default
|
||||
// value field.
|
||||
$this->assertSession()->optionExists('default_value_input[field_test_int_list]', 'first');
|
||||
$page->selectFieldOption('default_value_input[field_test_int_list]', 'first');
|
||||
$page->find('css', '.ui-dialog-buttonset button:contains("Save")')->press();
|
||||
$this->assertTrue($this->assertSession()->waitForText('Saved Test int list configuration.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create list field of a given type.
|
||||
*
|
||||
* @param string $type
|
||||
* One of 'list_integer', 'list_float' or 'list_string'.
|
||||
*/
|
||||
protected function createOptionsField($type): void {
|
||||
// Create a field.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'node',
|
||||
'type' => $type,
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'node',
|
||||
'bundle' => $this->type,
|
||||
])->save();
|
||||
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('node', $this->type)
|
||||
->setComponent($this->fieldName)
|
||||
->save();
|
||||
|
||||
$this->adminPath = 'admin/structure/types/manage/' . $this->type . '/fields/node.' . $this->type . '.' . $this->fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests `list_string` machine name with special characters.
|
||||
*/
|
||||
public function testMachineNameSpecialCharacters(): void {
|
||||
$this->fieldName = 'field_options_text';
|
||||
$this->createOptionsField('list_string');
|
||||
$this->drupalGet($this->adminPath);
|
||||
|
||||
$label_element_name = "field_storage[subform][settings][allowed_values][table][0][item][label]";
|
||||
$this->getSession()->getPage()->fillField($label_element_name, 'Hello world');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->exposeOptionMachineName(1);
|
||||
|
||||
$key_element_name = "field_storage[subform][settings][allowed_values][table][0][item][key]";
|
||||
|
||||
// Ensure that the machine name was generated correctly.
|
||||
$this->assertSession()->fieldValueEquals($key_element_name, 'hello_world');
|
||||
|
||||
// Ensure that the machine name can be overridden with a value that includes
|
||||
// special characters.
|
||||
$this->getSession()->getPage()->fillField($key_element_name, '.hello #world');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->getSession()->getPage()->pressButton('Save settings');
|
||||
$this->assertSession()->statusMessageContains("Saved {$this->fieldName} configuration.");
|
||||
|
||||
// Ensure that the machine name was saved correctly.
|
||||
$allowed_values = FieldStorageConfig::loadByName('node', $this->fieldName)
|
||||
->getSetting('allowed_values');
|
||||
$this->assertSame(['.hello #world'], array_keys($allowed_values));
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes the machine name input for a row.
|
||||
*
|
||||
* @param int $row
|
||||
* The row number.
|
||||
*/
|
||||
private function exposeOptionMachineName(int $row): void {
|
||||
$index = $row - 1;
|
||||
$rows = $this->getSession()->getPage()->findAll('css', '#allowed-values-order tr.draggable');
|
||||
$this->assertSession()->buttonExists('Edit', $rows[$index])->click();
|
||||
$this->assertSession()->waitForElementVisible('css', "[name='field_storage[subform][settings][allowed_values][table][$index][item][key]']");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\entity_test\Entity\EntityTestRev;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests the options allowed values api.
|
||||
*
|
||||
* @group options
|
||||
*/
|
||||
class OptionsDynamicValuesApiTest extends OptionsFieldUnitTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'options_test',
|
||||
'node',
|
||||
];
|
||||
|
||||
/**
|
||||
* The created entity.
|
||||
*/
|
||||
protected EntityInterface $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('entity_test_rev');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('node');
|
||||
|
||||
$this->fieldStorage = FieldStorageConfig::create([
|
||||
'field_name' => 'test_options',
|
||||
'entity_type' => 'entity_test_rev',
|
||||
'type' => 'list_string',
|
||||
'cardinality' => 1,
|
||||
'settings' => [
|
||||
'allowed_values_function' => '\Drupal\options_test\OptionsAllowedValues::dynamicValues',
|
||||
],
|
||||
]);
|
||||
$this->fieldStorage->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'field_name' => 'test_options',
|
||||
'entity_type' => 'entity_test_rev',
|
||||
'bundle' => 'entity_test_rev',
|
||||
'required' => TRUE,
|
||||
])->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('entity_test_rev', 'entity_test_rev')
|
||||
->setComponent('test_options', [
|
||||
'type' => 'options_select',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Create an entity and prepare test data that will be used by
|
||||
// \Drupal\options_test\OptionsAllowedValues::dynamicValues().
|
||||
$values = [
|
||||
'user_id' => 2,
|
||||
'name' => $this->randomMachineName(),
|
||||
];
|
||||
$this->entity = EntityTestRev::create($values);
|
||||
$this->entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests options_allowed_values().
|
||||
*
|
||||
* @see \Drupal\options_test\OptionsAllowedValues::dynamicValues()
|
||||
*/
|
||||
public function testOptionsAllowedValues(): void {
|
||||
// Test allowed values without passed $items.
|
||||
$values = options_allowed_values($this->fieldStorage);
|
||||
$this->assertEquals([], $values);
|
||||
|
||||
$values = options_allowed_values($this->fieldStorage, $this->entity);
|
||||
|
||||
$expected_values = [
|
||||
$this->entity->label(),
|
||||
$this->entity->toUrl()->toString(),
|
||||
$this->entity->uuid(),
|
||||
$this->entity->bundle(),
|
||||
];
|
||||
$expected_values = array_combine($expected_values, $expected_values);
|
||||
$this->assertEquals($expected_values, $values);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\entity_test\Entity\EntityTestRev;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests the Options field allowed values function.
|
||||
*
|
||||
* @group options
|
||||
*/
|
||||
class OptionsDynamicValuesValidationTest extends OptionsFieldUnitTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'options_test',
|
||||
'node',
|
||||
];
|
||||
|
||||
/**
|
||||
* The created entity.
|
||||
*/
|
||||
protected EntityInterface $entity;
|
||||
|
||||
/**
|
||||
* Test data.
|
||||
*/
|
||||
protected array $test;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('entity_test_rev');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('node');
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'test_options',
|
||||
'entity_type' => 'entity_test_rev',
|
||||
'type' => 'list_string',
|
||||
'cardinality' => 1,
|
||||
'settings' => [
|
||||
'allowed_values_function' => '\Drupal\options_test\OptionsAllowedValues::dynamicValues',
|
||||
],
|
||||
])->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'field_name' => 'test_options',
|
||||
'entity_type' => 'entity_test_rev',
|
||||
'bundle' => 'entity_test_rev',
|
||||
'required' => TRUE,
|
||||
])->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('entity_test_rev', 'entity_test_rev')
|
||||
->setComponent('test_options', [
|
||||
'type' => 'options_select',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Create an entity and prepare test data that will be used by
|
||||
// \Drupal\options_test\OptionsAllowedValues::dynamicValues().
|
||||
$values = [
|
||||
'user_id' => 2,
|
||||
'name' => $this->randomMachineName(),
|
||||
];
|
||||
$this->entity = EntityTestRev::create($values);
|
||||
$this->entity->save();
|
||||
|
||||
$this->test = [
|
||||
'label' => $this->entity->label(),
|
||||
'uuid' => $this->entity->uuid(),
|
||||
'bundle' => $this->entity->bundle(),
|
||||
'uri' => $this->entity->toUrl()->toString(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that allowed values function gets the entity.
|
||||
*/
|
||||
public function testDynamicAllowedValues(): void {
|
||||
// Verify that validation passes against every value we had.
|
||||
foreach ($this->test as $key => $value) {
|
||||
$this->entity->test_options->value = $value;
|
||||
$violations = $this->entity->test_options->validate();
|
||||
$this->assertCount(0, $violations, "$key is a valid value");
|
||||
}
|
||||
|
||||
// Now verify that validation does not pass against anything else.
|
||||
foreach ($this->test as $key => $value) {
|
||||
$this->entity->test_options->value = is_numeric($value) ? (100 - $value) : ('X' . $value);
|
||||
$violations = $this->entity->test_options->validate();
|
||||
$this->assertCount(1, $violations, "$key is not a valid value");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
111
web/core/modules/options/tests/src/Kernel/OptionsFieldTest.php
Normal file
111
web/core/modules/options/tests/src/Kernel/OptionsFieldTest.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests for the 'Options' field types.
|
||||
*
|
||||
* @group options
|
||||
*/
|
||||
class OptionsFieldTest extends OptionsFieldUnitTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['options'];
|
||||
|
||||
/**
|
||||
* Tests that allowed values can be updated.
|
||||
*/
|
||||
public function testUpdateAllowedValues(): void {
|
||||
// All three options appear.
|
||||
$entity = EntityTest::create();
|
||||
$form = \Drupal::service('entity.form_builder')->getForm($entity);
|
||||
$this->assertArrayHasKey(1, $form[$this->fieldName]['widget'], 'Option 1 exists');
|
||||
$this->assertArrayHasKey(2, $form[$this->fieldName]['widget'], 'Option 2 exists');
|
||||
$this->assertArrayHasKey(3, $form[$this->fieldName]['widget'], 'Option 3 exists');
|
||||
|
||||
// Use one of the values in an actual entity, and check that this value
|
||||
// cannot be removed from the list.
|
||||
$entity = EntityTest::create();
|
||||
$entity->{$this->fieldName}->value = 1;
|
||||
$entity->save();
|
||||
$this->fieldStorage->setSetting('allowed_values', [2 => 'Two']);
|
||||
try {
|
||||
$this->fieldStorage->save();
|
||||
$this->fail('Cannot update a list field storage to not include keys with existing data.');
|
||||
}
|
||||
catch (FieldStorageDefinitionUpdateForbiddenException) {
|
||||
// Expected exception; just continue testing.
|
||||
}
|
||||
// Empty the value, so that we can actually remove the option.
|
||||
unset($entity->{$this->fieldName});
|
||||
$entity->save();
|
||||
|
||||
// Removed options do not appear.
|
||||
$this->fieldStorage->setSetting('allowed_values', [2 => 'Two']);
|
||||
$this->fieldStorage->save();
|
||||
$entity = EntityTest::create();
|
||||
$form = \Drupal::service('entity.form_builder')->getForm($entity);
|
||||
$this->assertArrayNotHasKey(1, $form[$this->fieldName]['widget'], 'Option 1 does not exist');
|
||||
$this->assertArrayHasKey(2, $form[$this->fieldName]['widget'], 'Option 2 exists');
|
||||
$this->assertArrayNotHasKey(3, $form[$this->fieldName]['widget'], 'Option 3 does not exist');
|
||||
|
||||
// Completely new options appear.
|
||||
$this->fieldStorage->setSetting('allowed_values', [10 => 'Update', 20 => 'Twenty']);
|
||||
$this->fieldStorage->save();
|
||||
// The entity holds an outdated field object with the old allowed values
|
||||
// setting, so we need to reinitialize the entity object.
|
||||
$entity = EntityTest::create();
|
||||
$form = \Drupal::service('entity.form_builder')->getForm($entity);
|
||||
$this->assertArrayNotHasKey(1, $form[$this->fieldName]['widget'], 'Option 1 does not exist');
|
||||
$this->assertArrayNotHasKey(2, $form[$this->fieldName]['widget'], 'Option 2 does not exist');
|
||||
$this->assertArrayNotHasKey(3, $form[$this->fieldName]['widget'], 'Option 3 does not exist');
|
||||
$this->assertArrayHasKey(10, $form[$this->fieldName]['widget'], 'Option 10 exists');
|
||||
$this->assertArrayHasKey(20, $form[$this->fieldName]['widget'], 'Option 20 exists');
|
||||
|
||||
// Options are reset when a new field with the same name is created.
|
||||
$this->fieldStorage->delete();
|
||||
FieldStorageConfig::create($this->fieldStorageDefinition)->save();
|
||||
FieldConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'required' => TRUE,
|
||||
])->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => 'options_buttons',
|
||||
])
|
||||
->save();
|
||||
$entity = EntityTest::create();
|
||||
$form = \Drupal::service('entity.form_builder')->getForm($entity);
|
||||
$this->assertArrayHasKey(1, $form[$this->fieldName]['widget'], 'Option 1 exists');
|
||||
$this->assertArrayHasKey(2, $form[$this->fieldName]['widget'], 'Option 2 exists');
|
||||
$this->assertArrayHasKey(3, $form[$this->fieldName]['widget'], 'Option 3 exists');
|
||||
|
||||
// Test the generateSampleValue() method.
|
||||
$entity = EntityTest::create();
|
||||
$entity->{$this->fieldName}->generateSampleItems();
|
||||
$this->entityValidateAndSave($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that ::generateSampleItems does not fail with empty allowed values.
|
||||
*/
|
||||
public function testGenerateSampleItemsWithNoAllowedValues(): void {
|
||||
$this->fieldStorage->setSetting('allowed_values', [])->save();
|
||||
$entity = EntityTest::create();
|
||||
$value = $entity->{$this->fieldName}->generateSampleItems();
|
||||
$this->assertNull($value);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Kernel;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\Tests\field\Kernel\FieldKernelTestBase;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Base class for Options module integration tests.
|
||||
*/
|
||||
abstract class OptionsFieldUnitTestBase extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['options'];
|
||||
|
||||
/**
|
||||
* The field name used in the test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName = 'test_options';
|
||||
|
||||
/**
|
||||
* The field storage definition used to created the field storage.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldStorageDefinition;
|
||||
|
||||
/**
|
||||
* The list field storage used in the test.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldStorageConfig
|
||||
*/
|
||||
protected $fieldStorage;
|
||||
|
||||
/**
|
||||
* The list field used in the test.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldConfig
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->fieldStorageDefinition = [
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'list_integer',
|
||||
'cardinality' => 1,
|
||||
'settings' => [
|
||||
'allowed_values' => [1 => 'One', 2 => 'Two', 3 => 'Three'],
|
||||
],
|
||||
];
|
||||
$this->fieldStorage = FieldStorageConfig::create($this->fieldStorageDefinition);
|
||||
$this->fieldStorage->save();
|
||||
|
||||
$this->field = FieldConfig::create([
|
||||
'field_storage' => $this->fieldStorage,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$this->field->save();
|
||||
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => 'options_buttons',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Kernel;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
|
||||
/**
|
||||
* Tests the Options field type formatters.
|
||||
*
|
||||
* @group options
|
||||
* @see \Drupal\options\Plugin\Field\FieldFormatter\OptionsDefaultFormatter
|
||||
* @see \Drupal\options\Plugin\Field\FieldFormatter\OptionsKeyFormatter
|
||||
*/
|
||||
class OptionsFormattersTest extends OptionsFieldUnitTestBase {
|
||||
|
||||
/**
|
||||
* Tests the formatters.
|
||||
*/
|
||||
public function testFormatter(): void {
|
||||
$entity = EntityTest::create();
|
||||
$entity->{$this->fieldName}->value = 1;
|
||||
|
||||
$items = $entity->get($this->fieldName);
|
||||
|
||||
$build = $items->view();
|
||||
$this->assertEquals('list_default', $build['#formatter'], 'Ensure to fall back to the default formatter.');
|
||||
$this->assertEquals('One', $build[0]['#markup']);
|
||||
|
||||
$build = $items->view(['type' => 'list_key']);
|
||||
$this->assertEquals('list_key', $build['#formatter'], 'The chosen formatter is used.');
|
||||
$this->assertEquals(1, (string) $build[0]['#markup']);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Kernel\Views;
|
||||
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests file views data.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileViewsDataTest extends ViewsKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'file',
|
||||
'views',
|
||||
'entity_test',
|
||||
'user',
|
||||
'field',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE): void {
|
||||
parent::setUp($import_test_views);
|
||||
$this->installEntitySchema('entity_test');
|
||||
$this->installEntitySchema('entity_test_mul');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests views data generated for file field relationship.
|
||||
*
|
||||
* @see file_field_views_data()
|
||||
* @see file_field_views_data_views_data_alter()
|
||||
*/
|
||||
public function testRelationshipViewsData(): void {
|
||||
// Create file field to entity_test.
|
||||
FieldStorageConfig::create([
|
||||
'entity_type' => 'entity_test',
|
||||
'field_name' => 'field_base_file',
|
||||
'type' => 'file',
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'entity_test',
|
||||
'field_name' => 'field_base_file',
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
// Check the generated views data.
|
||||
$views_data = Views::viewsData()->get('entity_test__field_base_file');
|
||||
$relationship = $views_data['field_base_file_target_id']['relationship'];
|
||||
$this->assertEquals('standard', $relationship['id']);
|
||||
$this->assertEquals('file_managed', $relationship['base']);
|
||||
$this->assertEquals('fid', $relationship['base field']);
|
||||
$this->assertEquals('file', $relationship['entity type']);
|
||||
// Check the backwards reference.
|
||||
$views_data = Views::viewsData()->get('file_managed');
|
||||
$relationship = $views_data['reverse_field_base_file_entity_test']['relationship'];
|
||||
$this->assertEquals('entity_reverse', $relationship['id']);
|
||||
$this->assertEquals('entity_test', $relationship['base']);
|
||||
$this->assertEquals('id', $relationship['base field']);
|
||||
$this->assertEquals('entity_test__field_base_file', $relationship['field table']);
|
||||
$this->assertEquals('field_base_file_target_id', $relationship['field field']);
|
||||
$this->assertEquals('field_base_file', $relationship['field_name']);
|
||||
$this->assertEquals('entity_test', $relationship['entity_type']);
|
||||
$this->assertEquals(['field' => 'deleted', 'value' => 0, 'numeric' => TRUE], $relationship['join_extra'][0]);
|
||||
|
||||
// Create file field to entity_test_mul.
|
||||
FieldStorageConfig::create([
|
||||
'entity_type' => 'entity_test_mul',
|
||||
'field_name' => 'field_data_file',
|
||||
'type' => 'file',
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'entity_test_mul',
|
||||
'field_name' => 'field_data_file',
|
||||
'bundle' => 'entity_test_mul',
|
||||
])->save();
|
||||
// Check the generated views data.
|
||||
$views_data = Views::viewsData()->get('entity_test_mul__field_data_file');
|
||||
$relationship = $views_data['field_data_file_target_id']['relationship'];
|
||||
$this->assertEquals('standard', $relationship['id']);
|
||||
$this->assertEquals('file_managed', $relationship['base']);
|
||||
$this->assertEquals('fid', $relationship['base field']);
|
||||
$this->assertEquals('file', $relationship['entity type']);
|
||||
// Check the backwards reference.
|
||||
$views_data = Views::viewsData()->get('file_managed');
|
||||
$relationship = $views_data['reverse_field_data_file_entity_test_mul']['relationship'];
|
||||
$this->assertEquals('entity_reverse', $relationship['id']);
|
||||
$this->assertEquals('entity_test_mul_property_data', $relationship['base']);
|
||||
$this->assertEquals('id', $relationship['base field']);
|
||||
$this->assertEquals('entity_test_mul__field_data_file', $relationship['field table']);
|
||||
$this->assertEquals('field_data_file_target_id', $relationship['field field']);
|
||||
$this->assertEquals('field_data_file', $relationship['field_name']);
|
||||
$this->assertEquals('entity_test_mul', $relationship['entity_type']);
|
||||
$this->assertEquals(['field' => 'deleted', 'value' => 0, 'numeric' => TRUE], $relationship['join_extra'][0]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Kernel\Views;
|
||||
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests options list argument for views.
|
||||
*
|
||||
* @see \Drupal\options\Plugin\views\argument\NumberListField.
|
||||
* @group views
|
||||
*/
|
||||
class OptionsListArgumentTest extends OptionsTestBase {
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_options_list_argument_numeric', 'test_options_list_argument_string'];
|
||||
|
||||
/**
|
||||
* Tests the options field argument.
|
||||
*/
|
||||
public function testViewsTestOptionsListArgument(): void {
|
||||
$view = Views::getView('test_options_list_argument_numeric');
|
||||
$this->executeView($view, [1]);
|
||||
|
||||
$resultset = [
|
||||
['nid' => $this->nodes[0]->nid->value],
|
||||
['nid' => $this->nodes[1]->nid->value],
|
||||
];
|
||||
|
||||
$column_map = ['nid' => 'nid'];
|
||||
$this->assertIdenticalResultset($view, $resultset, $column_map);
|
||||
|
||||
$view = Views::getView('test_options_list_argument_string');
|
||||
$this->executeView($view, ['man', 'woman']);
|
||||
|
||||
$resultset = [
|
||||
['nid' => $this->nodes[0]->nid->value],
|
||||
['nid' => $this->nodes[1]->nid->value],
|
||||
];
|
||||
|
||||
$column_map = ['nid' => 'nid'];
|
||||
$this->assertIdenticalResultset($view, $resultset, $column_map);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Kernel\Views;
|
||||
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests options list filter for views.
|
||||
*
|
||||
* @see \Drupal\field\Plugin\views\filter\ListField.
|
||||
* @group views
|
||||
*/
|
||||
class OptionsListFilterTest extends OptionsTestBase {
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_options_list_filter'];
|
||||
|
||||
/**
|
||||
* Tests options list field filter.
|
||||
*/
|
||||
public function testViewsTestOptionsListFilter(): void {
|
||||
$view = Views::getView('test_options_list_filter');
|
||||
$this->executeView($view);
|
||||
|
||||
$resultset = [
|
||||
['nid' => $this->nodes[0]->nid->value],
|
||||
['nid' => $this->nodes[1]->nid->value],
|
||||
];
|
||||
|
||||
$column_map = ['nid' => 'nid'];
|
||||
$this->assertIdenticalResultset($view, $resultset, $column_map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests options list field filter when grouped.
|
||||
*/
|
||||
public function testViewsTestOptionsListGroupedFilter(): void {
|
||||
$view = Views::getView('test_options_list_filter');
|
||||
|
||||
$filters = [
|
||||
'field_test_list_string_value' => [
|
||||
'id' => 'field_test_list_string_value',
|
||||
'table' => 'field_data_field_test_list_string',
|
||||
'field' => 'field_test_list_string_value',
|
||||
'relationship' => 'none',
|
||||
'group_type' => 'group',
|
||||
'admin_label' => '',
|
||||
'operator' => 'or',
|
||||
'value' => [
|
||||
'man' => 'man',
|
||||
'woman' => 'woman',
|
||||
],
|
||||
'group' => '1',
|
||||
'exposed' => TRUE,
|
||||
'expose' => [
|
||||
'operator_id' => 'field_test_list_string_value_op',
|
||||
'label' => 'list-text',
|
||||
'description' => '',
|
||||
'identifier' => 'field_test_list_string_value',
|
||||
],
|
||||
'is_grouped' => TRUE,
|
||||
'group_info' => [
|
||||
'label' => 'list-text (field_list_text)',
|
||||
'description' => '',
|
||||
'identifier' => 'field_test_list_string_value',
|
||||
'optional' => TRUE,
|
||||
'widget' => 'radios',
|
||||
'multiple' => TRUE,
|
||||
'remember' => FALSE,
|
||||
'default_group' => '1',
|
||||
'group_items' => [
|
||||
1 => [
|
||||
'title' => 'First',
|
||||
'operator' => 'or',
|
||||
'value' => [
|
||||
$this->fieldValues[0] => $this->fieldValues[0],
|
||||
],
|
||||
],
|
||||
2 => [
|
||||
'title' => 'Second',
|
||||
'operator' => 'or',
|
||||
'value' => [
|
||||
$this->fieldValues[1] => $this->fieldValues[1],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'reduce_duplicates' => '',
|
||||
'plugin_id' => 'list_field',
|
||||
],
|
||||
];
|
||||
$view->setDisplay();
|
||||
$view->displayHandlers->get('default')->overrideOption('filters', $filters);
|
||||
|
||||
$view->storage->save();
|
||||
|
||||
$this->executeView($view);
|
||||
|
||||
$resultset = [
|
||||
['nid' => $this->nodes[0]->nid->value],
|
||||
['nid' => $this->nodes[1]->nid->value],
|
||||
];
|
||||
|
||||
$column_map = ['nid' => 'nid'];
|
||||
$this->assertIdenticalResultset($view, $resultset, $column_map);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Kernel\Views;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
|
||||
/**
|
||||
* Base class for options views tests.
|
||||
*/
|
||||
abstract class OptionsTestBase extends ViewsKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'options',
|
||||
'options_test_views',
|
||||
'node',
|
||||
'user',
|
||||
'field',
|
||||
];
|
||||
|
||||
/**
|
||||
* Stores the nodes used for the different tests.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $nodes = [];
|
||||
|
||||
/**
|
||||
* Stores the field values used for the different tests.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldValues = [];
|
||||
|
||||
/**
|
||||
* The used field names.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $fieldNames;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE): void {
|
||||
parent::setUp();
|
||||
$this->mockStandardInstall();
|
||||
|
||||
ViewTestData::createTestViews(static::class, ['options_test_views']);
|
||||
|
||||
$settings = [];
|
||||
$settings['type'] = 'article';
|
||||
$settings['title'] = $this->randomString();
|
||||
$settings['field_test_list_string'][]['value'] = $this->fieldValues[0];
|
||||
$settings['field_test_list_integer'][]['value'] = 0;
|
||||
|
||||
$node = Node::create($settings);
|
||||
$node->save();
|
||||
|
||||
$this->nodes[] = $node;
|
||||
$node = $node->createDuplicate();
|
||||
$node->save();
|
||||
$this->nodes[] = $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a workaround for the inability to use the standard profile.
|
||||
*
|
||||
* @see https://www.drupal.org/node/1708692
|
||||
*/
|
||||
protected function mockStandardInstall() {
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('node');
|
||||
|
||||
NodeType::create(['type' => 'article', 'name' => 'Article'])->save();
|
||||
$this->fieldValues = [
|
||||
$this->randomMachineName(),
|
||||
$this->randomMachineName(),
|
||||
];
|
||||
|
||||
$this->fieldNames = ['field_test_list_string', 'field_test_list_integer'];
|
||||
|
||||
// Create two field entities.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldNames[0],
|
||||
'entity_type' => 'node',
|
||||
'type' => 'list_string',
|
||||
'cardinality' => 1,
|
||||
'settings' => [
|
||||
'allowed_values' => [
|
||||
$this->fieldValues[0] => $this->fieldValues[0],
|
||||
$this->fieldValues[1] => $this->fieldValues[1],
|
||||
],
|
||||
],
|
||||
])->save();
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldNames[1],
|
||||
'entity_type' => 'node',
|
||||
'type' => 'list_integer',
|
||||
'cardinality' => 1,
|
||||
'settings' => [
|
||||
'allowed_values' => [
|
||||
$this->fieldValues[0],
|
||||
$this->fieldValues[1],
|
||||
],
|
||||
],
|
||||
])->save();
|
||||
foreach ($this->fieldNames as $field_name) {
|
||||
FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'node',
|
||||
'label' => 'Test options list field',
|
||||
'bundle' => 'article',
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\options\Kernel\Views;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Test to ensure views data is properly created for the Options module.
|
||||
*
|
||||
* @group views
|
||||
*/
|
||||
class ViewsDataTest extends OptionsTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'options',
|
||||
'options_test',
|
||||
'entity_test',
|
||||
'views',
|
||||
];
|
||||
|
||||
/**
|
||||
* The field storage.
|
||||
*
|
||||
* @var \Drupal\Core\Field\FieldStorageDefinitionInterface
|
||||
*/
|
||||
protected $fieldStorage;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected int $field;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('entity_test');
|
||||
$field_name = 'test_options';
|
||||
$this->fieldStorage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'list_string',
|
||||
'cardinality' => 1,
|
||||
'settings' => [
|
||||
'allowed_values_function' => '\Drupal\options_test\OptionsAllowedValues::dynamicValues',
|
||||
],
|
||||
]);
|
||||
$this->fieldStorage->save();
|
||||
|
||||
$this->field = FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'required' => TRUE,
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the option module's implementation of hook_field_views_data().
|
||||
*/
|
||||
public function testOptionsFieldViewsData(): void {
|
||||
$field_data = \Drupal::service('views.views_data')->get('entity_test__test_options');
|
||||
|
||||
// Check that the options module has properly overridden default views data.
|
||||
$test_options_field = $field_data['test_options_value'];
|
||||
$this->assertEquals('string_list_field', $test_options_field['argument']['id'], 'Argument handler is properly set for fields with allowed value callbacks.');
|
||||
$this->assertEquals('list_field', $test_options_field['filter']['id'], 'Filter handler is properly set for fields with allowed value callbacks.');
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user