You are here

abstract class WebformOtherBase in Webform 6.x

Same name and namespace in other branches
  1. 8.5 src/Element/WebformOtherBase.php \Drupal\webform\Element\WebformOtherBase

Base class for webform other element.

Hierarchy

Expanded class hierarchy of WebformOtherBase

3 files declare their use of WebformOtherBase
ScheduleEmailWebformHandler.php in modules/webform_scheduled_email/src/Plugin/WebformHandler/ScheduleEmailWebformHandler.php
WebformButtonsOther.php in modules/webform_jqueryui_buttons/src/Element/WebformButtonsOther.php
WebformStatesServerTest.php in tests/src/Functional/States/WebformStatesServerTest.php

File

src/Element/WebformOtherBase.php, line 17

Namespace

Drupal\webform\Element
View source
abstract class WebformOtherBase extends FormElement {
  use WebformCompositeFormElementTrait;

  /**
   * Other option value.
   */
  const OTHER_OPTION = '_other_';

  /**
   * The type of element.
   *
   * @var string
   */
  protected static $type;

  /**
   * The properties of the element.
   *
   * @var array
   *
   * @see \Drupal\webform\Element\WebformSelectOther::$properties
   */
  protected static $properties = [
    '#title',
    '#required',
    '#required_error',
    '#options',
    '#options_display',
    '#options_randomize',
    '#options_description_display',
    '#options__properties',
    '#default_value',
    '#attributes',
  ];

  /**
   * The properties of the other element.
   *
   * @var array
   */
  protected static $otherProperties = [
    '#required_error',
  ];

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = get_class($this);
    return [
      '#input' => TRUE,
      '#process' => [
        [
          $class,
          'processWebformOther',
        ],
        [
          $class,
          'processAjaxForm',
        ],
      ],
      '#pre_render' => [
        [
          $class,
          'preRenderWebformCompositeFormElement',
        ],
      ],
      '#options' => [],
      '#other__option_delimiter' => ', ',
      '#states' => [],
      // Add '#markup' property to add an 'id' attribute to the form element.
      // @see template_preprocess_form_element()
      '#markup' => '',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {

    // Remove 'webform_' prefix from type.
    $type = str_replace('webform_', '', static::$type);
    if ($input === FALSE) {
      $value = static::convertDefaultValueToElementValue($element);
      $element[$type]['#default_value'] = $value[$type];
      if ($value['other'] !== NULL) {
        $element['other']['#default_value'] = $value['other'];
      }
      return $value;
    }

    // Return NULL so that current $input is used.
    return NULL;
  }

  /**
   * Processes an 'other' element.
   *
   * See select list webform element for select list properties.
   *
   * @see \Drupal\Core\Render\Element\Select
   */
  public static function processWebformOther(&$element, FormStateInterface $form_state, &$complete_form) {

    // Remove 'webform_' prefix from type.
    $type = str_replace('webform_', '', static::$type);
    $properties = static::$properties;
    $element['#tree'] = TRUE;
    $element[$type]['#type'] = static::$type;
    $element[$type]['#webform_element'] = TRUE;
    $element[$type]['#webform_other'] = TRUE;
    $element[$type] += array_intersect_key($element, array_combine($properties, $properties));
    $element[$type]['#title_display'] = 'invisible';
    if (!isset($element[$type]['#options'][static::OTHER_OPTION])) {
      $element[$type]['#options'][static::OTHER_OPTION] = !empty($element['#other__option_label']) ? $element['#other__option_label'] : t('Other…');
    }
    $element[$type]['#error_no_message'] = TRUE;

    // Build other textfield.
    $element += [
      'other' => [],
    ];
    foreach ($element as $key => $value) {
      if (strpos($key, '#other__') === 0) {
        $other_key = str_replace('#other__', '#', $key);
        if (!isset($element['other'][$other_key])) {
          $element['other'][$other_key] = $value;
        }
      }
    }
    $element['other'] += [
      '#type' => 'textfield',
      '#webform_element' => TRUE,
      '#placeholder' => t('Enter other…'),
    ];
    if (!isset($element['other']['#title'])) {
      $element['other'] += [
        '#title' => $element['other']['#placeholder'],
        '#title_display' => 'invisible',
      ];
    }
    $element['other'] += array_intersect_key($element, array_combine(static::$otherProperties, static::$otherProperties));
    $element['other']['#wrapper_attributes']['class'][] = "js-webform-{$type}-other-input";
    $element['other']['#wrapper_attributes']['class'][] = "webform-{$type}-other-input";
    if ($element['other']['#type'] === 'datetime') {
      $element['other']['#prefix'] = '<div class="' . implode(' ', $element['other']['#wrapper_attributes']['class']) . '">';
      $element['other']['#suffix'] = '</div>';
      unset($element['other']['#wrapper_attributes']['class']);
    }

    // Apply #parents to $type and other element.
    if (isset($element['#parents'])) {
      $element[$type]['#parents'] = array_merge($element['#parents'], [
        $type,
      ]);
      $element['other']['#parents'] = array_merge($element['#parents'], [
        'other',
      ]);
    }

    // Add custom required error message so that clientside_validation.module
    // can display it.
    // @see https://www.drupal.org/project/clientside_validation/issues/3084798
    if (\Drupal::moduleHandler()
      ->moduleExists('clientside_validation') && isset($element['other']['#required_error'])) {
      $element['other']['#attributes']['data-msg-required'] = $element['other']['#required_error'];
    }

    // Initialize the type and other elements to allow for webform enhancements.

    /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */
    $element_manager = \Drupal::service('plugin.manager.webform.element');
    $element_manager
      ->buildElement($element[$type], $complete_form, $form_state);
    $element_manager
      ->buildElement($element['other'], $complete_form, $form_state);

    // Prevent nested fieldset by removing fieldset theme wrapper around
    // radios and checkboxes.
    // @see \Drupal\Core\Render\Element\CompositeFormElementTrait
    $element[$type]['#pre_render'] = [];

    // Add js trigger attributes to the composite wrapper.
    // @see \Drupal\webform\Element\WebformCompositeFormElementTrait
    $is_form_element_wrapper = isset($element['#wrapper_type']) && $element['#wrapper_type'] === 'form_element';
    $wrapper_attributes = $is_form_element_wrapper ? '#wrapper_attributes' : '#attributes';
    $element[$wrapper_attributes]['class'][] = "js-webform-{$type}-other";
    $element[$wrapper_attributes]['class'][] = "webform-{$type}-other";

    // Apply the element id to the wrapper so that inline form errors point
    // to the correct element.
    $element['#attributes']['id'] = $element['#id'];

    // Make sure form element label has no 'for' attribute.
    $element['#label_attributes']['webform-remove-for-attribute'] = TRUE;

    // Remove options.
    unset($element['#options']);

    // Add validate callback.
    $element += [
      '#element_validate' => [],
    ];
    array_unshift($element['#element_validate'], [
      get_called_class(),
      'validateWebformOther',
    ]);

    // Attach library.
    $element['#attached']['library'][] = 'webform/webform.element.other';

    // Process states.
    WebformFormHelper::processStates($element, '#wrapper_attributes');
    return $element;
  }

  /**
   * Validates an other element.
   */
  public static function validateWebformOther(&$element, FormStateInterface $form_state, &$complete_form) {
    $type = static::getElementType();

    // Determine if the element has multiple values.
    $is_multiple = static::isMultiple($element);

    // Get value.
    $value = NestedArray::getValue($form_state
      ->getValues(), $element['#parents']);

    // Get return value.
    $return_value = static::processValue($element, $value);

    // Determine if the return value is empty.
    if ($is_multiple) {
      $is_empty = empty($return_value) ? TRUE : FALSE;
    }
    else {
      $is_empty = $return_value === '' || $return_value === NULL ? TRUE : FALSE;
    }

    // Determine if there is an other value and is the other value empty.
    $element_value = (array) $value[$type];
    $other_value = $value['other'];
    if ($element_value) {
      $element_value = array_filter($element_value);
      $element_value = array_combine($element_value, $element_value);
    }
    $other_is_empty = isset($element_value[static::OTHER_OPTION]) && $other_value === '';

    // Display missing other or missing value error.
    if (Element::isVisibleElement($element)) {
      $required_error_title = isset($element['#title']) ? $element['#title'] : NULL;
      if ($other_is_empty) {
        WebformElementHelper::setRequiredError($element['other'], $form_state, $required_error_title);
      }
      elseif ($element['#required'] && $is_empty) {
        WebformElementHelper::setRequiredError($element, $form_state, $required_error_title);
        $element['other']['#error_no_message'] = TRUE;
      }
    }
    $form_state
      ->setValueForElement($element[$type], NULL);
    $form_state
      ->setValueForElement($element['other'], NULL);
    $element['#value'] = $return_value;
    $form_state
      ->setValueForElement($element, $return_value);
  }

  /**
   * Processed other element's submitted value.
   *
   * @param array $element
   *   The element.
   * @param array $value
   *   The submitted value.
   *
   * @return array|string
   *   An array of values or a string.
   */
  public static function processValue(array $element, array $value) {
    $type = static::getElementType();
    $element_value = $value[$type];
    $other_value = $value['other'];
    if (static::isMultiple($element)) {
      $return_value = array_filter($element_value);
      $return_value = array_combine($return_value, $return_value);
      if (isset($return_value[static::OTHER_OPTION])) {
        unset($return_value[static::OTHER_OPTION]);
        if ($other_value !== '') {
          $return_value += [
            $other_value => $other_value,
          ];
        }
      }
      return $return_value;
    }
    else {
      return $element_value === static::OTHER_OPTION ? $other_value : $element_value;
    }
  }

  /****************************************************************************/

  // Helper functions.

  /****************************************************************************/

  /**
   * Get the element type.
   *
   * @return string
   *   The element type.
   */
  public static function getElementType() {

    // Remove 'webform_' prefix from type.
    return str_replace('webform_', '', static::$type);
  }

  /**
   * Determine if the webform element contains multiple values.
   *
   * @param array $element
   *   A webform element.
   *
   * @return bool
   *   TRUE if the webform element contains multiple values.
   */
  protected static function isMultiple(array $element) {
    return !empty($element['#multiple']) || static::$type === 'checkboxes' ? TRUE : FALSE;
  }

  /**
   * Convert default value to element value.
   *
   * @param array $element
   *   A other form element.
   *
   * @return array
   *   An associative array container (element) type and other value.
   */
  protected static function convertDefaultValueToElementValue(array $element) {
    $type = str_replace('webform_', '', static::$type);
    $default_value = isset($element['#default_value']) && $element['#default_value'] !== '' ? $element['#default_value'] : NULL;
    if (static::isMultiple($element)) {

      // Handle edge case where $default_value is not an array.
      if (!is_array($default_value)) {
        return [
          $type => [],
          'other' => NULL,
        ];
      }
      $default_options = array_combine($default_value, $default_value);
      $flattened_options = OptGroup::flattenOptions($element['#options']);
      if ($other_options = array_diff_key($default_options, $flattened_options)) {
        return [
          $type => array_diff_key($default_options, $other_options) + [
            static::OTHER_OPTION => static::OTHER_OPTION,
          ],
          'other' => implode($element['#other__option_delimiter'], $other_options),
        ];
      }
      return [
        $type => $default_options,
        'other' => NULL,
      ];
    }
    else {
      if ($default_value !== NULL && !WebformOptionsHelper::hasOption($default_value, $element['#options'])) {
        return [
          $type => static::OTHER_OPTION,
          'other' => $default_value,
        ];
      }
      return [
        $type => $default_value,
        'other' => NULL,
      ];
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property
DependencySerializationTrait::$_serviceIds protected property
DependencySerializationTrait::__sleep public function 2
DependencySerializationTrait::__wakeup public function 2
FormElement::processAutocomplete public static function Adds autocomplete functionality to elements.
FormElement::processPattern public static function #process callback for #pattern form element property.
FormElement::validatePattern public static function #element_validate callback for #pattern form element property.
MessengerTrait::$messenger protected property The messenger. 27
MessengerTrait::messenger public function Gets the messenger. 27
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 2
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
PluginBase::__construct public function Constructs a \Drupal\Component\Plugin\PluginBase object. 98
RenderElement::preRenderAjaxForm public static function Adds Ajax information about an element to communicate with JavaScript.
RenderElement::preRenderGroup public static function Adds members of this group as actual elements for rendering.
RenderElement::processAjaxForm public static function Form element processing handler for the #ajax form property. 1
RenderElement::processGroup public static function Arranges elements into groups.
RenderElement::setAttributes public static function Sets a form element's class attribute. Overrides ElementInterface::setAttributes
StringTranslationTrait::$stringTranslation protected property The string translation service. 4
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
WebformCompositeFormElementTrait::preRenderWebformCompositeFormElement public static function Adds form element theming to an element if its title or description is set. 1
WebformOtherBase::$otherProperties protected static property The properties of the other element.
WebformOtherBase::$properties protected static property The properties of the element. 1
WebformOtherBase::$type protected static property The type of element. 4
WebformOtherBase::convertDefaultValueToElementValue protected static function Convert default value to element value.
WebformOtherBase::getElementType public static function Get the element type.
WebformOtherBase::getInfo public function Returns the element properties for this element. Overrides ElementInterface::getInfo
WebformOtherBase::isMultiple protected static function Determine if the webform element contains multiple values.
WebformOtherBase::OTHER_OPTION constant Other option value.
WebformOtherBase::processValue public static function Processed other element's submitted value.
WebformOtherBase::processWebformOther public static function Processes an 'other' element. 1
WebformOtherBase::validateWebformOther public static function Validates an other element.
WebformOtherBase::valueCallback public static function Determines how user input is mapped to an element's #value property. Overrides FormElement::valueCallback 1