You are here

class WebformElementComposite in Webform 8.5

Same name and namespace in other branches
  1. 6.x src/Element/WebformElementComposite.php \Drupal\webform\Element\WebformElementComposite

Provides a element for the composite elements.

Plugin annotation

@FormElement("webform_element_composite");

Hierarchy

Expanded class hierarchy of WebformElementComposite

1 #type use of WebformElementComposite
WebformCustomComposite::buildCompositeElementsTable in src/Plugin/WebformElement/WebformCustomComposite.php
Build the composite elements settings table.

File

src/Element/WebformElementComposite.php, line 18

Namespace

Drupal\webform\Element
View source
class WebformElementComposite extends FormElement {

  /**
   * List of supported element properties.
   *
   * @var array
   */
  protected static $supportedProperties = [
    'key' => 'key',
    'type' => 'type',
    'title' => 'title',
    'help' => 'help',
    'placeholder' => 'placeholder',
    'description' => 'description',
    'options' => 'options',
    'required' => 'required',
  ];

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = get_class($this);
    return [
      '#input' => TRUE,
      '#process' => [
        [
          $class,
          'processWebformElementComposite',
        ],
        [
          $class,
          'processAjaxForm',
        ],
      ],
      '#theme_wrappers' => [
        'form_element',
      ],
      // 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) {
    if ($input === FALSE) {
      if (!isset($element['#default_value']) || !is_array($element['#default_value'])) {
        return [];
      }
      else {
        $default_value = [];
        foreach ($element['#default_value'] as $composite_key => $composite_element) {
          $composite_element = [
            'key' => $composite_key,
          ] + WebformArrayHelper::removePrefix($composite_element);

          // Get supported properties.
          $composite_properties = array_intersect_key($composite_element, static::$supportedProperties);

          // Move 'unsupported' properties to 'custom'.
          $custom_properties = array_diff_key($composite_element, static::$supportedProperties);
          $composite_properties['custom'] = $custom_properties ? WebformYaml::encode($custom_properties) : '';
          $default_value[] = $composite_properties;
        }
        $element['#default_value'] = $default_value;
        return $default_value;
      }
    }
    elseif (is_array($input)) {
      return $input;
    }
    else {
      return NULL;
    }
  }

  /**
   * Processes a webform element composite (builder) element.
   */
  public static function processWebformElementComposite(&$element, FormStateInterface $form_state, &$complete_form) {

    /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */
    $element_manager = \Drupal::service('plugin.manager.webform.element');
    $placeholder_elements = [];
    $options_elements = [];
    $type_options = [];
    $elements = $element_manager
      ->getInstances();
    $definitions = $element_manager
      ->getDefinitions();
    $definitions = $element_manager
      ->getSortedDefinitions($definitions, 'category');
    $definitions = $element_manager
      ->removeExcludeDefinitions($definitions);
    $grouped_definitions = $element_manager
      ->getGroupedDefinitions($definitions);
    foreach ($grouped_definitions as $group => $definitions) {
      foreach ($definitions as $element_type => $definition) {
        if (!WebformCompositeBaseElement::isSupportedElementType($element_type)) {
          continue;
        }
        $element_plugin = $elements[$element_type];
        $type_options[$group][$element_type] = $definition['label'];
        if ($element_plugin
          ->hasProperty('options')) {
          $options_elements[$element_type] = $element_type;
        }
        if ($element_plugin
          ->hasProperty('placeholder')) {
          $placeholder_elements[$element_type] = $element_type;
        }
      }
    }
    $edit_source = \Drupal::currentUser()
      ->hasPermission('edit webform source');
    $element['#tree'] = TRUE;
    $element['elements'] = [
      '#type' => 'webform_multiple',
      '#title' => t('Elements'),
      '#title_display' => 'invisible',
      '#label' => t('element'),
      '#labels' => t('elements'),
      '#empty_items' => 0,
      '#min_items' => 1,
      '#header' => TRUE,
      '#add' => FALSE,
      '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL,
      '#error_no_message' => TRUE,
      '#element' => [
        'settings' => [
          '#type' => 'container',
          '#title' => t('Settings'),
          '#help' => '<b>' . t('Key') . ':</b> ' . t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.') . '<hr/>' . '<b>' . t('Type') . ':</b> ' . t('The type of element to be displayed.') . '<hr/>' . '<b>' . t('Options') . ':</b> ' . t('Please select predefined options or enter custom options.') . ' ' . t('Key-value pairs MUST be specified as "safe_key: \'Some readable options\'". Use of only alphanumeric characters and underscores is recommended in keys. One option per line.') . ($edit_source ? '<hr/>' . '<b>' . t('Custom Properties') . ':</b> ' . t('Properties do not have to be prepended with a hash (#) character, the hash character will be automatically added to the custom properties.') : '') . '<hr/>' . '<b>' . t('Required') . ':</b> ' . t('Check this option if the user must enter a value.'),
          'key' => [
            '#type' => 'textfield',
            '#title' => t('Key'),
            '#title_display' => 'invisible',
            '#placeholder' => t('Enter key…'),
            '#pattern' => '^[a-z0-9_]+$',
            '#attributes' => [
              'title' => t('Enter a unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'),
            ],
            '#required' => TRUE,
            '#error_no_message' => TRUE,
          ],
          'type' => [
            '#type' => 'select',
            '#title' => t('Type'),
            '#title_display' => 'invisible',
            '#description' => t('The type of element to be displayed.'),
            '#description_display' => 'invisible',
            '#options' => $type_options,
            '#empty_option' => t('- Select type -'),
            '#required' => TRUE,
            '#attributes' => [
              'class' => [
                'js-webform-composite-type',
              ],
            ],
            '#error_no_message' => TRUE,
          ],
          'options' => [
            '#type' => 'webform_element_options',
            '#yaml' => TRUE,
            '#title' => t('Options'),
            '#title_display' => 'invisible',
            '#description' => t('Please select predefined options or enter custom options.') . ' ' . t('Key-value pairs MUST be specified as "safe_key: \'Some readable options\'". Use of only alphanumeric characters and underscores is recommended in keys. One option per line.'),
            '#description_display' => 'invisible',
            '#wrapper_attributes' => [
              'data-composite-types' => implode(',', $options_elements),
              'data-composite-required' => 'data-composite-required',
            ],
            '#error_no_message' => TRUE,
          ],
          // ISSUE:
          // Set #access: FALSE is losing the custom properties.
          //
          // WORKAROUND:
          // Use 'hidden' element.
          // @see \Drupal\webform\Element\WebformMultiple::buildElementRow
          'custom' => $edit_source ? [
            '#type' => 'webform_codemirror',
            '#mode' => 'yaml',
            '#title' => t('Custom properties'),
            '#title_display' => 'invisible',
            '#description' => t('Properties do not have to be prepended with a hash (#) character, the hash character will be automatically added to the custom properties.'),
            '#description_display' => 'invisible',
            '#placeholder' => t('Enter custom properties…'),
            '#error_no_message' => TRUE,
          ] : [
            '#type' => 'hidden',
          ],
          // Note: Setting #return_value: TRUE is not returning any value.
          'required' => [
            '#type' => 'checkbox',
            '#title' => t('Required'),
            '#description' => t('Check this option if the user must enter a value.'),
            '#description_display' => 'invisible',
            '#error_no_message' => TRUE,
          ],
        ],
        'labels' => [
          '#type' => 'container',
          '#title' => t('Labels'),
          '#help' => '<b>' . t('Title') . ':</b> ' . t('This is used as a descriptive label when displaying this webform element.') . '<hr/><b>' . t('Placeholder') . ':</b> ' . t('The placeholder will be shown in the element until the user starts entering a value.') . '<hr/><b>' . t('Description') . ':</b> ' . t('A short description of the element used as help for the user when they use the webform.') . '<hr/><b>' . t('Help text') . ':</b> ' . t('A tooltip displayed after the title.'),
          'title' => [
            '#type' => 'textfield',
            '#title' => t('Title'),
            '#title_display' => 'invisible',
            '#description' => t('This is used as a descriptive label when displaying this webform element.'),
            '#description_display' => 'invisible',
            '#placeholder' => t('Enter title…'),
            '#required' => TRUE,
            '#error_no_message' => TRUE,
          ],
          'placeholder' => [
            '#type' => 'textfield',
            '#title' => t('Placeholder'),
            '#title_display' => 'invisible',
            '#description' => t('The placeholder will be shown in the element until the user starts entering a value.'),
            '#description_display' => 'invisible',
            '#placeholder' => t('Enter placeholder…'),
            '#attributes' => [
              'data-composite-types' => implode(',', $placeholder_elements),
            ],
            '#error_no_message' => TRUE,
          ],
          'description' => [
            '#type' => 'textarea',
            '#title' => t('Description'),
            '#description' => t('A short description of the element used as help for the user when they use the webform.'),
            '#description_display' => 'invisible',
            '#title_display' => 'invisible',
            '#placeholder' => t('Enter description…'),
            '#rows' => 2,
            '#error_no_message' => TRUE,
          ],
          'help' => [
            '#type' => 'textarea',
            '#title' => t('Help text'),
            '#title_display' => 'invisible',
            '#description' => t('A tooltip displayed after the title.'),
            '#description_display' => 'invisible',
            '#placeholder' => t('Enter help text…'),
            '#rows' => 2,
            '#error_no_message' => TRUE,
          ],
        ],
      ],
    ];
    $element['#attached']['library'][] = 'webform/webform.element.composite';

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

  /**
   * Validates a webform element composite (builder) element.
   */
  public static function validateWebformElementComposite(&$element, FormStateInterface $form_state, &$complete_form) {

    /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */
    $element_manager = \Drupal::service('plugin.manager.webform.element');
    $elements_value = NestedArray::getValue($form_state
      ->getValues(), $element['elements']['#parents']);

    // Check for duplicate keys.
    $keys = [];
    foreach ($elements_value as $element_value) {
      $key = $element_value['key'];
      if (isset($keys[$key])) {
        $form_state
          ->setError($element, t('Duplicate key found. The %key key must only be assigned on one element.', [
          '%key' => $key,
        ]));
        return;
      }
      $keys[$key] = $key;
    }

    // Convert the $elements value which is a simple associative array into a
    // render array.
    $elements = [];
    foreach ($elements_value as $element_value) {
      $key = $element_value['key'];
      unset($element_value['key']);

      // Remove empty strings from array.
      $element_value = array_filter($element_value, function ($value) {
        return $value !== '';
      });

      // Unset empty required or case to boolean.
      if (empty($element_value['required'])) {
        unset($element_value['required']);
      }
      else {
        $element_value['required'] = TRUE;
      }

      // Limit value keys to supported element properties.
      // This removes options from elements that don't support #options.
      if (isset($element_value['type'])) {
        foreach ($element_value as $property_name => $property_value) {
          if (!in_array($property_name, [
            'type',
            'custom',
          ])) {

            /** @var \Drupal\webform\Plugin\WebformElementInterface $element_plugin */
            $element_plugin = $element_manager
              ->createInstance($element_value['type']);
            if (!$element_plugin
              ->hasProperty($property_name)) {
              unset($element_value[$property_name]);
            }
          }
        }
      }
      if (isset($element_value['custom'])) {
        if ($element_value['custom']) {
          $custom = Yaml::decode($element_value['custom']);
          if ($custom && is_array($custom)) {
            $element_value += $custom;
          }
        }
        unset($element_value['custom']);
      }
      $elements[$key] = WebformArrayHelper::addPrefix($element_value);
    }
    foreach ($elements as $composite_element_key => $composite_element) {
      if (!isset($composite_element['#type'])) {
        continue;
      }

      /** @var \Drupal\webform\Plugin\WebformElementInterface $element_plugin */
      $element_plugin = $element_manager
        ->getElementInstance($composite_element);
      $t_args = [
        '%title' => isset($composite_element['#title']) ? $composite_element['#title'] : $composite_element_key,
        '@type' => $composite_element['#type'],
      ];

      // Make sure #options is set for composite element's that require #options.
      if ($element_plugin
        ->hasProperty('options') && empty($composite_element['#options'])) {
        $form_state
          ->setError($element, t('Options for %title is required.', $t_args));
      }

      // Make sure element is not storing multiple values.
      if ($element_plugin
        ->hasMultipleValues($composite_element)) {
        $form_state
          ->setError($element, t('Multiple value is not supported for %title (@type).', $t_args));
      }
    }
    $form_state
      ->setValueForElement($element['elements'], NULL);
    $element['#value'] = $elements;
    $form_state
      ->setValueForElement($element, $elements);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
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. 29
MessengerTrait::messenger public function Gets the messenger. 29
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 3
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. 92
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. 1
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.
WebformElementComposite::$supportedProperties protected static property List of supported element properties.
WebformElementComposite::getInfo public function Returns the element properties for this element. Overrides ElementInterface::getInfo
WebformElementComposite::processWebformElementComposite public static function Processes a webform element composite (builder) element.
WebformElementComposite::validateWebformElementComposite public static function Validates a webform element composite (builder) element.
WebformElementComposite::valueCallback public static function Determines how user input is mapped to an element's #value property. Overrides FormElement::valueCallback