Skip to content
Snippets Groups Projects
Verified Commit 32b37dee authored by Lee Rowlands's avatar Lee Rowlands
Browse files

Issue #3525331 by nicxvan, ghost of drupal past, dww, larowlan, andypost,...

Issue #3525331 by nicxvan, ghost of drupal past, dww, larowlan, andypost, moshe weitzman, godotislate, nod_, kingdutch, pdureau, tim.plunkett, phenaproxima, catch, donquixote: Reuse element plugins as object wrappers around render arrays
parent ebdb7a6f
Branches
No related tags found
3 merge requests!12628#3524738 backport without deprecation,!12477#3532243: JSON support status during updates,!5423Draft: Resolve #3329907 "Test2"
Pipeline #532860 passed with warnings
Pipeline: drupal

#532880

    Pipeline: drupal

    #532867

      Pipeline: drupal

      #532862

        Showing
        with 323 additions and 58 deletions
        ......@@ -9715,12 +9715,6 @@
        'count' => 1,
        'path' => __DIR__ . '/lib/Drupal/Core/Render/Element/Weight.php',
        ];
        $ignoreErrors[] = [
        'message' => '#^Method Drupal\\\\Core\\\\Render\\\\ElementInfoManager\\:\\:clearCachedDefinitions\\(\\) has no return type specified\\.$#',
        'identifier' => 'missingType.return',
        'count' => 1,
        'path' => __DIR__ . '/lib/Drupal/Core/Render/ElementInfoManager.php',
        ];
        $ignoreErrors[] = [
        'message' => '#^Method Drupal\\\\Core\\\\Render\\\\HtmlResponseAttachmentsProcessor\\:\\:renderHtmlResponseAttachmentPlaceholders\\(\\) has no return type specified\\.$#',
        'identifier' => 'missingType.return',
        ......@@ -19,29 +19,38 @@
        * entities, which can come from all or specific bundles of an entity type.
        *
        * Properties:
        * - #target_type: (required) The ID of the target entity type.
        * - #tags: (optional) TRUE if the element allows multiple selection. Defaults
        *
        * @property $target_type
        * (required) The ID of the target entity type.
        * @property $tags
        * (optional) TRUE if the element allows multiple selection. Defaults
        * to FALSE.
        * - #default_value: (optional) The default entity or an array of default
        * @property $default_value
        * (optional) The default entity or an array of default
        * entities, depending on the value of #tags.
        * - #selection_handler: (optional) The plugin ID of the entity reference
        * @property $selection_handler
        * (optional) The plugin ID of the entity reference
        * selection handler (a plugin of type EntityReferenceSelection). The default
        * value is the lowest-weighted plugin that is compatible with #target_type.
        * - #selection_settings: (optional) An array of settings for the selection
        * @property $selection_settings
        * (optional) An array of settings for the selection
        * handler. Settings for the default selection handler
        * \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection are:
        * - target_bundles: Array of bundles to allow (omit to allow all bundles).
        * - sort: Array with 'field' and 'direction' keys, determining how results
        * will be sorted. Defaults to unsorted.
        * - #autocreate: (optional) Array of settings used to auto-create entities
        * @property $autocreate
        * (optional) Array of settings used to auto-create entities
        * that do not exist (omit to not auto-create entities). Elements:
        * - bundle: (required) Bundle to use for auto-created entities.
        * - uid: User ID to use as the author of auto-created entities. Defaults to
        * the current user.
        * - #process_default_value: (optional) Set to FALSE if the #default_value
        * @property $process_default_value
        * (optional) Set to FALSE if the #default_value
        * property is processed and access checked elsewhere (such as by a Field API
        * widget). Defaults to TRUE.
        * - #validate_reference: (optional) Set to FALSE if validation of the selected
        * @property $validate_reference
        * (optional) Set to FALSE if validation of the selected
        * entities is performed elsewhere. Defaults to TRUE.
        *
        * Usage example:
        ......
        ......@@ -9,6 +9,7 @@
        use Drupal\Core\Field\WidgetBase;
        use Drupal\Core\Form\FormStateInterface;
        use Drupal\Core\Form\OptGroup;
        use Drupal\Core\Render\ElementInfoManagerInterface;
        use Drupal\Core\StringTranslation\TranslatableMarkup;
        /**
        ......@@ -56,8 +57,8 @@ abstract class OptionsWidgetBase extends WidgetBase {
        /**
        * {@inheritdoc}
        */
        public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings) {
        parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
        public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ?ElementInfoManagerInterface $elementInfoManager = NULL) {
        parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $elementInfoManager);
        $property_names = $this->fieldDefinition->getFieldStorageDefinition()->getPropertyNames();
        $this->column = $property_names[0];
        }
        ......
        ......@@ -6,6 +6,9 @@
        use Drupal\Core\Field\FieldItemListInterface;
        use Drupal\Core\Field\WidgetBase;
        use Drupal\Core\Form\FormStateInterface;
        use Drupal\Core\Render\Element\ElementInterface;
        use Drupal\Core\Render\Element\Textfield;
        use Drupal\Core\Render\Element\Widget;
        use Drupal\Core\StringTranslation\TranslatableMarkup;
        /**
        ......@@ -66,17 +69,14 @@ public function settingsSummary() {
        /**
        * {@inheritdoc}
        */
        public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
        $element['value'] = $element + [
        '#type' => 'textfield',
        '#default_value' => $items[$delta]->value ?? NULL,
        '#size' => $this->getSetting('size'),
        '#placeholder' => $this->getSetting('placeholder'),
        '#maxlength' => $this->getFieldSetting('max_length'),
        '#attributes' => ['class' => ['js-text-full', 'text-full']],
        ];
        return $element;
        public function singleElementObject(FieldItemListInterface $items, $delta, Widget $widget, ElementInterface $form, FormStateInterface $form_state): ElementInterface {
        $value = $widget->createChild('value', Textfield::class, copyProperties: TRUE);
        $value->default_value = $items[$delta]->value ?? NULL;
        $value->size = $this->getSetting('size');
        $value->placeholder = $this->getSetting('placeholder');
        $value->maxlength = $this->getFieldSetting('max_length');
        $value->attributes = ['class' => ['js-text-full', 'text-full']];
        return $widget;
        }
        }
        ......@@ -11,6 +11,9 @@
        use Drupal\Core\Form\FormStateInterface;
        use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
        use Drupal\Core\Render\Element;
        use Drupal\Core\Render\Element\ElementInterface;
        use Drupal\Core\Render\Element\Widget;
        use Drupal\Core\Render\ElementInfoManagerInterface;
        use Symfony\Component\DependencyInjection\ContainerInterface;
        use Symfony\Component\Validator\ConstraintViolationInterface;
        use Symfony\Component\Validator\ConstraintViolationListInterface;
        ......@@ -49,19 +52,32 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface,
        * The widget settings.
        * @param array $third_party_settings
        * Any third party settings.
        * @param \Drupal\Core\Render\ElementInfoManagerInterface $elementInfoManager
        * The element info manager.
        */
        public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings) {
        public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, protected ?ElementInfoManagerInterface $elementInfoManager = NULL) {
        parent::__construct([], $plugin_id, $plugin_definition);
        $this->fieldDefinition = $field_definition;
        $this->settings = $settings;
        $this->thirdPartySettings = $third_party_settings;
        if (!$this->elementInfoManager) {
        @trigger_error('Calling ' . __METHOD__ . '() without the $elementInfoManager argument is deprecated in drupal:11.3.0 and it will be required in drupal:12.0.0. See https://www.drupal.org/node/3526683', E_USER_DEPRECATED);
        $this->elementInfoManager = \Drupal::service('plugin.manager.element_info');
        }
        }
        /**
        * {@inheritdoc}
        */
        public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
        return new static($plugin_id, $plugin_definition, $configuration['field_definition'], $configuration['settings'], $configuration['third_party_settings']);
        return new static(
        $plugin_id,
        $plugin_definition,
        $configuration['field_definition'],
        $configuration['settings'],
        $configuration['third_party_settings'],
        $container->get('plugin.manager.element_info'),
        );
        }
        /**
        ......@@ -461,7 +477,9 @@ protected function formSingleElement(FieldItemListInterface $items, $delta, arra
        '#weight' => $delta,
        ];
        $element = $this->formElement($items, $delta, $element, $form, $form_state);
        $formObject = $this->elementInfoManager->fromRenderable($form);
        $widget = $this->elementInfoManager->fromRenderable($element, Widget::class);
        $element = $this->singleElementObject($items, $delta, $widget, $formObject, $form_state)->toRenderable();
        if ($element) {
        // Allow modules to alter the field widget form element.
        ......@@ -481,6 +499,21 @@ protected function formSingleElement(FieldItemListInterface $items, $delta, arra
        return $element;
        }
        /**
        * {@inheritdoc}
        */
        public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
        return $element;
        }
        /**
        * {@inheritdoc}
        */
        public function singleElementObject(FieldItemListInterface $items, $delta, Widget $widget, ElementInterface $form, FormStateInterface $form_state): ElementInterface {
        $element = $this->formElement($items, $delta, $widget->toRenderable(), $form->toRenderable(), $form_state);
        return $this->elementInfoManager->fromRenderable($element);
        }
        /**
        * {@inheritdoc}
        */
        ......
        ......@@ -3,15 +3,17 @@
        namespace Drupal\Core\Field;
        use Drupal\Core\Form\FormStateInterface;
        use Drupal\Core\Render\Element\ElementInterface;
        use Drupal\Core\Render\Element\Widget;
        use Symfony\Component\Validator\ConstraintViolationInterface;
        /**
        * Interface definition for field widget plugins.
        *
        * This interface details the methods that most plugin implementations will want
        * to override. See Drupal\Core\Field\WidgetBaseInterface for base
        * to override. See \Drupal\Core\Field\WidgetBaseInterface for base
        * wrapping methods that should most likely be inherited directly from
        * Drupal\Core\Field\WidgetBase..
        * \Drupal\Core\Field\WidgetBase.
        *
        * @ingroup field_widget
        */
        ......@@ -103,6 +105,51 @@ public function settingsSummary();
        */
        public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state);
        /**
        * Returns the form for a single field widget.
        *
        * Field widget form elements should be based on the passed-in $element, which
        * contains the base form element properties derived from the field
        * configuration.
        *
        * The BaseWidget methods will set the weight, field name and delta values for
        * each form element. If there are multiple values for this field, the
        * formElement() method will be called as many times as needed.
        *
        * Other modules may alter the form element provided by this function using
        * hook_field_widget_single_element_form_alter() or
        * hook_field_widget_single_element_WIDGET_TYPE_form_alter().
        *
        * The FAPI element callbacks (such as #process, #element_validate,
        * #value_callback, etc.) used by the widget do not have access to the
        * original $field_definition passed to the widget's constructor. Therefore,
        * if any information is needed from that definition by those callbacks, the
        * widget implementing this method, or a
        * hook_field_widget[_WIDGET_TYPE]_form_alter() implementation, must extract
        * the needed properties from the field definition and set them as ad-hoc
        * $element['#custom'] properties, for later use by its element callbacks.
        *
        * @param \Drupal\Core\Field\FieldItemListInterface $items
        * Array of default values for this field.
        * @param int $delta
        * The order of this item in the array of sub-elements (0, 1, 2, etc.).
        * @param \Drupal\Core\Render\Element\Widget $widget
        * A widget element.
        * @param \Drupal\Core\Render\Element\ElementInterface $form
        * The form structure where widgets are being attached to. This might be a
        * full form structure, or a sub-element of a larger form.
        * @param \Drupal\Core\Form\FormStateInterface $form_state
        * The current state of the form.
        *
        * @return \Drupal\Core\Render\Element\ElementInterface
        * The wrapper object. Some widgets need to change the type of it so the
        * returned object might not be a Wrapper object.
        *
        * @see hook_field_widget_single_element_form_alter()
        * @see hook_field_widget_single_element_WIDGET_TYPE_form_alter()
        */
        public function singleElementObject(FieldItemListInterface $items, $delta, Widget $widget, ElementInterface $form, FormStateInterface $form_state): ElementInterface;
        /**
        * Assigns a field-level validation error to the right widget sub-element.
        *
        ......
        ......@@ -6,6 +6,7 @@
        use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
        use Drupal\Core\DependencyInjection\DependencySerializationTrait;
        use Drupal\Core\Logger\LoggerChannelTrait;
        use Drupal\Core\Render\ElementInfoManagerInterface;
        use Drupal\Core\Routing\RedirectDestinationTrait;
        use Drupal\Core\StringTranslation\StringTranslationTrait;
        use Drupal\Core\Url;
        ......@@ -75,6 +76,13 @@ abstract class FormBase implements FormInterface, ContainerInjectionInterface {
        */
        protected $routeMatch;
        /**
        * The element info manager.
        *
        * @var \Drupal\Core\Render\ElementInfoManagerInterface
        */
        protected ElementInfoManagerInterface $elementInfoManager;
        /**
        * {@inheritdoc}
        */
        ......@@ -125,6 +133,29 @@ protected function configFactory() {
        return $this->configFactory;
        }
        /**
        * The element info manager.
        *
        * @return \Drupal\Core\Render\ElementInfoManagerInterface
        * The element info manager.
        */
        protected function elementInfoManager(): ElementInfoManagerInterface {
        if (!isset($this->elementInfoManager)) {
        $this->elementInfoManager = $this->container()->get('plugin.manager.element_info');
        }
        return $this->elementInfoManager;
        }
        /**
        * Sets the element info manager for this form.
        *
        * @return $this
        */
        public function setElementInfoManager(ElementInfoManagerInterface $elementInfoManager): static {
        $this->elementInfoManager = $elementInfoManager;
        return $this;
        }
        /**
        * Sets the config factory for this form.
        *
        ......
        ......@@ -18,11 +18,15 @@
        * using JavaScript or other mechanisms.
        *
        * Properties:
        * - #limit_validation_errors: An array of form element keys that will block
        *
        * @property $limit_validation_errors
        * An array of form element keys that will block
        * form submission when validation for these elements or any child elements
        * fails. Specify an empty array to suppress all form validation errors.
        * - #value: The text to be shown on the button.
        * - #submit_button: This has a default value of TRUE. If set to FALSE, the
        * @property $value
        * The text to be shown on the button.
        * @property $submit_button
        * This has a default value of TRUE. If set to FALSE, the
        * 'type' attribute is set to 'button.'
        *
        *
        ......
        ......@@ -10,7 +10,9 @@
        * Provides a form element for a single checkbox.
        *
        * Properties:
        * - #return_value: The value to return when the checkbox is checked.
        *
        * @property $return_value
        * The value to return when the checkbox is checked.
        *
        * Usage example:
        * @code
        ......
        ......@@ -9,7 +9,9 @@
        * Provides a form element for a set of checkboxes.
        *
        * Properties:
        * - #options: An associative array whose keys are the values returned for each
        *
        * @property $options
        * An associative array whose keys are the values returned for each
        * checkbox, and whose values are the labels next to each checkbox. The
        * #options array cannot have a 0 key, as it would not be possible to discern
        * checked and unchecked states.
        ......
        ......@@ -11,7 +11,9 @@
        * Provides a form element for choosing a color.
        *
        * Properties:
        * - #default_value: Default value, in a format like #ffffff.
        *
        * @property $default_value
        * Default value, in a format like #ffffff.
        *
        * Example usage:
        * @code
        ......
        ......@@ -11,11 +11,16 @@
        * Provides a Single-Directory Component render element.
        *
        * Properties:
        * - #component: The machine name of the component.
        * - #variant: (optional) The variant to be used for the component.
        * - #props: an associative array where the keys are the names of the
        *
        * @property $component
        * The machine name of the component.
        * @property $variant
        * (optional) The variant to be used for the component.
        * @property $props
        * an associative array where the keys are the names of the
        * component props, and the values are the prop values.
        * - #slots: an associative array where the keys are the slot names, and the
        * @property $slots
        * an associative array where the keys are the slot names, and the
        * values are the slot values. Expected slot values are renderable arrays.
        * - #propsAlter: an array of trusted callbacks. These are used to prepare the
        * context. Typical uses include replacing tokens in props.
        ......
        ......@@ -14,7 +14,9 @@
        * an HTML ID.
        *
        * Properties:
        * - #optional: Indicates whether the container should render when it has no
        *
        * @property $optional
        * Indicates whether the container should render when it has no
        * visible children. Defaults to FALSE.
        *
        * Usage example:
        ......
        ......@@ -9,13 +9,18 @@
        * Provides a form element for date or time selection.
        *
        * Properties:
        * - #attributes: An associative array containing:
        *
        * @property $attributes
        * An associative array containing:
        * - type: The type of date field rendered, valid values include 'date',
        * 'time', 'datetime', and 'datetime-local'.
        * - #date_date_format: The date format used in PHP formats.
        * - #default_value: A string representing the date formatted as Y-m-d, or
        * @property $date_date_format
        * The date format used in PHP formats.
        * @property $default_value
        * A string representing the date formatted as Y-m-d, or
        * hh:mm for time.
        * - #size: The size of the input element in characters.
        * @property $size
        * The size of the input element in characters.
        *
        * @code
        * $form['expiration'] = [
        ......
        ......@@ -13,10 +13,14 @@
        * element, showing or hiding the contained elements.
        *
        * Properties:
        * - #title: The title of the details container. Defaults to "Details".
        * - #open: Indicates whether the container should be open by default.
        *
        * @property $title
        * The title of the details container. Defaults to "Details".
        * @property $open
        * Indicates whether the container should be open by default.
        * Defaults to FALSE.
        * - #summary_attributes: An array of attributes to apply to the <summary>
        * @property $summary_attributes
        * An array of attributes to apply to the <summary>
        * element.
        *
        * Usage example:
        ......
        ......@@ -17,9 +17,12 @@
        * element property #links to provide $variables['links'] for theming.
        *
        * Properties:
        * - #links: An array of links to actions. See template_preprocess_links() for
        *
        * @property $links
        * An array of links to actions. See template_preprocess_links() for
        * documentation the properties of links in this array.
        * - #dropbutton_type: A string defining a type of dropbutton variant for
        * @property $dropbutton_type
        * A string defining a type of dropbutton variant for
        * styling proposes. Renders as class `dropbutton--#dropbutton_type`.
        *
        * Usage Example:
        ......
        ......@@ -40,6 +40,22 @@ interface ElementInterface extends PluginInspectionInterface, RenderCallbackInte
        */
        public function getInfo();
        /**
        * Initialize storage.
        *
        * This will only have an effect the first time it is called, once it has
        * been called, subsequent calls will not have an effect.
        * Only the plugin manager should ever call this method.
        *
        * @param array $element
        * The containing element.
        *
        * @return $this
        *
        * @internal
        */
        public function initializeInternalStorage(array &$element): static;
        /**
        * Sets a form element's class attribute.
        *
        ......@@ -52,4 +68,101 @@ public function getInfo();
        */
        public static function setAttributes(&$element, $class = []);
        /**
        * Returns a render array.
        *
        * @param string|null $wrapper_key
        * An optional wrapper.
        *
        * @return array|\Drupal\Core\Render\Element\ElementInterface
        * A render array. Make sure to take the return value as a reference.
        * If $wrapper_key is not given then the stored render element is returned.
        * If $wrapper_key is given then [$wrapper_key => &$element] is returned.
        * The return value is typed with array|ElementInterface to prepare for
        * Drupal 12, where the plan for this method is to return an
        * ElementInterface object. If that plan goes through then in Drupal 13
        * support for render arrays will be dropped.
        */
        public function &toRenderable(?string $wrapper_key = NULL): array|ElementInterface;
        /**
        * Returns child elements.
        *
        * @return \Traversable<\Drupal\Core\Render\Element\ElementInterface>
        * Keys will be children names, values are render objects.
        */
        public function getChildren(): \Traversable;
        /**
        * Gets a child.
        *
        * @param int|string|list<int|string> $name
        * The name of the child. Can also be an integer. Or a list of these.
        * It is an integer when the field API uses the delta for children.
        *
        * @return ?\Drupal\Core\Render\Element\ElementInterface
        * The child render object.
        */
        public function getChild(int|string|array $name): ?ElementInterface;
        /**
        * Adds a child render element.
        *
        * @param int|string $name
        * The name of the child. Can also be an integer when the child is a delta.
        * @param array|\Drupal\Core\Render\Element\ElementInterface $child
        * A render array or a render object.
        *
        * @return \Drupal\Core\Render\Element\ElementInterface
        * The added child as a render object.
        */
        public function addChild(int|string $name, ElementInterface|array &$child): ElementInterface;
        /**
        * Creates a render object and attaches it to the current render object.
        *
        * @param int|string $name
        * The name of the child. Can also be an integer.
        * @param class-string<T> $class
        * The class of the render object.
        * @param array $configuration
        * An array of configuration relevant to the render object.
        * @param bool $copyProperties
        * Copy properties (but not children) from the parent. This is useful for
        * widgets for example.
        *
        * @return T
        * The child render object.
        *
        * @template T of \Drupal\Core\Render\Element\ElementInterface
        */
        public function createChild(int|string $name, string $class, array $configuration = [], bool $copyProperties = FALSE): ElementInterface;
        /**
        * Removes a child.
        *
        * @param int|string $name
        * The name of the child. Can also be an integer.
        *
        * @return ?\Drupal\Core\Render\Element\ElementInterface
        * The removed render object if any, or NULL if the child could not be
        * found.
        */
        public function removeChild(int|string $name): ?ElementInterface;
        /**
        * Change the type of the element.
        *
        * Changes only the #type all other properties and children are preserved.
        *
        * @param class-string<T> $class
        * The class of the new render object.
        *
        * @return T
        * The new render object.
        *
        * @template T of \Drupal\Core\Render\Element\ElementInterface
        */
        public function changeType(string $class): ElementInterface;
        }
        ......@@ -10,9 +10,13 @@
        * Provides a form input element for entering an email address.
        *
        * Properties:
        * - #default_value: An RFC-compliant email address.
        * - #size: The size of the input element in characters.
        * - #pattern: A string for the native HTML5 pattern attribute.
        *
        * @property $default_value
        * An RFC-compliant email address.
        * @property $size
        * The size of the input element in characters.
        * @property $pattern
        * A string for the native HTML5 pattern attribute.
        *
        * Example usage:
        * @code
        ......
        ......@@ -13,8 +13,11 @@
        * will automatically be added to the form element.
        *
        * Properties:
        * - #multiple: A Boolean indicating whether multiple files may be uploaded.
        * - #size: The size of the file input element in characters.
        *
        * @property $multiple
        * A Boolean indicating whether multiple files may be uploaded.
        * @property $size
        * The size of the file input element in characters.
        *
        * The value of this form element will always be an array of
        * \Symfony\Component\HttpFoundation\File\UploadedFile objects, regardless of
        ......
        ......@@ -18,7 +18,8 @@ abstract class FormElement extends FormElementBase {
        * {@inheritdoc}
        */
        public function __construct(array $configuration, $plugin_id, $plugin_definition) {
        parent::__construct($configuration, $plugin_id, $plugin_definition);
        $elementInfoManager = \Drupal::service('plugin.manager.element_info');
        parent::__construct($configuration, $plugin_id, $plugin_definition, $elementInfoManager);
        @trigger_error('\Drupal\Core\Render\Element\FormElement is deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. Use \Drupal\Core\Render\Element\FormElementBase instead. See https://www.drupal.org/node/3436275', E_USER_DEPRECATED);
        }
        ......
        0% Loading or .
        You are about to add 0 people to the discussion. Proceed with caution.
        Please register or to comment