You are here

abstract class WebformComputedBase in Webform 6.x

Same name in this branch
  1. 6.x src/Element/WebformComputedBase.php \Drupal\webform\Element\WebformComputedBase
  2. 6.x src/Plugin/WebformElement/WebformComputedBase.php \Drupal\webform\Plugin\WebformElement\WebformComputedBase
Same name and namespace in other branches
  1. 8.5 src/Element/WebformComputedBase.php \Drupal\webform\Element\WebformComputedBase

Provides a base class for 'webform_computed' elements.

Hierarchy

Expanded class hierarchy of WebformComputedBase

1 file declares its use of WebformComputedBase
WebformComputedBase.php in src/Plugin/WebformElement/WebformComputedBase.php

File

src/Element/WebformComputedBase.php, line 21

Namespace

Drupal\webform\Element
View source
abstract class WebformComputedBase extends FormElement implements WebformComputedInterface {

  /**
   * Cache of submissions being processed.
   *
   * @var array
   */
  protected static $submissions = [];

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = get_class($this);
    return [
      '#process' => [
        [
          $class,
          'processWebformComputed',
        ],
      ],
      '#input' => TRUE,
      '#template' => '',
      '#mode' => NULL,
      '#hide_empty' => FALSE,
      // Note: Computed elements do not use the default #ajax wrapper, which is
      // why we can use #ajax as a boolean.
      // @see \Drupal\Core\Render\Element\RenderElement::preRenderAjaxForm
      '#ajax' => FALSE,
      '#webform_submission' => NULL,
      '#theme_wrappers' => [
        'form_element',
      ],
    ];
  }

  /**
   * Processes a Webform computed token element.
   *
   * @param array $element
   *   The element.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param array $complete_form
   *   The complete form structure.
   *
   * @return array
   *   The processed element.
   */
  public static function processWebformComputed(&$element, FormStateInterface $form_state, &$complete_form) {
    $webform_submission = static::getWebformSubmission($element, $form_state, $complete_form);
    if ($webform_submission) {

      // Set tree.
      $element['#tree'] = TRUE;

      // Set #type to item to trigger #states behavior.
      // @see \Drupal\Core\Form\FormHelper::processStates;
      $element['#type'] = 'item';
      $value = static::computeValue($element, $webform_submission);
      static::setWebformComputedElementValue($element, $value);
    }
    if (!empty($element['#states'])) {
      if (!empty($element['#ajax'])) {

        // The element's #states must be place outside the
        // computed ajax wrapper.
        WebformElementHelper::fixStatesWrapper($element);
      }
      else {
        WebformFormHelper::processStates($element, '#wrapper_attributes');
      }
    }

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

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

    // Ajax support

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

    // Enabled Ajax support only for computed elements associated with a
    // webform submission form.
    if ($element['#ajax'] && $form_state
      ->getFormObject() instanceof WebformSubmissionForm) {

      // Get button name and wrapper id.
      $button_name = 'webform-computed-' . implode('-', $element['#parents']) . '-button';
      $wrapper_id = 'webform-computed-' . implode('-', $element['#parents']) . '-wrapper';

      // Get computed value element keys which are used to trigger Ajax updates.
      preg_match_all('/(?:\\[webform_submission:values:|data\\.|data\\[\')([_a-z0-9]+)/', $element['#template'], $matches);
      $element_keys = $matches[1] ?: [];
      $element_keys = array_unique($element_keys);

      // Wrapping the computed element is two div tags.
      // div.js-webform-computed is used to initialize the Ajax updates.
      // div#wrapper_id is used to display response from the Ajax updates.
      $element += [
        '#prefix' => '',
        '#suffix' => '',
      ];
      $element['#wrapper_id'] = $wrapper_id;
      $element['#prefix'] .= '<div class="js-webform-computed" data-webform-element-keys="' . implode(',', $element_keys) . '">' . '<div class="js-webform-computed-wrapper" id="' . $wrapper_id . '">';
      $element['#suffix'] = '</div></div>' . $element['#suffix'];

      // Add hidden update button.
      $element['update'] = [
        '#type' => 'submit',
        '#value' => t('Update'),
        '#validate' => [
          [
            get_called_class(),
            'validateWebformComputedCallback',
          ],
        ],
        '#submit' => [
          [
            get_called_class(),
            'submitWebformComputedCallback',
          ],
        ],
        '#ajax' => [
          'callback' => [
            get_called_class(),
            'ajaxWebformComputedCallback',
          ],
          'wrapper' => $wrapper_id,
          'progress' => [
            'type' => 'none',
          ],
        ],
        // Disable validation, hide button, add submit button trigger class.
        '#attributes' => [
          'formnovalidate' => 'formnovalidate',
          'class' => [
            'js-hide',
            'js-webform-computed-submit',
          ],
        ],
        // Issue #1342066 Document that buttons with the same #value need a unique
        // #name for the Form API to distinguish them, or change the Form API to
        // assign unique #names automatically.
        '#name' => $button_name,
      ];

      // Attached computed element Ajax library.
      $element['#attached']['library'][] = 'webform/webform.element.computed';
    }
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public static function computeValue(array $element, WebformSubmissionInterface $webform_submission) {
    return $element['#template'];
  }

  /**
   * Validates an computed element.
   */
  public static function validateWebformComputed(&$element, FormStateInterface $form_state, &$complete_form) {

    // Make sure the form's state value uses the computed value and not the
    // raw #value. This ensures conditional handlers are triggered using
    // the accurate computed value.
    $webform_submission = static::getWebformSubmission($element, $form_state, $complete_form);
    if ($webform_submission) {
      $value = static::computeValue($element, $webform_submission);
      $form_state
        ->setValueForElement($element['value'], NULL);
      $form_state
        ->setValueForElement($element['hidden'], NULL);
      $form_state
        ->setValueForElement($element, $value);
    }
  }

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

  // Form/Ajax callbacks.

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

  /**
   * Set computed element's value.
   *
   * @param array $element
   *   A computed element.
   * @param string $value
   *   A computer value.
   */
  protected static function setWebformComputedElementValue(array &$element, $value) {

    // Hide empty computed element using display:none so that #states API
    // can still use the empty computed value.
    if ($element['#hide_empty']) {
      $element += [
        '#wrapper_attributes' => [],
      ];
      $element['#wrapper_attributes'] += [
        'style' => '',
      ];
      if ($value === '') {
        $element['#wrapper_attributes']['style'] .= ($element['#wrapper_attributes']['style'] ? ';' : '') . 'display:none';
      }
      else {
        $element['#wrapper_attributes']['style'] = preg_replace('/;?display:none/', '', $element['#wrapper_attributes']['style']);
      }
    }

    // Display markup.
    $element['value']['#markup'] = $value;
    $element['value']['#allowed_tags'] = WebformXss::getAdminTagList();

    // Include hidden element so that computed value will be available to
    // conditions (#states).
    $element['hidden']['#type'] = 'hidden';
    $element['hidden']['#value'] = [
      '#markup' => $value,
    ];
    $element['hidden']['#parents'] = $element['#parents'];
  }

  /**
   * Determine if the current request is using Ajax.
   */
  protected static function isAjax() {

    // return (\Drupal::request()->get(MainContentViewSubscriber::WRAPPER_FORMAT) === 'drupal_ajax');
    //
    // ISSUE:
    // For nodes with computed elements there is a duplicate
    // _wrapper_format parameter.
    // (i.e ?_wrapper_format=html&_wrapper_format=drupal_ajax)
    // WORKAROUND:
    // See if _wrapper_format=drupal_ajax is being appended to the query string.
    $querystring = \Drupal::request()
      ->getQueryString();
    return strpos($querystring, MainContentViewSubscriber::WRAPPER_FORMAT . '=drupal_ajax') !== FALSE;
  }

  /**
   * Webform computed element validate callback.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public static function validateWebformComputedCallback(array $form, FormStateInterface $form_state) {
    $form_state
      ->clearErrors();
  }

  /**
   * Webform computed element submit callback.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public static function submitWebformComputedCallback(array $form, FormStateInterface $form_state) {

    // Only rebuild if the request is not using Ajax.
    if (!static::isAjax()) {
      $form_state
        ->setRebuild();
    }
  }

  /**
   * Webform computed element Ajax callback.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @return array
   *   The computed element element.
   */
  public static function ajaxWebformComputedCallback(array $form, FormStateInterface $form_state) {
    $button = $form_state
      ->getTriggeringElement();
    $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));

    // Set element value and #markup  after the form has been validated.
    $webform_submission = static::getWebformSubmission($element, $form_state, $form);
    $value = static::computeValue($element, $webform_submission);
    static::setWebformComputedElementValue($element, $value);

    // Only return the wrapper id, this prevents the computed element from
    // being reinitialized via JS after each update.
    // @see js/webform.element.computed.js
    //
    // The announce attribute allows FAPI Ajax callbacks to easily
    // trigger announcements.
    // @see js/webform.announce.js
    $t_args = [
      '@title' => $element['#title'],
      '@value' => strip_tags($value),
    ];
    $attributes = [
      'class' => [
        'js-webform-computed-wrapper',
      ],
      'id' => $element['#wrapper_id'],
      'data-webform-announce' => t('@title is @value', $t_args),
    ];
    $element['#prefix'] = '<div' . new Attribute($attributes) . '>';
    $element['#suffix'] = '</div>';

    // Disable states and flexbox wrapper.
    // @see \Drupal\webform\Plugin\WebformElementBase::preRenderFixFlexboxWrapper
    $element['#webform_wrapper'] = FALSE;
    return $element;
  }

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

  // Form/Ajax helpers and callbacks.

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

  /**
   * Get an element's value mode/type.
   *
   * @param array $element
   *   The element.
   *
   * @return string
   *   The markup type (html or text).
   */
  public static function getMode(array $element) {
    if (empty($element['#mode']) || $element['#mode'] === WebformComputedInterface::MODE_AUTO) {
      return WebformHtmlHelper::containsHtml($element['#template']) ? WebformComputedInterface::MODE_HTML : WebformComputedInterface::MODE_TEXT;
    }
    else {
      return $element['#mode'];
    }
  }

  /**
   * Get the Webform submission for element.
   *
   * @param array $element
   *   The element.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param array $complete_form
   *   The complete form structure.
   *
   * @return \Drupal\webform\WebformSubmissionInterface|null
   *   A webform submission.
   */
  protected static function getWebformSubmission(array $element, FormStateInterface $form_state, array &$complete_form) {
    $form_object = $form_state
      ->getFormObject();
    if ($form_object instanceof WebformSubmissionForm) {

      /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */
      $webform_submission = $form_object
        ->getEntity();

      // We must continually copy validated form values to the
      // webform submission since a computed element's value can be based on
      // another computed element's value.
      //
      // Therefore, we are creating a single clone of the webform submission
      // and only copying the submitted form values to the cached submission.
      if ($form_state
        ->isValidationComplete() && !$form_state
        ->isRebuilding()) {
        if (!isset(static::$submissions[$webform_submission
          ->uuid()])) {
          static::$submissions[$webform_submission
            ->uuid()] = clone $form_object
            ->getEntity();
        }
        $webform_submission = static::$submissions[$webform_submission
          ->uuid()];
        $form_object
          ->copyFormValuesToEntity($webform_submission, $complete_form, $form_state);
      }
      return $webform_submission;
    }
    elseif (isset($element['#webform_submission'])) {
      if (is_string($element['#webform_submission'])) {
        return WebformSubmission::load($element['#webform_submission']);
      }
      else {
        return $element['#webform_submission'];
      }
    }
    else {
      return 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.
FormElement::valueCallback public static function Determines how user input is mapped to an element's #value property. Overrides FormElementInterface::valueCallback 15
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.
WebformComputedBase::$submissions protected static property Cache of submissions being processed.
WebformComputedBase::ajaxWebformComputedCallback public static function Webform computed element Ajax callback.
WebformComputedBase::computeValue public static function Compute value. Overrides WebformComputedInterface::computeValue 2
WebformComputedBase::getInfo public function Returns the element properties for this element. Overrides ElementInterface::getInfo 1
WebformComputedBase::getMode public static function Get an element's value mode/type.
WebformComputedBase::getWebformSubmission protected static function Get the Webform submission for element.
WebformComputedBase::isAjax protected static function Determine if the current request is using Ajax.
WebformComputedBase::processWebformComputed public static function Processes a Webform computed token element.
WebformComputedBase::setWebformComputedElementValue protected static function Set computed element's value.
WebformComputedBase::submitWebformComputedCallback public static function Webform computed element submit callback.
WebformComputedBase::validateWebformComputed public static function Validates an computed element.
WebformComputedBase::validateWebformComputedCallback public static function Webform computed element validate callback.
WebformComputedInterface::MODE_AUTO constant Denotes markup whose content type should be detected.
WebformComputedInterface::MODE_HTML constant Denotes HTML.
WebformComputedInterface::MODE_TEXT constant Denotes plain text.