You are here

class WebformSubmissionForm in Webform 6.x

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

Provides a webform to collect and edit submissions.

Hierarchy

Expanded class hierarchy of WebformSubmissionForm

13 files declare their use of WebformSubmissionForm
Captcha.php in src/Plugin/WebformElement/Captcha.php
webform.form_alter.inc in includes/webform.form_alter.inc
Webform module form alter hooks.
WebformAjaxFormTrait.php in src/Form/WebformAjaxFormTrait.php
WebformAttachmentBase.php in modules/webform_attachment/src/Element/WebformAttachmentBase.php
WebformComputedBase.php in src/Element/WebformComputedBase.php

... See full list

File

src/WebformSubmissionForm.php, line 42

Namespace

Drupal\webform
View source
class WebformSubmissionForm extends ContentEntityForm {
  use WebformDialogFormTrait;

  /**
   * The configuration object factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The renderer service.
   *
   * @var \Drupal\Core\Render\RendererInterface
   */
  protected $renderer;

  /**
   * The form builder.
   *
   * @var \Drupal\Core\Form\FormBuilderInterface
   */
  protected $formBuilder;

  /**
   * The kill switch.
   *
   * @var \Drupal\Core\PageCache\ResponsePolicy\KillSwitch
   */
  protected $killSwitch;

  /**
   * The path alias manager.
   *
   * @var \Drupal\path_alias\AliasManagerInterface
   */
  protected $aliasManager;

  /**
   * The path validator service.
   *
   * @var \Drupal\Core\Path\PathValidatorInterface
   */
  protected $pathValidator;

  /**
   * The entity field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $entityFieldManager;

  /**
   * The selection plugin manager service.
   *
   * @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface
   */
  protected $selectionManager;

  /**
   * The webform element plugin manager.
   *
   * @var \Drupal\webform\Plugin\WebformElementManagerInterface
   */
  protected $elementManager;

  /**
   * The webform request handler.
   *
   * @var \Drupal\webform\WebformRequestInterface
   */
  protected $requestHandler;

  /**
   * The webform third party settings manager.
   *
   * @var \Drupal\webform\WebformThirdPartySettingsManagerInterface
   */
  protected $thirdPartySettingsManager;

  /**
   * The webform message manager.
   *
   * @var \Drupal\webform\WebformMessageManagerInterface
   */
  protected $messageManager;

  /**
   * The webform token manager.
   *
   * @var \Drupal\webform\WebformTokenManagerInterface
   */
  protected $tokenManager;

  /**
   * The webform submission conditions (#states) validator.
   *
   * @var \Drupal\webform\WebformSubmissionConditionsValidatorInterface
   */
  protected $conditionsValidator;

  /**
   * The webform entity reference manager.
   *
   * @var \Drupal\webform\WebformEntityReferenceManagerInterface
   */
  protected $webformEntityReferenceManager;

  /**
   * The webform submission generation service.
   *
   * @var \Drupal\webform\WebformSubmissionGenerateInterface
   */
  protected $generate;

  /**
   * The webform submission.
   *
   * @var \Drupal\webform\WebformSubmissionInterface
   */
  protected $entity;

  /**
   * The source entity.
   *
   * @var \Drupal\Core\Entity\EntityInterface
   */
  protected $sourceEntity;

  /**
   * States API prefix.
   *
   * @var string
   */
  protected $statesPrefix;

  /**
   * Stores the original submission data passed via the EntityFormBuilder.
   *
   * @var array
   *
   * @see \Drupal\webform\WebformSubmissionForm::setEntity
   */
  protected $originalData;

  /**
   * Bubbleable metadata.
   *
   * @var \Drupal\webform\Cache\WebformBubbleableMetadata
   *
   * @see \Drupal\webform\WebformSubmissionForm::buildForm
   */
  protected $bubbleableMetadata;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    $instance = parent::create($container);
    $instance->configFactory = $container
      ->get('config.factory');
    $instance->renderer = $container
      ->get('renderer');
    $instance->formBuilder = $container
      ->get('form_builder');
    $instance->killSwitch = $container
      ->get('page_cache_kill_switch');
    $instance->aliasManager = $container
      ->get('path_alias.manager');
    $instance->pathValidator = $container
      ->get('path.validator');
    $instance->selectionManager = $container
      ->get('plugin.manager.entity_reference_selection');
    $instance->entityFieldManager = $container
      ->get('entity_field.manager');
    $instance->requestHandler = $container
      ->get('webform.request');
    $instance->elementManager = $container
      ->get('plugin.manager.webform.element');
    $instance->thirdPartySettingsManager = $container
      ->get('webform.third_party_settings_manager');
    $instance->messageManager = $container
      ->get('webform.message_manager');
    $instance->tokenManager = $container
      ->get('webform.token_manager');
    $instance->conditionsValidator = $container
      ->get('webform_submission.conditions_validator');
    $instance->webformEntityReferenceManager = $container
      ->get('webform.entity_reference_manager');
    $instance->generate = $container
      ->get('webform_submission.generate');
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function getBaseFormId() {
    $base_form_id = $this->entity
      ->getEntityTypeId();
    $base_form_id .= '_' . $this->entity
      ->bundle();
    return $base_form_id . '_form';
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    $form_id = $this->entity
      ->getEntityTypeId();
    $form_id .= '_' . $this->entity
      ->bundle();
    if ($source_entity = $this->entity
      ->getSourceEntity()) {
      $form_id .= '_' . $source_entity
        ->getEntityTypeId() . '_' . $source_entity
        ->id();
    }
    if ($this->operation !== 'default') {
      $form_id .= '_' . $this->operation;
    }
    return $form_id . '_form';
  }

  /**
   * {@inheritdoc}
   */
  protected function getWrapperId() {

    // Get the form id with the source entity but without the operation.
    $form_id = $this->entity
      ->getEntityTypeId();
    $form_id .= '_' . $this->entity
      ->bundle();
    if ($source_entity = $this->entity
      ->getSourceEntity()) {
      $form_id .= '_' . $source_entity
        ->getEntityTypeId() . '_' . $source_entity
        ->id();
    }
    $form_id .= '_form';
    return Html::getId($form_id . '-ajax');
  }

  /**
   * {@inheritdoc}
   */
  protected function init(FormStateInterface $form_state) {
    parent::init($form_state);
    $this
      ->getWebform()
      ->invokeHandlers('prepareForm', $this->entity, $this->operation, $form_state);
  }

  /**
   * {@inheritdoc}
   *
   * This is the best place to override an entity form's default settings
   * because it is called immediately after the form object is initialized.
   *
   * @see \Drupal\Core\Entity\EntityFormBuilder::getForm
   */
  public function setEntity(EntityInterface $entity) {

    // Create new metadata to be applie when the form is built.
    // @see \Drupal\webform\WebformSubmissionForm::buildForm
    $this->bubbleableMetadata = new WebformBubbleableMetadata();

    /** @var \Drupal\webform\WebformSubmissionInterface $entity */
    $webform = $entity
      ->getWebform();

    // Initialize the webform submission entity by getting it default data
    // and storing its original data.
    if (!isset($this->originalData)) {

      // Store the original data passed via the EntityFormBuilder.
      // This allows us to reset the submission to it's original state
      // via ::reset.
      // @see \Drupal\Core\Entity\EntityFormBuilder::getForm
      // @see \Drupal\webform\Entity\Webform::getSubmissionForm
      // @see \Drupal\webform\WebformSubmissionForm::reset
      $this->originalData = $entity
        ->getRawData();
    }

    // Get the submission data and only call WebformSubmission::setData once.
    $data = $entity
      ->getRawData();

    // If ?_webform_test is defined for the current webform, override
    // the 'add' operation with 'test' operation.
    if ($this->operation === 'add' && $this
      ->getRequest()->query
      ->get('_webform_test') === $webform
      ->id() && $webform
      ->access('test')) {
      $this->operation = 'test';
    }

    // Generate test data.
    if ($this->operation === 'test' && $webform
      ->access('test')) {
      $webform
        ->applyVariants($entity);
      $data = $webform
        ->getVariantsData($entity) + $this->generate
        ->getData($webform) + $data;
    }

    // Get the source entity and allow webform submission to be used as a source
    // entity.
    $source_entity = $entity
      ->getSourceEntity(TRUE) ?: $this->requestHandler
      ->getCurrentSourceEntity([
      'webform',
    ]);
    if ($source_entity === $entity) {
      $source_entity = $this->requestHandler
        ->getCurrentSourceEntity([
        'webform',
        'webform_submission',
      ]);
    }

    // Handle paragraph source entity.
    if ($source_entity && $source_entity
      ->getEntityTypeId() === 'paragraph') {

      // Disable :clear suffix to prevent webform tokens from being removed.
      $data = $this->tokenManager
        ->replace($data, $source_entity, [], [
        'suffixes' => [
          'clear' => FALSE,
        ],
      ], $this->bubbleableMetadata);
      $source_entity = WebformSourceEntityManager::getMainSourceEntity($source_entity);
    }

    // Set source entity.
    $this->sourceEntity = $source_entity;

    // Get account.
    $account = $this
      ->currentUser();

    // Load entity from token or saved draft when not editing or testing
    // submission form.
    if (!in_array($this->operation, [
      'edit',
      'edit_all',
      'test',
    ])) {
      $token = $this
        ->getRequest()->query
        ->get('token');
      $webform_submission_token = $this
        ->getStorage()
        ->loadFromToken($token, $webform, $source_entity);
      if ($webform_submission_token) {
        $entity = $webform_submission_token;
        $data = $entity
          ->getRawData();
        $this->operation = 'edit';
      }
      elseif ($webform
        ->getSetting('draft') !== WebformInterface::DRAFT_NONE) {
        if ($webform
          ->getSetting('draft_multiple')) {

          // Allow multiple drafts to be restored using token.
          // This allows the webform's public facing URL to be used instead of
          // the admin URL of the webform.
          $webform_submission_token = $this
            ->getStorage()
            ->loadFromToken($token, $webform, $source_entity, $account);
          if ($webform_submission_token && $webform_submission_token
            ->isDraft()) {
            $entity = $webform_submission_token;
            $data = $entity
              ->getRawData();
          }
          elseif (isset($_POST['webform_submission_token'])) {
            $webform_submission_token = $this
              ->getStorage()
              ->loadFromToken($_POST['webform_submission_token'], $webform, $source_entity, $account);
            if ($webform_submission_token && $webform_submission_token
              ->isDraft()) {
              $entity = $webform_submission_token;
              $data = $entity
                ->getRawData();
            }
          }
        }
        elseif ($webform_submission_draft = $this
          ->getStorage()
          ->loadDraft($webform, $source_entity, $account)) {

          // Else load the most recent draft.
          $entity = $webform_submission_draft;
          $data = $entity
            ->getRawData();
        }
      }
    }

    // Set entity before calling get last submission.
    $this->entity = $entity;
    if ($entity
      ->isNew()) {
      $last_submission = NULL;
      if ($webform
        ->getSetting('limit_total_unique')) {

        // Require user to have update any submission access.
        if (!$webform
          ->access('submission_view_any') || !$webform
          ->access('submission_update_any')) {
          throw new AccessDeniedHttpException();
        }

        // Get last webform/source entity submission.
        $last_submission = $this
          ->getStorage()
          ->getLastSubmission($webform, $source_entity, NULL, [
          'in_draft' => FALSE,
        ]);
      }
      elseif ($webform
        ->getSetting('limit_user_unique')) {

        // Require user to be authenticated to access a unique submission.
        if (!$account
          ->isAuthenticated()) {
          throw new AccessDeniedHttpException();
        }

        // Require user to have update own submission access.
        if (!$webform
          ->access('submission_view_own') || !$webform
          ->access('submission_update_own')) {
          throw new AccessDeniedHttpException();
        }

        // Get last user submission.
        $last_submission = $this
          ->getStorage()
          ->getLastSubmission($webform, $source_entity, $account, [
          'in_draft' => FALSE,
        ]);
      }

      // If the webform is closed and user can not update any submission,
      // block the submission from being updated.
      if ($webform
        ->isClosed() && !$webform
        ->access('submission_update_any')) {
        $last_submission = NULL;
      }

      // Set last submission and switch to the edit operation.
      if ($last_submission) {
        $entity = $last_submission;
        $data = $entity
          ->getRawData();
        $this->operation = 'edit';
      }
    }

    // Autofill with previous submission.
    if ($this->operation === 'add' && $entity
      ->isNew() && $webform
      ->getSetting('autofill')) {
      if ($last_submission = $this
        ->getStorage()
        ->getLastSubmission($webform, $source_entity, $account, [
        'in_draft' => FALSE,
        'access_check' => FALSE,
      ])) {
        $excluded_elements = $webform
          ->getSetting('autofill_excluded_elements') ?: [];
        $last_submission_data = array_diff_key($last_submission
          ->getRawData(), $excluded_elements);
        $data = $last_submission_data + $data;
      }
    }

    // Get default data and append it to the submission's data.
    // This allows computed elements to be executed and tokens
    // to be replaced using the webform's default data.
    $default_data = $webform
      ->getElementsDefaultData();
    $default_data = $this->tokenManager
      ->replace($default_data, $entity, [], [], $this->bubbleableMetadata);
    $data += $default_data;

    // Set data and calculate computed values.
    $entity
      ->setData($data);

    // Override settings.
    $this
      ->overrideSettings($entity);

    // Set the webform's current operation.
    $webform
      ->setOperation($this->operation);
    return parent::setEntity($entity);
  }

  /**
   * {@inheritdoc}
   */
  public function buildEntity(array $form, FormStateInterface $form_state) {

    /** @var \Drupal\webform\WebformSubmissionInterface $entity */
    $entity = parent::buildEntity($form, $form_state);

    // Override settings.
    $this
      ->overrideSettings($entity);
    return $entity;
  }

  /**
   * Override webform settings for the webform submission.
   *
   * @param \Drupal\webform\WebformSubmissionInterface $webform_submission
   *   A webform submission.
   */
  protected function overrideSettings(WebformSubmissionInterface $webform_submission) {
    $webform = $webform_submission
      ->getWebform();

    // Invoke override settings which resets the webform settings.
    $webform
      ->invokeHandlers('overrideSettings', $webform_submission);

    // Look for ?_webform_dialog=1 which enables Ajax support when this form is
    // opened in dialog.
    // @see webform.dialog.js
    //
    // Must be called after WebformHandler::overrideSettings which resets all
    // overridden settings.
    // @see \Drupal\webform\Entity\Webform::invokeHandlers
    if ($this
      ->getRequest()->query
      ->get('_webform_dialog') && !$webform
      ->getSetting('ajax', TRUE)) {
      $webform
        ->setSettingOverride('ajax', TRUE);
    }

    // If share page (i.e. /webform_share/{webform}), enable Ajax support
    // when this form is embedded in an iframe.
    if ($this
      ->isSharePage() && !$webform
      ->getSetting('ajax', TRUE)) {
      $webform
        ->setSettingOverride('ajax', TRUE);
    }

    // Apply source entity open/close state to the webform before
    // it is rendered.
    $source_entity = $webform_submission
      ->getSourceEntity();
    if ($webform
      ->isOpen() && $source_entity && $source_entity instanceof FieldableEntityInterface) {
      foreach ($source_entity
        ->getFieldDefinitions() as $fieldName => $fieldDefinition) {
        if ($fieldDefinition
          ->getType() === 'webform') {
          $item = $source_entity
            ->get($fieldName);
          if ($item->target_id === $webform
            ->id()) {
            $webform
              ->setOverride()
              ->set('open', $item->open)
              ->set('close', $item->close)
              ->setStatus($item->status);
          }
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {

    // NOTE: We are not copying form values to the entity because
    // webform element keys can override webform submission properties.

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

    // Get elements values from webform submission and merge existing data.
    $element_values = array_intersect_key($form_state
      ->getValues(), $webform
      ->getElementsInitializedFlattenedAndHasValue());
    $webform_submission
      ->setData($element_values + $webform_submission
      ->getData());

    // Get field values.
    // This used to support the Workflows Field module.
    // @see https://www.drupal.org/project/webform/issues/3002547
    // @see https://www.drupal.org/project/workflows_field
    $base_field_definitions = $this->entityFieldManager
      ->getBaseFieldDefinitions($entity
      ->getEntityTypeId());
    $all_fields = $webform_submission
      ->getFields(FALSE);
    $bundle_fields = array_diff_key($all_fields, $base_field_definitions);
    $field_values = array_intersect_key($form_state
      ->getValues(), $bundle_fields);
    foreach ($field_values as $name => $field_value) {
      $webform_submission
        ->set($name, $field_value);
    }

    // Set current page.
    if ($current_page = $this
      ->getCurrentPage($form, $form_state)) {
      $entity
        ->setCurrentPage($current_page);
    }

    // Set in draft.
    $in_draft = $form_state
      ->get('in_draft');
    if ($in_draft !== NULL) {
      $entity
        ->set('in_draft', $in_draft);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {

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

    // Track the 'webform_submission_token' form multiple draft submissions.
    // The 'webform_submission_token' value is set after the form is cached
    // and built.
    // @see \Drupal\webform\WebformSubmissionForm::afterBuild
    if ($webform
      ->getSetting('draft_multiple') && ($webform_submission
      ->isNew() || $webform_submission
      ->isDraft())) {
      $form['webform_submission_token'] = [
        '#type' => 'hidden',
        '#value' => $webform_submission
          ->getToken(),
      ];
    }

    // Only prepopulate data when a webform is initially loaded.
    if (!$form_state
      ->isRebuilding()) {
      $data = $webform_submission
        ->getData();
      $this
        ->prepopulateData($data);
      $webform_submission
        ->setData($data);
    }

    // Apply variants.
    $webform
      ->applyVariants($webform_submission);

    // Add cache dependency to the form's render array.
    $this
      ->addCacheableDependency($form);

    // Add the webform as a cacheable dependency.
    $this->renderer
      ->addCacheableDependency($form, $webform);

    // Kill page cache for scheduled webforms.
    // @todo Remove once bubbling of element's max-age to page cache is fixed.
    // @see https://www.drupal.org/project/webform/issues/3015760
    // @see https://www.drupal.org/project/drupal/issues/2352009
    // @see \Drupal\webform\Element\Webform::preRenderWebformElement
    if ($webform
      ->isScheduled() && $this
      ->currentUser()
      ->isAnonymous() && $this->moduleHandler
      ->moduleExists('page_cache')) {
      $this->killSwitch
        ->trigger();
    }

    // Display status messages.
    $this
      ->displayMessages($form, $form_state);

    // Build the webform.
    $form = parent::buildForm($form, $form_state);

    // Ajax: Scroll to.
    // @see \Drupal\webform\Form\WebformAjaxFormTrait::submitAjaxForm
    if ($this
      ->isAjax()) {
      $form['#webform_ajax_scroll_top'] = $this
        ->getWebformSetting('ajax_scroll_top', '');
    }

    // Alter element's form.
    if (isset($form['elements']) && is_array($form['elements'])) {
      $elements = $form['elements'];
      $this
        ->alterElementsForm($elements, $form, $form_state);
    }

    // Add Ajax callbacks.
    $ajax_settings = [
      'effect' => $this
        ->getWebformSetting('ajax_effect'),
      'speed' => (int) $this
        ->getWebformSetting('ajax_speed'),
      'progress' => [
        'type' => $this
          ->getWebformSetting('ajax_progress_type'),
        'message' => '',
      ],
    ];
    $form = $this
      ->buildAjaxForm($form, $form_state, $ajax_settings);

    // Alter webform via webform handler.
    $this
      ->getWebform()
      ->invokeHandlers('alterForm', $form, $form_state, $webform_submission);

    // Call custom webform alter hook.
    $form_id = $this
      ->getFormId();
    $this->thirdPartySettingsManager
      ->alter('webform_submission_form', $form, $form_state, $form_id);

    // Server side #states API validation.
    $this->conditionsValidator
      ->buildForm($form, $form_state);

    // Append the bubbleable metadat to the form's render array.
    // @see \Drupal\webform\WebformSubmissionForm::setEntity
    $this->bubbleableMetadata
      ->appendTo($form);
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function form(array $form, FormStateInterface $form_state) {

    /* @var $webform_submission \Drupal\webform\WebformSubmissionInterface */
    $webform_submission = $this
      ->getEntity();
    $source_entity = $webform_submission
      ->getSourceEntity();
    $webform = $this
      ->getWebform();

    // Add a reference to the webform's id to the $form render array.
    $form['#webform_id'] = $webform
      ->id();

    // Track current page name or index by setting the
    // "data-webform-wizard-page"
    // attribute which is used Drupal.behaviors.webformWizardTrackPage.
    //
    // The data parameter is append to the URL after the form has
    // been submitted.
    //
    // @see js/webform.wizard.track.js
    $track = $this
      ->getWebform()
      ->getSetting('wizard_track');
    if ($track && $this
      ->getRequest()
      ->isMethod('POST')) {
      $current_page = $this
        ->getCurrentPage($form, $form_state);
      if ($track === 'index') {
        $pages = $this
          ->getWebform()
          ->getPages($this->operation);
        $track_pages = array_flip(array_keys($pages));
        $form['#attributes']['data-webform-wizard-current-page'] = $track_pages[$current_page] + 1;
      }
      else {
        $form['#attributes']['data-webform-wizard-current-page'] = $current_page;
      }
    }

    // Disable the default $form['#theme'] templates.
    // If the webform's id begins with an underscore the #theme
    // was automatically being set to 'webform_submission__WEBFORM_ID', this
    // causes the form to be rendered using the 'webform_submission' template.
    // @see \Drupal\Core\Form\FormBuilder::prepareForm
    // @see webform-submission-form.html.twig
    $form['#theme'] = [
      'webform_submission_form',
    ];

    // Define very specific webform classes, this override the form's
    // default classes.
    // @see \Drupal\Core\Form\FormBuilder::retrieveForm
    $webform_id = Html::cleanCssIdentifier($webform
      ->id());
    $operation = $this->operation;
    $class = [];
    $class[] = "webform-submission-form";
    $class[] = "webform-submission-{$operation}-form";
    $class[] = "webform-submission-{$webform_id}-form";
    $class[] = "webform-submission-{$webform_id}-{$operation}-form";
    if ($source_entity) {
      $source_entity_type = $source_entity
        ->getEntityTypeId();
      $source_entity_id = $source_entity
        ->id();
      $class[] = "webform-submission-{$webform_id}-{$source_entity_type}-{$source_entity_id}-form";
      $class[] = "webform-submission-{$webform_id}-{$source_entity_type}-{$source_entity_id}-{$operation}-form";
    }
    array_walk($class, [
      '\\Drupal\\Component\\Utility\\Html',
      'getClass',
    ]);
    $form['#attributes']['class'] = $class;

    // Get last class, which is the most specific, as #states prefix.
    // @see \Drupal\webform\WebformSubmissionForm::addStatesPrefix
    $this->statesPrefix = '.' . end($class);

    // Check for a custom webform, track it, and return it.
    if ($custom_form = $this
      ->getCustomForm($form, $form_state)) {
      $custom_form['#custom_form'] = TRUE;
      return $custom_form;
    }
    $form = parent::form($form, $form_state);

    /* Information */

    // Prepend webform submission data using the default view without the data.
    if (!$webform_submission
      ->isNew() && !$webform_submission
      ->isDraft()) {
      $form['navigation'] = [
        '#type' => 'webform_submission_navigation',
        '#webform_submission' => $webform_submission,
        '#weight' => -20,
      ];
      $form['information'] = [
        '#type' => 'webform_submission_information',
        '#webform_submission' => $webform_submission,
        '#source_entity' => $this->sourceEntity,
        '#weight' => -19,
      ];
    }

    /* Confirmation */

    // Add confirmation modal.
    if ($webform_confirmation_modal = $form_state
      ->get('webform_confirmation_modal')) {
      $form['webform_confirmation_modal'] = [
        '#type' => 'webform_message',
        '#message_type' => 'status',
        '#message_message' => [
          'title' => [
            '#markup' => $webform_confirmation_modal['title'],
            '#prefix' => '<b class="webform-confirmation-modal--title">',
            '#suffix' => '</b><br/>',
          ],
          'content' => [
            'content' => $webform_confirmation_modal['content'],
            '#prefix' => '<div class="webform-confirmation-modal--content">',
            '#suffix' => '</div>',
          ],
        ],
        '#attributes' => [
          'class' => [
            'js-hide',
            'webform-confirmation-modal',
            'js-webform-confirmation-modal',
          ],
        ],
        '#weight' => -1000,
        '#attached' => [
          'library' => [
            'webform/webform.confirmation.modal',
          ],
        ],
        '#element_validate' => [
          '::removeConfirmationModal',
        ],
      ];
    }

    /* Data */

    // Get and prepopulate (via query string) submission data.
    $data = $webform_submission
      ->getData();

    /* Elements */

    // Get webform elements.
    $elements = $webform_submission
      ->getWebform()
      ->getElementsInitialized();

    // Populate webform elements with webform submission data.
    $this
      ->populateElements($elements, $data);

    // Prepare webform elements.
    $this
      ->prepareElements($elements, $form, $form_state);

    // Add wizard progress tracker and page links to the webform.
    $pages = $webform
      ->getPages($this->operation);
    if ($pages && $operation !== 'edit_all') {
      $current_page = $this
        ->getCurrentPage($form, $form_state);

      // Add hidden pages submit actions.
      $form['pages'] = $this
        ->pagesElement($form, $form_state);

      // Add progress tracker.
      $display_wizard_progress = $this
        ->getWebformSetting('wizard_progress_bar') || $this
        ->getWebformSetting('wizard_progress_pages') || $this
        ->getWebformSetting('wizard_progress_percentage');
      if ($current_page && $display_wizard_progress) {
        $form['progress'] = [
          '#theme' => 'webform_progress',
          '#webform' => $this
            ->getWebform(),
          '#webform_submission' => $webform_submission,
          '#current_page' => $current_page,
          '#operation' => $this->operation,
          '#weight' => -20,
        ];
      }
    }

    // Required indicator.
    $current_page = $this
      ->getCurrentPage($form, $form_state);
    if ($current_page !== WebformInterface::PAGE_PREVIEW && $this
      ->getWebformSetting('form_required') && $webform
      ->hasRequired()) {
      $form['required'] = [
        '#theme' => 'webform_required',
        '#label' => $this
          ->getWebformSetting('form_required_label'),
      ];
    }

    // Append elements to the webform.
    $form['elements'] = $elements;

    // Pages: Set current wizard or preview page.
    $this
      ->displayCurrentPage($form, $form_state);

    // Move all $elements properties to the $form.
    $this
      ->setFormPropertiesFromElements($form, $elements);

    // Attach libraries to the form.
    $this
      ->attachLibraries($form, $form_state);

    // Attach behaviors to the form.
    $this
      ->attachBehaviors($form, $form_state);

    // Add #after_build callbacks.
    $form['#after_build'][] = '::afterBuild';
    return $form;
  }

  /**
   * Get custom webform which is displayed instead of the webform's elements.
   *
   * @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|bool
   *   A custom webform or FALSE if the default webform containing the webform's
   *   elements should be built.
   */
  protected function getCustomForm(array &$form, FormStateInterface $form_state) {

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

    // Exit if elements are broken, usually occurs when elements YAML is edited
    // directly in the export config file.
    if (!$webform_submission
      ->getWebform()
      ->getElementsInitialized()) {
      return $this
        ->getMessageManager()
        ->append($form, WebformMessageManagerInterface::FORM_EXCEPTION_MESSAGE, 'warning');
    }

    // Exit if submission is locked.
    if ($webform_submission
      ->isLocked()) {
      return $this
        ->getMessageManager()
        ->append($form, WebformMessageManagerInterface::SUBMISSION_LOCKED_MESSAGE, 'warning');
    }

    // Check prepopulate source entity required and type.
    if ($webform
      ->getSetting('form_prepopulate_source_entity')) {
      if ($webform
        ->getSetting('form_prepopulate_source_entity_required') && empty($this
        ->getSourceEntity())) {
        $this
          ->getMessageManager()
          ->log(WebformMessageManagerInterface::PREPOPULATE_SOURCE_ENTITY_REQUIRED, 'notice');
        return $this
          ->getMessageManager()
          ->append($form, WebformMessageManagerInterface::PREPOPULATE_SOURCE_ENTITY_REQUIRED, 'warning');
      }
      $source_entity_type = $webform
        ->getSetting('form_prepopulate_source_entity_type');
      if ($source_entity_type && $this
        ->getSourceEntity() && $source_entity_type !== $this
        ->getSourceEntity()
        ->getEntityTypeId()) {
        $this
          ->getMessageManager()
          ->log(WebformMessageManagerInterface::PREPOPULATE_SOURCE_ENTITY_TYPE, 'notice');
        return $this
          ->getMessageManager()
          ->append($form, WebformMessageManagerInterface::PREPOPULATE_SOURCE_ENTITY_TYPE, 'warning');
      }
    }

    // Display inline confirmation message with back to link.
    if ($form_state
      ->get('current_page') === WebformInterface::PAGE_CONFIRMATION) {
      $form['confirmation'] = [
        '#theme' => 'webform_confirmation',
        '#webform' => $webform,
        '#source_entity' => $webform_submission
          ->getSourceEntity(TRUE),
        '#webform_submission' => $webform_submission,
      ];

      // Add hidden back (aka reset) button used by the Ajaxified back to link.
      // NOTE: Below code could be used to add a 'Reset' button to any webform.
      // @see Drupal.behaviors.webformConfirmationBackAjax
      $form['actions'] = [
        '#type' => 'actions',
        '#attributes' => [
          'style' => 'display:none',
        ],
        // Do not process the actions element. This prevents the
        // .form-actions class from being added, which then makes the button
        // appear in dialogs.
        // @see \Drupal\Core\Render\Element\Actions::processActions
        // @see Drupal.behaviors.dialog.prepareDialogButtons
        '#process' => [],
      ];
      $form['actions']['reset'] = [
        '#type' => 'submit',
        '#value' => $this
          ->t('Reset'),
        // @see \Drupal\webform\WebformSubmissionForm::noValidate
        '#validate' => [
          '::noValidate',
        ],
        '#submit' => [
          '::reset',
        ],
        '#attributes' => [
          'style' => 'display:none',
          'class' => [
            'js-webform-confirmation-back-submit-ajax',
          ],
        ],
      ];
      return $form;
    }

    // Don't display webform if it is closed.
    if (($webform_submission
      ->isNew() || $webform_submission
      ->isDraft()) && $webform
      ->isClosed()) {

      // If the current user can update any submission just display the closed
      // message and still allow them to create new submissions.
      if ($webform
        ->isTemplate() && $webform
        ->access('duplicate') && !$webform
        ->isArchived()) {
        if (!$this
          ->isDialog()) {
          $this
            ->getMessageManager()
            ->display(WebformMessageManagerInterface::TEMPLATE_PREVIEW, 'warning');
        }
      }
      elseif ($webform
        ->access('submission_update_any')) {
        $form = $this
          ->getMessageManager()
          ->append($form, $webform
          ->isArchived() ? WebformMessageManagerInterface::ADMIN_ARCHIVED : WebformMessageManagerInterface::ADMIN_CLOSED, 'info');
      }
      else {
        if ($webform
          ->isOpening()) {
          return $this
            ->getMessageManager()
            ->append($form, WebformMessageManagerInterface::FORM_OPEN_MESSAGE);
        }
        else {
          return $this
            ->getMessageManager()
            ->append($form, WebformMessageManagerInterface::FORM_CLOSE_MESSAGE);
        }
      }
    }

    // Disable this webform if confidential and user is logged in.
    if ($this
      ->isConfidential() && $this
      ->currentUser()
      ->isAuthenticated() && $this->entity
      ->isNew() && $this->operation === 'add') {
      return $this
        ->getMessageManager()
        ->append($form, WebformMessageManagerInterface::FORM_CONFIDENTIAL_MESSAGE, 'warning');
    }

    // Disable this webform if submissions are not being saved to the database or
    // passed to a WebformHandler.
    if ($this
      ->getWebformSetting('results_disabled') && !$this
      ->getWebformSetting('results_disabled_ignore') && !$webform
      ->getHandlers(NULL, TRUE, WebformHandlerInterface::RESULTS_PROCESSED)
      ->count()) {
      $this
        ->getMessageManager()
        ->log(WebformMessageManagerInterface::FORM_SAVE_EXCEPTION, 'error');
      if ($this
        ->currentUser()
        ->hasPermission('administer webform')) {

        // Display error to admin but allow them to submit the broken webform.
        $form = $this
          ->getMessageManager()
          ->append($form, WebformMessageManagerInterface::FORM_SAVE_EXCEPTION, 'error');
        $form = $this
          ->getMessageManager()
          ->append($form, WebformMessageManagerInterface::ADMIN_CLOSED, 'info');
      }
      else {

        // Display exception message to users.
        return $this
          ->getMessageManager()
          ->append($form, WebformMessageManagerInterface::FORM_EXCEPTION_MESSAGE, 'warning');
      }
    }

    // Check total limit.
    if ($this
      ->checkTotalLimit() && empty($this
      ->getWebformSetting('limit_total_unique'))) {
      $form = $this
        ->getMessageManager()
        ->append($form, WebformMessageManagerInterface::LIMIT_TOTAL_MESSAGE, 'warning');
      if ($webform
        ->access('submission_update_any')) {
        $form = $this
          ->getMessageManager()
          ->append($form, WebformMessageManagerInterface::ADMIN_CLOSED, 'info');
        return FALSE;
      }
      else {
        return $form;
      }
    }

    // Check user limit.
    if ($this
      ->checkUserLimit() && empty($this
      ->getWebformSetting('limit_user_unique'))) {
      $form = $this
        ->getMessageManager()
        ->append($form, WebformMessageManagerInterface::LIMIT_USER_MESSAGE, 'warning');
      if ($webform
        ->access('submission_update_any')) {
        $form = $this
          ->getMessageManager()
          ->append($form, WebformMessageManagerInterface::ADMIN_CLOSED, 'info');
        return FALSE;
      }
      else {
        return $form;
      }
    }
    return FALSE;
  }

  /**
   * Display draft, previous submission, and autofill status messages for this webform submission.
   *
   * @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.
   */
  protected function displayMessages(array $form, FormStateInterface $form_state) {

    /* @var $webform_submission \Drupal\webform\WebformSubmissionInterface */
    $webform_submission = $this
      ->getEntity();
    $webform = $this
      ->getWebform();
    $source_entity = $this
      ->getSourceEntity();
    $account = $this
      ->currentUser();

    // Display test message, except on share page.
    if ($this
      ->isGet() && $this->operation === 'test' && !$this
      ->isSharePage()) {
      $this
        ->getMessageManager()
        ->display(WebformMessageManagerInterface::SUBMISSION_TEST, 'warning');

      // Display devel generate link for webform or source entity.
      if ($this->moduleHandler
        ->moduleExists('devel_generate') && $this
        ->currentUser()
        ->hasPermission('administer webform')) {
        $query = [
          'webform_id' => $webform
            ->id(),
        ];
        if ($source_entity) {
          $query += [
            'entity_type' => $source_entity
              ->getEntityTypeId(),
            'entity_id' => $source_entity
              ->id(),
          ];
        }
        $query['destination'] = $this->requestHandler
          ->getUrl($webform, $source_entity, 'webform.results_submissions')
          ->toString();
        $offcanvas = WebformDialogHelper::useOffCanvas();
        $build = [
          '#type' => 'link',
          '#title' => $this
            ->t('Generate %title submissions', [
            '%title' => $webform
              ->label(),
          ]),
          '#url' => Url::fromRoute('devel_generate.webform_submission', [], [
            'query' => $query,
          ]),
          '#attributes' => $offcanvas ? WebformDialogHelper::getOffCanvasDialogAttributes(400) : [
            'class' => [
              'button',
            ],
          ],
        ];
        if ($offcanvas) {
          WebformDialogHelper::attachLibraries($form);
        }
        $this
          ->messenger()
          ->addWarning($this->renderer
          ->renderPlain($build));
      }
    }

    // Display admin only message.
    if ($this
      ->isGet() && $this
      ->isRoute('webform.canonical') && $this
      ->getRouteMatch()
      ->getRawParameter('webform') === $webform
      ->id() && !$this
      ->getWebform()
      ->getSetting('page')) {
      $this
        ->getMessageManager()
        ->display(WebformMessageManagerInterface::ADMIN_PAGE, 'info');
    }

    // Display loaded or saved draft message.
    if ($webform_submission
      ->isDraft()) {
      if ($form_state
        ->get('draft_saved')) {
        $this
          ->getMessageManager()
          ->display(WebformMessageManagerInterface::SUBMISSION_DRAFT_SAVED_MESSAGE);
        $form_state
          ->set('draft_saved', FALSE);
      }
      elseif ($this
        ->isGet() && !$webform
        ->getSetting('draft_multiple') && !$webform
        ->isClosed()) {
        $this
          ->getMessageManager()
          ->display(WebformMessageManagerInterface::SUBMISSION_DRAFT_LOADED_MESSAGE);
      }
    }

    // Display link to multiple drafts message when user is adding a new
    // submission.
    if ($this
      ->isGet() && $this->operation === 'add' && $this
      ->getWebformSetting('draft') !== WebformInterface::DRAFT_NONE && $this
      ->getWebformSetting('draft_multiple', FALSE) && ($previous_draft_total = $this
      ->getStorage()
      ->getTotal($webform, $this->sourceEntity, $this
      ->currentUser(), [
      'in_draft' => TRUE,
    ]))) {
      if ($previous_draft_total > 1) {
        $this
          ->getMessageManager()
          ->display(WebformMessageManagerInterface::DRAFT_PENDING_MULTIPLE);
      }
      else {
        $draft_submission = $this
          ->getStorage()
          ->loadDraft($webform, $this->sourceEntity, $this
          ->currentUser());
        if (!$draft_submission || $webform_submission
          ->id() !== $draft_submission
          ->id()) {
          $this
            ->getMessageManager()
            ->display(WebformMessageManagerInterface::DRAFT_PENDING_SINGLE);
        }
      }
    }

    // Display link to previous submissions message when user is adding a new
    // submission.
    if ($this
      ->isGet() && $this->operation === 'add' && $this
      ->getWebformSetting('form_previous_submissions', FALSE) && ($webform
      ->access('submission_view_own') || $this
      ->currentUser()
      ->hasPermission('view own webform submission')) && ($previous_submission_total = $this
      ->getStorage()
      ->getTotal($webform, $this->sourceEntity, $this
      ->currentUser()))) {
      if ($previous_submission_total > 1) {
        $this
          ->getMessageManager()
          ->display(WebformMessageManagerInterface::PREVIOUS_SUBMISSIONS);
      }
      else {
        $last_submission = $this
          ->getStorage()
          ->getLastSubmission($webform, $source_entity, $account);
        if ($last_submission && $webform_submission
          ->id() !== $last_submission
          ->id()) {
          $this
            ->getMessageManager()
            ->display(WebformMessageManagerInterface::PREVIOUS_SUBMISSION);
        }
      }
    }

    // Display autofill message.
    if ($this
      ->isGet() && $this->operation === 'add' && $webform_submission
      ->isNew() && $webform
      ->getSetting('autofill') && $this
      ->getStorage()
      ->getLastSubmission($webform, $source_entity, $account, [
      'in_draft' => FALSE,
      'access_check' => FALSE,
    ])) {
      $this
        ->getMessageManager()
        ->display(WebformMessageManagerInterface::AUTOFILL_MESSAGE);
    }
  }

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

  // Webform libraries and behaviors.

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

  /**
   * Attach libraries to the form.
   *
   * @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.
   */
  protected function attachLibraries(array &$form, FormStateInterface $form_state) {

    // Default: Add CSS and JS.
    // @see https://www.drupal.org/node/2274843#inline
    $form['#attached']['library'][] = 'webform/webform.form';

    // Assets: Add custom shared and webform specific CSS and JS.
    // @see webform_library_info_build()
    // @see _webform_page_attachments()
    $webform = $this
      ->getWebform();
    $assets = $webform
      ->getAssets();
    foreach ($assets as $type => $value) {
      if ($value) {
        $form['#attached']['library'][] = 'webform/webform.' . $type . '.' . $webform
          ->id();
      }
    }
  }

  /**
   * Attach behaviors with libraries to the form.
   *
   * @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.
   */
  protected function attachBehaviors(array &$form, FormStateInterface $form_state) {

    // Form: Inline form errors.
    // Add #disable_inline_form_errors property to form.
    if ($this
      ->getWebformSetting('form_disable_inline_errors')) {
      $form['#disable_inline_form_errors'] = TRUE;
    }

    // Form: Novalidate
    // Add novalidate attribute to form if client side validation disabled.
    if ($this
      ->getWebformSetting('form_novalidate')) {
      $form['#attributes']['novalidate'] = 'novalidate';
    }

    // Form: Autocomplete.
    // Add autocomplete=off attribute to form if autocompletion is disabled.
    if ($this
      ->getWebformSetting('form_disable_autocomplete')) {
      $form['#attributes']['autocomplete'] = 'off';
    }

    // Form: Disable back button.
    if ($this
      ->getWebformSetting('form_disable_back')) {
      $form['#attached']['library'][] = 'webform/webform.form.disable_back';
    }

    // Form: Back button submit.
    // Move back when back button is pressed on multistep forms.
    if ($this
      ->getWebformSetting('form_submit_back') && !$this
      ->isAjax()) {
      $form['#attached']['library'][] = 'webform/webform.form.submit_back';
    }

    // Form; Unsaved.
    // Add unsaved message.
    if ($this
      ->getWebformSetting('form_unsaved')) {
      $form['#attributes']['class'][] = 'js-webform-unsaved';

      // Set 'data-webform-unsaved' attribute if unsaved wizard.
      $pages = $this
        ->getPages($form, $form_state);
      $current_page = $this
        ->getCurrentPage($form, $form_state);
      if ($current_page && $current_page !== $this
        ->getFirstPage($pages)) {
        $form['#attributes']['data-webform-unsaved'] = TRUE;
      }
      $form['#attached']['library'][] = 'webform/webform.form.unsaved';
    }

    // Form: Submit once.
    // Prevent duplicate submissions.
    if ($this
      ->getWebformSetting('form_submit_once')) {
      $form['#attributes']['class'][] = 'js-webform-submit-once';
      $form['#attached']['library'][] = 'webform/webform.form.submit_once';
    }

    // Form: Autosubmit.
    // Disable webform auto submit on enter for wizard webform pages only.
    if ($this
      ->hasPages()) {
      $form['#attributes']['class'][] = 'js-webform-disable-autosubmit';
    }

    // Element: Autofocus.
    // Add autofocus class to webform.
    if ($this->entity
      ->isNew() && $this
      ->getWebformSetting('form_autofocus')) {
      $form['#attributes']['class'][] = 'js-webform-autofocus';
      $form['#attached']['library'][] = 'webform/webform.form.auto_focus';
    }

    // Details: Save.
    // Attach details element save open/close library.
    // This ensures that the library will be loaded even if the webform is
    // used as a block or a node.
    if ($this
      ->config('webform.settings')
      ->get('ui.details_save')) {
      $form['#attached']['library'][] = 'webform/webform.element.details.save';
    }

    // Details Toggle:
    // Display collapse/expand all details link.
    if ($this
      ->getWebformSetting('form_details_toggle')) {
      $form['#attributes']['class'][] = 'js-webform-details-toggle';
      $form['#attributes']['class'][] = 'webform-details-toggle';
      $form['#attached']['library'][] = 'webform/webform.element.details.toggle';
    }
  }

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

  // Webform actions.

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

  /**
   * {@inheritdoc}
   */
  public function afterBuild(array $form, FormStateInterface $form_state) {

    // If webform has a custom #action remove Form API fields.
    // @see \Drupal\Core\Form\FormBuilder::prepareForm
    if (strpos($form['#action'], 'form_action_') === FALSE) {

      // Remove 'op' #name from all action buttons.
      foreach (Element::children($form['actions']) as $child_key) {
        unset($form['actions'][$child_key]['#name']);
      }
      unset($form['form_build_id'], $form['form_token'], $form['form_id']);
    }
    return $form;
  }

  /**
   * Returns the wizard page submit buttons for the current entity form.
   */
  protected function pagesElement(array $form, FormStateInterface $form_state) {
    $pages = $this
      ->getPages($form, $form_state);
    if (!$pages) {
      return NULL;
    }
    $current_page_name = $this
      ->getCurrentPage($form, $form_state);
    if (!$this
      ->getWebformSetting('wizard_progress_link') && !($this
      ->getWebformSetting('wizard_preview_link') && $current_page_name === WebformInterface::PAGE_PREVIEW)) {
      return NULL;
    }
    $page_indexes = array_flip(array_keys($pages));
    $current_index = $page_indexes[$current_page_name] - 1;

    // Build dedicated actions element for pages links.
    $element = [
      '#type' => 'actions',
      '#weight' => -20,
      '#attributes' => [
        'class' => [
          'webform-wizard-pages-links',
          'js-webform-wizard-pages-links',
        ],
      ],
      // Only process the container and prevent .form-actions from being added
      // which force submit buttons to be rendered in dialogs.
      // @see \Drupal\Core\Render\Element\Actions
      // @see Drupal.behaviors.dialog.prepareDialogButtons
      '#process' => [
        [
          '\\Drupal\\Core\\Render\\Element\\Actions',
          'processContainer',
        ],
      ],
    ];
    if ($this
      ->getWebformSetting('wizard_progress_link')) {
      $element['#attributes']['data-wizard-progress-link'] = 'true';
    }
    if ($this
      ->getWebformSetting('wizard_preview_link')) {
      $element['#attributes']['data-wizard-preview-link'] = 'true';
    }
    $index = 1;
    $total = count($pages);
    foreach ($pages as $page_name => $page) {

      // Always include submit button for each page but only allows access
      // to previous and visible pages.
      //
      // Developers who want to allow users to jump to any wizard page can
      // expose these buttons via a form alter hook. Beware that
      // skipped pages will not be validated.
      $access = $page['#access'] && $page_indexes[$page_name] <= $current_index ? TRUE : FALSE;
      $t_args = [
        '@label' => $page['#title'],
        '@start' => $index++,
        '@end' => $total,
      ];
      $element[$page_name] = [
        '#type' => 'submit',
        '#value' => $this
          ->t('Edit'),
        '#page' => $page_name,
        '#validate' => [
          '::noValidate',
        ],
        '#submit' => [
          '::gotoPage',
        ],
        '#name' => 'webform_wizard_page-' . $page_name,
        '#attributes' => [
          'data-webform-page' => $page_name,
          'formnovalidate' => 'formnovalidate',
          'class' => [
            'webform-wizard-pages-link',
            'js-webform-wizard-pages-link',
          ],
          'title' => $this
            ->t("Edit '@label' (@start of @end)", $t_args),
        ],
        '#access' => $access,
      ];
    }
    $element['#attached']['library'][] = 'webform/webform.wizard.pages';
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  protected function actionsElement(array $form, FormStateInterface $form_state) {

    // Custom webforms, which completely override the ContentEntityForm, should
    // not return the actions element (aka submit buttons).
    if (!empty($form['#custom_form'])) {
      return NULL;
    }
    $element = parent::actionsElement($form, $form_state);
    if (!empty($element)) {
      $element['#theme'] = 'webform_actions';
    }
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  protected function actions(array $form, FormStateInterface $form_state) {

    /* @var $webform_submission \Drupal\webform\WebformSubmissionInterface */
    $webform_submission = $this->entity;
    $element = parent::actions($form, $form_state);

    /* @var \Drupal\webform\WebformSubmissionInterface $webform_submission */
    $preview_mode = $this
      ->getWebformSetting('preview');

    // Mark the submit action as the primary action, when it appears.
    $element['submit']['#button_type'] = 'primary';
    $element['submit']['#attributes']['class'][] = 'webform-button--submit';
    $element['submit']['#weight'] = 10;

    // Customize the submit button's label for new submissions only.
    if ($webform_submission
      ->isNew() || $webform_submission
      ->isDraft()) {
      $element['submit']['#value'] = $this
        ->config('webform.settings')
        ->get('settings.default_submit_button_label');
    }

    // Add validate and complete handler to submit.
    $element['submit']['#validate'][] = '::validateForm';
    $element['submit']['#validate'][] = '::autosave';
    $element['submit']['#validate'][] = '::complete';

    // Add confirm(ation) handler to submit button.
    $element['submit']['#submit'][] = '::confirmForm';

    // Hide the delete button and move it last.
    if (isset($element['delete'])) {
      $element['delete']['#access'] = FALSE;
      $element['delete']['#title'] = $this
        ->config('webform.settings')
        ->get('settings.default_delete_button_label');

      // Redirect to the 'add' submission form when this submission is deleted.
      if ($this->operation === 'add') {
        $element['delete']['#url']
          ->mergeOptions([
          'query' => $this
            ->getRedirectDestination()
            ->getAsArray(),
        ]);
      }
      $element['delete']['#weight'] = 20;
    }
    $pages = $this
      ->getPages($form, $form_state);
    $current_page = $this
      ->getCurrentPage($form, $form_state);
    if ($pages) {

      // Get current page element which can contain custom prev(ious) and next button
      // labels.
      $current_page_element = $this
        ->getWebform()
        ->getPage($this->operation, $current_page);
      $previous_page = $this
        ->getPreviousPage($pages, $current_page);
      $next_page = $this
        ->getNextPage($pages, $current_page);

      // Track previous and next page.
      $track = $this
        ->getWebform()
        ->getSetting('wizard_track');
      switch ($track) {
        case 'index':
          $track_pages = array_flip(array_keys($pages));
          $track_previous_page = $previous_page ? $track_pages[$previous_page] + 1 : NULL;
          $track_next_page = $next_page ? $track_pages[$next_page] + 1 : NULL;
          $track_last_page = $this
            ->getWebform()
            ->getSetting('wizard_confirmation') ? count($track_pages) : count($track_pages) + 1;
          break;
        default:
        case 'name':
          $track_previous_page = $previous_page;
          $track_next_page = $next_page;
          $track_last_page = WebformInterface::PAGE_CONFIRMATION;
          break;
      }
      $is_first_page = $current_page === $this
        ->getFirstPage($pages) ? TRUE : FALSE;
      $is_last_page = in_array($current_page, [
        WebformInterface::PAGE_PREVIEW,
        WebformInterface::PAGE_CONFIRMATION,
        $this
          ->getLastPage($pages),
      ]) ? TRUE : FALSE;
      $is_preview_page = $current_page === WebformInterface::PAGE_PREVIEW;
      $is_next_page_preview = $next_page === WebformInterface::PAGE_PREVIEW ? TRUE : FALSE;
      $is_next_page_complete = $next_page === WebformInterface::PAGE_CONFIRMATION ? TRUE : FALSE;
      $is_next_page_optional_preview = $is_next_page_preview && $preview_mode !== DRUPAL_REQUIRED;

      // Only show that save button if this is the last page of the wizard or
      // on preview page or right before the optional preview.
      $element['submit']['#access'] = $is_last_page || $is_preview_page || $is_next_page_optional_preview || $is_next_page_complete;

      // Use next page submit callback to make sure conditional page logic
      // is executed.
      $element['submit']['#submit'] = [
        '::submit',
      ];
      if ($track) {
        $element['submit']['#attributes']['data-webform-wizard-page'] = $track_last_page;
      }
      if (!$is_first_page) {
        if ($is_preview_page) {
          $element['preview_prev'] = [
            '#type' => 'submit',
            '#value' => $this
              ->config('webform.settings')
              ->get('settings.default_preview_prev_button_label'),
            // @see \Drupal\webform\WebformSubmissionForm::noValidate
            '#validate' => [
              '::noValidate',
            ],
            '#submit' => [
              '::previous',
            ],
            '#attributes' => [
              'formnovalidate' => 'formnovalidate',
              'class' => [
                'webform-button--previous',
              ],
            ],
            '#weight' => 0,
          ];
          if ($track) {
            $element['preview_prev']['#attributes']['data-webform-wizard-page'] = $track_previous_page;
          }
        }
        else {
          if (isset($current_page_element['#prev_button_label'])) {
            $previous_button_label = $current_page_element['#prev_button_label'];
            $previous_button_custom = TRUE;
          }
          else {
            $previous_button_label = $this
              ->config('webform.settings')
              ->get('settings.default_wizard_prev_button_label');
            $previous_button_custom = FALSE;
          }
          $element['wizard_prev'] = [
            '#type' => 'submit',
            '#value' => $previous_button_label,
            '#webform_actions_button_custom' => $previous_button_custom,
            // @see \Drupal\webform\WebformSubmissionForm::noValidate
            '#validate' => [
              '::noValidate',
            ],
            '#submit' => [
              '::previous',
            ],
            '#attributes' => [
              'formnovalidate' => 'formnovalidate',
              'class' => [
                'webform-button--previous',
              ],
            ],
            '#weight' => 0,
          ];
          if ($track) {
            $element['wizard_prev']['#attributes']['data-webform-wizard-page'] = $track_previous_page;
          }
        }
      }
      if (!$is_last_page && !$is_next_page_complete) {
        if ($is_next_page_preview) {
          $element['preview_next'] = [
            '#type' => 'submit',
            '#value' => $this
              ->config('webform.settings')
              ->get('settings.default_preview_next_button_label'),
            '#validate' => [
              '::validateForm',
            ],
            '#submit' => [
              '::next',
            ],
            '#attributes' => [
              'class' => [
                'webform-button--preview',
              ],
            ],
            '#weight' => 1,
          ];
          if ($track) {
            $element['preview_next']['#attributes']['data-webform-wizard-page'] = $track_next_page;
          }
        }
        else {
          if (isset($current_page_element['#next_button_label'])) {
            $next_button_label = $current_page_element['#next_button_label'];
            $next_button_custom = TRUE;
          }
          else {
            $next_button_label = $this
              ->config('webform.settings')
              ->get('settings.default_wizard_next_button_label');
            $next_button_custom = FALSE;
          }
          $element['wizard_next'] = [
            '#type' => 'submit',
            '#value' => $next_button_label,
            '#webform_actions_button_custom' => $next_button_custom,
            '#validate' => [
              '::validateForm',
            ],
            '#submit' => [
              '::next',
            ],
            '#attributes' => [
              'class' => [
                'webform-button--next',
              ],
            ],
            '#weight' => 1,
          ];
          if ($track) {
            $element['wizard_next']['#attributes']['data-webform-wizard-page'] = $track_next_page;
          }
        }
      }
      if ($track) {
        $element['#attached']['library'][] = 'webform/webform.wizard.track';
      }
    }

    // Draft.
    if ($this
      ->draftEnabled()) {
      $element['draft'] = [
        '#type' => 'submit',
        '#value' => $this
          ->config('webform.settings')
          ->get('settings.default_draft_button_label'),
        '#validate' => [
          '::draft',
        ],
        '#submit' => [
          '::submitForm',
          '::save',
          '::rebuild',
        ],
        '#attributes' => [
          'formnovalidate' => 'formnovalidate',
          'class' => [
            'webform-button--draft',
          ],
        ],
        '#weight' => -10,
      ];
    }

    // Reset.
    if ($this
      ->resetEnabled()) {
      $element['reset'] = [
        '#type' => 'submit',
        '#value' => $this
          ->config('webform.settings')
          ->get('settings.default_reset_button_label'),
        '#validate' => [
          '::noValidate',
        ],
        '#submit' => [
          '::reset',
        ],
        '#attributes' => [
          'formnovalidate' => 'formnovalidate',
          'class' => [
            'webform-button--reset',
          ],
        ],
        '#weight' => 10,
      ];
    }
    uasort($element, [
      'Drupal\\Component\\Utility\\SortArray',
      'sortByWeightProperty',
    ]);
    return $element;
  }

  /**
   * Webform submission handler for the 'goto' action.
   *
   * @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 function gotoPage(array &$form, FormStateInterface $form_state) {
    $element = $form_state
      ->getTriggeringElement();
    $form_state
      ->set('current_page', $element['#page']);
    $this
      ->wizardSubmit($form, $form_state);
  }

  /**
   * Webform submission handler for the 'submit' action.
   *
   * @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 function submit(array &$form, FormStateInterface $form_state) {
    $this
      ->next($form, $form_state, TRUE);
  }

  /**
   * Webform submission handler for the 'next' action.
   *
   * @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 function next(array &$form, FormStateInterface $form_state, $skip_preview = FALSE) {
    if ($form_state
      ->getErrors()) {
      return;
    }
    $pages = $this
      ->getPages($form, $form_state);

    // Get next page.
    $current_page = $this
      ->getCurrentPage($form, $form_state);
    $next_page = $this
      ->getNextPage($pages, $current_page);

    // If there is no next page jump to the confirmation page which will also
    // submit this form.
    // @see \Drupal\webform\WebformSubmissionForm::wizardSubmit
    if (empty($next_page)) {
      $next_page = WebformInterface::PAGE_CONFIRMATION;
    }

    // Skip preview page and move to the confirmation page.
    // @see
    if ($skip_preview && $next_page === WebformInterface::PAGE_PREVIEW) {
      $next_page = WebformInterface::PAGE_CONFIRMATION;
    }

    // Set next page.
    $form_state
      ->set('current_page', $next_page);

    // Submit next page.
    $this
      ->wizardSubmit($form, $form_state);
  }

  /**
   * Webform submission handler for the 'previous' action.
   *
   * @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 function previous(array &$form, FormStateInterface $form_state) {
    $pages = $this
      ->getPages($form, $form_state);

    // Get previous page.
    $current_page = $this
      ->getCurrentPage($form, $form_state);
    $previous_page = $this
      ->getPreviousPage($pages, $current_page);

    // Set previous page.
    $form_state
      ->set('current_page', $previous_page);

    // Submit previous page.
    $this
      ->wizardSubmit($form, $form_state);
  }

  /**
   * Webform submission handler for the wizard submit action.
   *
   * @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.
   */
  protected function wizardSubmit(array &$form, FormStateInterface $form_state) {
    $current_page = $form_state
      ->get('current_page');
    if ($current_page === WebformInterface::PAGE_CONFIRMATION) {
      $this
        ->complete($form, $form_state);
      $this
        ->submitForm($form, $form_state);
      $this
        ->save($form, $form_state);
      $this
        ->confirmForm($form, $form_state);
    }
    elseif ($this
      ->draftEnabled() && $this
      ->getWebformSetting('draft_auto_save') && !$this->entity
      ->isCompleted()) {
      $form_state
        ->set('in_draft', TRUE);
      $this
        ->submitForm($form, $form_state);
      $this
        ->save($form, $form_state);
      $this
        ->rebuild($form, $form_state);
    }
    else {
      $this
        ->submitForm($form, $form_state);
      $this
        ->rebuild($form, $form_state);
    }

    // Announce current page with progress.
    // @see template_preprocess_webform_progress()
    if ($this
      ->isAjax()) {
      $pages = $this
        ->getPages($form, $form_state);

      // Make sure the current page exists because the confirmation page
      // may not be included in the wizard's pages.
      if (isset($pages[$current_page])) {
        $page_keys = array_keys($pages);
        $page_indexes = array_flip($page_keys);
        $total_pages = count($page_keys);
        $current_index = $page_indexes[$current_page];
        $t_args = [
          '@title' => $this
            ->getWebform()
            ->label(),
          '@page' => $pages[$current_page]['#title'],
          '@start' => $current_index + 1,
          '@end' => $total_pages,
        ];
        $this
          ->announce($this
          ->t('"@title: @page" loaded. (@start of @end)', $t_args));
      }
    }
  }

  /**
   * Webform submission handler to autosave when there are validation errors.
   *
   * @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 function autosave(array &$form, FormStateInterface $form_state) {
    if ($form_state
      ->hasAnyErrors()) {
      if ($this
        ->draftEnabled() && $this
        ->getWebformSetting('draft_auto_save') && !$this->entity
        ->isCompleted()) {
        $form_state
          ->set('in_draft', TRUE);
        $was_new = $this->entity
          ->isNew();
        $this
          ->submitForm($form, $form_state);
        $this
          ->save($form, $form_state);
        $this
          ->rebuild($form, $form_state);
        if ($was_new && $form_state
          ->hasAnyErrors()) {

          // Prevent the previously-cached form object, which is stored in
          // $form['build_info']['callback_object'] from being used because it
          // refers to the original new (unsaved) entity.
          $this->formBuilder
            ->deleteCache($form['#build_id']);
        }
      }
    }
  }

  /**
   * Webform submission handler for the 'draft' action.
   *
   * @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 function draft(array &$form, FormStateInterface $form_state) {
    $form_state
      ->clearErrors();
    $form_state
      ->set('in_draft', TRUE);
    $form_state
      ->set('draft_saved', TRUE);
    $this->entity
      ->validate();
  }

  /**
   * Webform submission handler for the 'complete' action.
   *
   * @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 function complete(array &$form, FormStateInterface $form_state) {
    $form_state
      ->set('in_draft', FALSE);
  }

  /**
   * Webform submission validation that does nothing but clear validation errors.
   *
   * This method is used by wizard/preview previous buttons and the reset button
   * to prevent all form validation errors from being displayed while still
   * allowing an element's #validate callback to be triggered.
   *
   * This callback is being used instead of adding
   * #limit_validation_errors = [] to the submit buttons because
   * #limit_validation_errors also ignores all form values set via an element's
   * #validate callback.
   *
   * More complex (web)form elements user #validate callbacks
   * to process and alter an element's submitted value. Element's that rely on
   * #validate to alter the submitted value include 'Password Confirm',
   * 'Email Confirm', 'Composite Elements', 'Other Elements', and more…
   *
   * If the #limit_validation_errors property is used within a multi-step wizard
   * form, previously submitted values will be corrupted.
   *
   * @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.
   *
   * @see \Drupal\Core\Form\FormValidator::handleErrorsWithLimitedValidation
   * @see \Drupal\Core\Render\Element\PasswordConfirm::validatePasswordConfirm
   * @see \Drupal\webform\Element\WebformEmailConfirm
   * @see \Drupal\webform\Element\WebformOtherBase::validateWebformOther
   */
  public function noValidate(array &$form, FormStateInterface $form_state) {
    $form_state
      ->clearErrors();
    $this->entity
      ->validate();
  }

  /**
   * Webform submission handler for the 'rebuild' action.
   *
   * @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 function rebuild(array &$form, FormStateInterface $form_state) {
    $form_state
      ->setRebuild();
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    parent::validateForm($form, $form_state);

    // Disable inline form error when performing validation via the API.
    if ($this->operation === 'api') {

      // @see \Drupal\webform\WebformSubmissionForm::submitWebformSubmission
      $form['#disable_inline_form_errors'] = TRUE;
    }

    // Build webform submission with validated and processed data.
    $this->entity = $this
      ->buildEntity($form, $form_state);

    // Server side #states API validation.
    $this->conditionsValidator
      ->validateForm($form, $form_state);

    // Validate webform via webform handler.
    $this
      ->getWebform()
      ->invokeHandlers('validateForm', $form, $form_state, $this->entity);

    // Webform validate handlers (via form['#validate']) are not called when
    // #validate handlers are attached to the trigger element
    // (i.e. submit button), so we need to manually call $form['validate']
    // handlers to support the modules that use form['#validate'] like the
    // validators.module.
    // @see \Drupal\webform\WebformSubmissionForm::actions
    // @see \Drupal\Core\Form\FormBuilder::doBuildForm
    $trigger_element = $form_state
      ->getTriggeringElement();
    if (isset($trigger_element['#validate'])) {
      $handlers = array_filter($form['#validate'], function ($callback) {

        // Remove ::validateForm to prevent a recursion.
        return is_array($callback) || $callback !== '::validateForm';
      });

      // @see \Drupal\Core\Form\FormValidator::executeValidateHandlers
      foreach ($handlers as $callback) {
        $arguments = [
          &$form,
          &$form_state,
        ];
        call_user_func_array($form_state
          ->prepareCallback($callback), $arguments);
      }
    }

    // Validate file (upload) limit.
    $this
      ->validateUploadedManagedFiles($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    parent::submitForm($form, $form_state);

    // Server side #states API submit.
    $this->conditionsValidator
      ->submitForm($form, $form_state);

    // Submit webform via webform handler.
    $this
      ->getWebform()
      ->invokeHandlers('submitForm', $form, $form_state, $this->entity);
  }

  /**
   * Webform confirm(ation) handler.
   *
   * @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 function confirmForm(array &$form, FormStateInterface $form_state) {
    $this
      ->setConfirmation($form_state);

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

    // Confirm webform via webform handler.
    $this
      ->getWebform()
      ->invokeHandlers('confirmForm', $form, $form_state, $webform_submission);

    // Get confirmation type and submission state.
    $confirmation_type = $this
      ->getWebformSetting('confirmation_type');
    $state = $webform_submission
      ->getState();

    // Rebuild or reset the form if reloading the current form via AJAX.
    if ($this
      ->isAjax()) {

      // On update, rebuild and display message unless ?destination= is set.
      // @see \Drupal\webform\WebformSubmissionForm::setConfirmation
      if ($state === WebformSubmissionInterface::STATE_UPDATED) {
        if (!$this
          ->getRequest()
          ->get('destination')) {
          $this
            ->rebuild($form, $form_state);
        }
      }
      elseif ($confirmation_type === WebformInterface::CONFIRMATION_MESSAGE || $confirmation_type === WebformInterface::CONFIRMATION_NONE) {
        $this
          ->reset($form, $form_state);
      }
    }

    // Always rebuild or  reset the form to trigger a modal dialog.
    if ($confirmation_type === WebformInterface::CONFIRMATION_MODAL) {
      if ($state === WebformSubmissionInterface::STATE_UPDATED) {
        $this
          ->rebuild($form, $form_state);
      }
      else {
        $this
          ->reset($form, $form_state);
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function save(array $form, FormStateInterface $form_state) {
    $webform = $this
      ->getWebform();

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

    // Apply variants.
    $webform
      ->applyVariants($webform_submission);

    // Make sure the uri and remote addr are set correctly because
    // Ajax requests can cause these values to be reset.
    if ($webform_submission
      ->isNew()) {
      if (preg_match('/\\.webform\\.test_form$/', $this
        ->getRouteMatch()
        ->getRouteName())) {

        // For test submissions use the source URL.
        $source_url = $webform_submission
          ->set('uri', NULL)
          ->getSourceUrl()
          ->setAbsolute(FALSE);
        $uri = preg_replace('#^' . base_path() . '#', '/', $source_url
          ->toString());
      }
      else {

        // For all other submissions, use the request URI.
        $uri = preg_replace('#^' . base_path() . '#', '/', $this
          ->getRequest()
          ->getRequestUri());

        // Remove Ajax query string parameters.
        $uri = preg_replace('/(ajax_form=1|_wrapper_format=(drupal_ajax|drupal_modal|drupal_dialog|html|ajax))(&|$)/', '', $uri);

        // Remove empty query string.
        $uri = preg_replace('/\\?$/', '', $uri);
      }
      $webform_submission
        ->set('uri', $uri);
      $webform_submission
        ->set('remote_addr', $this
        ->getWebform()
        ->hasRemoteAddr() ? $this
        ->getRequest()
        ->getClientIp() : '');
      if ($this
        ->isConfidential()) {
        $webform_submission
          ->setOwnerId(0);
      }
    }

    // Block users from submitting templates that they can't update.
    if ($webform
      ->isTemplate() && !$webform
      ->access('update')) {
      return;
    }

    // Save and log webform submission.
    $webform_submission
      ->save();

    // Invalidate cache if any limits are specified.
    if ($this
      ->getWebformSetting('limit_total') || $this
      ->getWebformSetting('user_limit_total') || $this
      ->getWebformSetting('entity_limit_total') || $this
      ->getWebformSetting('entity_limit_user') || $this
      ->getWebformSetting('limit_total_unique') || $this
      ->getWebformSetting('limit_user_unique')) {
      Cache::invalidateTags([
        'webform:' . $this
          ->getWebform()
          ->id(),
      ]);
    }

    // Check limits rebuild.
    if ($this
      ->checkTotalLimit() || $this
      ->checkUserLimit()) {
      $form_state
        ->setRebuild();
    }
  }

  /**
   * Webform submission handler for the 'reset' action.
   *
   * @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 function reset(array &$form, FormStateInterface $form_state) {

    // Delete save draft.

    /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */
    $webform_submission = $this
      ->getEntity();
    if ($webform_submission
      ->isDraft()) {
      $webform_submission
        ->delete();
    }

    // Create new webform submission.

    /** @var \Drupal\webform\Entity\WebformSubmission $webform_submission */
    $webform_submission = $this
      ->getEntity()
      ->createDuplicate();
    $webform_submission
      ->setData($this->originalData);
    $this
      ->setEntity($webform_submission);

    // Reset user input but preserve form tokens.
    $form_state
      ->setUserInput(array_intersect_key($form_state
      ->getUserInput(), [
      'form_build_id' => 'form_build_id',
      'form_token' => 'form_token',
      'form_id' => 'form_id',
    ]));

    // Reset values.
    $form_state
      ->setValues([]);

    // Reset current page.
    $storage = $form_state
      ->getStorage();
    unset($storage['current_page']);
    $form_state
      ->setStorage($storage);

    // Rebuild the form.
    $this
      ->rebuild($form, $form_state);
  }

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

  // Validate functions.

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

  /**
   * Validate uploaded managed file limits.
   *
   * @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.
   *
   * @see \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::validateManagedFileLimit
   */
  protected function validateUploadedManagedFiles(array $form, FormStateInterface $form_state) {
    $file_limit = $this
      ->getWebform()
      ->getSetting('form_file_limit') ?: $this->configFactory
      ->get('webform.settings')
      ->get('settings.default_form_file_limit') ?: '';
    $file_limit = Bytes::toInt($file_limit);
    if (!$file_limit) {
      return;
    }

    // Validate file upload limit.
    $fids = $this
      ->getUploadedManagedFileIds();
    if (!$fids) {
      return;
    }
    $file_names = [];
    $total_file_size = 0;

    /** @var \Drupal\file\FileInterface[] $files */
    $files = $this->entityTypeManager
      ->getStorage('file')
      ->loadMultiple($fids);
    foreach ($files as $file) {
      $total_file_size += (int) $file
        ->getSize();
      $file_names[] = $file
        ->getFilename() . ' - ' . format_size($file
        ->getSize(), $this->entity
        ->language()
        ->getId());
    }
    if ($total_file_size > $file_limit) {
      $t_args = [
        '%quota' => format_size($file_limit),
      ];
      $message = [];
      $message['content'] = [
        '#markup' => $this
          ->t("This form's file upload quota of %quota has been exceeded. Please remove some files.", $t_args),
      ];
      $message['files'] = [
        '#theme' => 'item_list',
        '#items' => $file_names,
      ];
      $form_state
        ->setErrorByName(NULL, $this->renderer
        ->renderPlain($message));
    }
  }

  /**
   * Get uploaded managed file ids.
   *
   * @return array
   *   An array of uploaded file ids.
   */
  protected function getUploadedManagedFileIds() {
    $fids = [];
    $element_keys = $this
      ->getWebform()
      ->getElementsManagedFiles();
    foreach ($element_keys as $element_key) {
      $data = $this->entity
        ->getElementData($element_key);
      if (!$data) {
        continue;
      }
      $element = $this
        ->getWebform()
        ->getElement($element_key);
      $element_plugin = $this->elementManager
        ->getElementInstance($element);
      $multiple = $element_plugin
        ->hasMultipleValues($element);

      // Get fids from composite sub-elements.
      if ($element_plugin instanceof WebformCompositeBase) {
        $managed_file_keys = $element_plugin
          ->getManagedFiles($element);

        // Convert single composite value to array of multiple composite values.
        $data = !$multiple ? [
          $data,
        ] : $data;
        foreach ($data as $item) {
          foreach ($managed_file_keys as $manage_file_key) {
            if ($item[$manage_file_key]) {
              $fids[] = $item[$manage_file_key];
            }
          }
        }
      }
      else {
        $fids = array_merge($fids, (array) $data);
      }
    }
    return $fids;
  }

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

  // Webform functions

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

  /**
   * Set the webform properties from the elements.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param array $elements
   *   An associative array containing the elements.
   */
  protected function setFormPropertiesFromElements(array &$form, array &$elements) {
    foreach ($elements as $key => $value) {
      if (is_string($key) && $key[0] === '#') {
        $value = $this->tokenManager
          ->replace($value, $this
          ->getEntity(), [], [], $this->bubbleableMetadata);
        if (isset($form[$key]) && is_array($form[$key]) && is_array($value)) {
          $form[$key] = NestedArray::mergeDeep($form[$key], $value);
        }
        else {
          $form[$key] = $value;
        }

        // Remove the properties from the $elements and $form['elements'] array.
        unset($elements[$key], $form['elements'][$key]);
      }
    }

    // Replace token in #attributes.
    if (isset($form['#attributes'])) {
      $form['#attributes'] = $this->tokenManager
        ->replace($form['#attributes'], $this
        ->getEntity(), [], [], $this->bubbleableMetadata);
    }
  }

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

  // Wizard page functions

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

  /**
   * Determine if this is a multi-step wizard form.
   *
   * @return bool
   *   TRUE if this multi-step wizard form.
   */
  protected function hasPages() {
    return $this
      ->getWebform()
      ->getPages($this->operation);
  }

  /**
   * Get visible wizard pages.
   *
   * Note: The array of pages is stored in the webform's state so that it can be
   * altered using hook_form_alter() and #validate callbacks.
   *
   * @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
   *   Array of visible wizard pages.
   */
  protected function getPages(array &$form, FormStateInterface $form_state) {
    if ($form_state
      ->get('pages') === NULL) {
      $pages = $this
        ->getWebform()
        ->getPages($this->operation);
      $form_state
        ->set('pages', $pages);
    }

    // Get pages from form state.
    $pages = $form_state
      ->get('pages');

    /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */
    $webform_submission = $this
      ->getEntity();
    return $this->conditionsValidator
      ->buildPages($pages, $webform_submission);
  }

  /**
   * Get the current page's key.
   *
   * @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 string
   *   The current page's key.
   */
  protected function getCurrentPage(array &$form, FormStateInterface $form_state) {
    if ($form_state
      ->get('current_page') === NULL) {
      $pages = $this
        ->getWebform()
        ->getPages($this->operation);
      if (empty($pages)) {
        $form_state
          ->set('current_page', '');
      }
      else {
        $current_page = $this->entity
          ->getCurrentPage();
        if ($current_page && isset($pages[$current_page]) && !$this->entity
          ->isCompleted()) {
          $form_state
            ->set('current_page', $current_page);
        }
        else {
          $form_state
            ->set('current_page', WebformArrayHelper::getFirstKey($pages));
        }
      }
    }
    return $form_state
      ->get('current_page');
  }

  /**
   * Get first page's key.
   *
   * @param array $pages
   *   An associative array of visible wizard pages.
   *
   * @return null|string
   *   The first page's key.
   */
  protected function getFirstPage(array $pages) {
    return WebformArrayHelper::getFirstKey($pages);
  }

  /**
   * Get last page's key.
   *
   * @param array $pages
   *   An associative array of visible wizard pages.
   *
   * @return null|string
   *   The last page's key.
   */
  protected function getLastPage(array $pages) {
    return WebformArrayHelper::getLastKey($pages);
  }

  /**
   * Get next page's key.
   *
   * @param array $pages
   *   An associative array of visible wizard pages.
   * @param string $current_page
   *   The current page.
   *
   * @return null|string
   *   The next page's key. NULL if there is no next page.
   */
  protected function getNextPage(array $pages, $current_page) {
    return WebformArrayHelper::getNextKey($pages, $current_page);
  }

  /**
   * Get previous page's key.
   *
   * @param array $pages
   *   An associative array of visible wizard pages.
   * @param string $current_page
   *   The current page.
   *
   * @return null|string
   *   The previous page's key. NULL if there is no previous page.
   */
  protected function getPreviousPage(array $pages, $current_page) {
    return WebformArrayHelper::getPreviousKey($pages, $current_page);
  }

  /**
   * Set webform wizard current page.
   *
   * @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.
   */
  protected function displayCurrentPage(array &$form, FormStateInterface $form_state) {
    $current_page = $this
      ->getCurrentPage($form, $form_state);
    if ($current_page === WebformInterface::PAGE_PREVIEW) {

      // Hide all elements except 'webform_actions'.
      foreach ($form['elements'] as $element_key => $element) {
        if (isset($element['#type']) && $element['#type'] === 'webform_actions') {
          continue;
        }

        // Set #access to FALSE which will suppresses webform #required validation.
        if (Element::child($element_key) && is_array($form['elements'])) {
          WebformElementHelper::setPropertyRecursive($form['elements'][$element_key], '#access', FALSE);
        }
      }

      // Display preview message.
      $this
        ->getMessageManager()
        ->display(WebformMessageManagerInterface::FORM_PREVIEW_MESSAGE, 'warning');

      // Build preview.
      $preview_attributes = new Attribute($this
        ->getWebform()
        ->getSetting('preview_attributes'));
      $preview_attributes
        ->addClass('webform-preview');
      $form['#title'] = PlainTextOutput::renderFromHtml($this
        ->getWebformSetting('preview_title'));
      $form['preview'] = [
        '#type' => 'container',
        '#attributes' => $preview_attributes,
        // Progress bar is -20.
        '#weight' => -10,
        'submission' => $this->entityTypeManager
          ->getViewBuilder('webform_submission')
          ->view($this->entity, 'preview'),
      ];
    }
    else {

      // Get all pages so that we can also hide skipped pages.
      $pages = $this
        ->getWebform()
        ->getPages($this->operation);
      foreach ($pages as $page_key => $page) {
        if (isset($form['elements'][$page_key])) {
          $page_element =& $form['elements'][$page_key];
          $page_element_plugin = $this->elementManager
            ->getElementInstance($page_element);
          if ($page_element_plugin instanceof WebformElementWizardPageInterface) {
            if ($page_key != $current_page) {
              $page_element_plugin
                ->hidePage($page_element);
            }
            else {
              $page_element_plugin
                ->showPage($page_element);
            }
          }
        }
      }
    }
  }

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

  // Webform state functions

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

  /**
   * Set webform state to redirect to a trusted redirect response.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param \Drupal\Core\Url $url
   *   A URL object.
   */
  protected function setTrustedRedirectUrl(FormStateInterface $form_state, Url $url) {
    $form_state
      ->setResponse(new TrustedRedirectResponse($url
      ->setAbsolute()
      ->toString()));
  }

  /**
   * Set webform state confirmation redirect and message.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  protected function setConfirmation(FormStateInterface $form_state) {

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

    // Get current route name, parameters, and options.
    $route_name = $this
      ->getRouteMatch()
      ->getRouteName();
    $route_parameters = $this
      ->getRouteMatch()
      ->getRawParameters()
      ->all();
    $route_options = [];

    // Add current query to route options.
    if (!$webform
      ->getSetting('confirmation_exclude_query')) {
      $query = $this
        ->getRequest()->query
        ->all();

      // Remove Ajax parameters from query.
      unset($query['ajax_form'], $query['_wrapper_format']);
      if ($query) {
        $route_options['query'] = $query;
      }
    }

    // Default to displaying a confirmation message on this page when submission
    // is updated or locked (but not just completed).
    $state = $webform_submission
      ->getState();
    $is_updated = $state === WebformSubmissionInterface::STATE_UPDATED;
    $is_locked = $state === WebformSubmissionInterface::STATE_LOCKED && $webform_submission
      ->getChangedTime() > $webform_submission
      ->getCompletedTime();
    $confirmation_update = $this
      ->getWebformSetting('confirmation_update');
    if ($is_updated && !$confirmation_update || $is_locked) {
      $this
        ->getMessageManager()
        ->display(WebformMessageManagerInterface::SUBMISSION_UPDATED);
      $form_state
        ->set('current_page', NULL);
      $form_state
        ->setRedirect($route_name, $route_parameters, $route_options);
      return;
    }

    // Add token route query options.
    if ($state === WebformSubmissionInterface::STATE_COMPLETED && !$webform
      ->getSetting('confirmation_exclude_token')) {
      $route_options['query']['token'] = $webform_submission
        ->getToken();
    }

    // Handle 'page', 'url', and 'inline' confirmation types.
    $confirmation_type = $this
      ->getWebformSetting('confirmation_type');
    switch ($confirmation_type) {
      case WebformInterface::CONFIRMATION_PAGE:
        $redirect_url = $this->requestHandler
          ->getUrl($webform, $this->sourceEntity, 'webform.confirmation', $route_options);
        $form_state
          ->setRedirectUrl($redirect_url);
        return;
      case WebformInterface::CONFIRMATION_URL:
      case WebformInterface::CONFIRMATION_URL_MESSAGE:
        $redirect_url = $this
          ->getConfirmationUrl();
        if ($redirect_url) {
          if ($confirmation_type === WebformInterface::CONFIRMATION_URL_MESSAGE) {
            $this
              ->getMessageManager()
              ->display(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION_MESSAGE);
          }
          $this
            ->setTrustedRedirectUrl($form_state, $redirect_url);
        }
        else {
          $this
            ->getMessageManager()
            ->display(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION_MESSAGE);
          $route_options['query']['webform_id'] = $webform
            ->id();
          $form_state
            ->setRedirect($route_name, $route_parameters, $route_options);
        }
        return;
      case WebformInterface::CONFIRMATION_INLINE:
        $form_state
          ->set('current_page', WebformInterface::PAGE_CONFIRMATION);
        $form_state
          ->setRebuild();
        return;
      case WebformInterface::CONFIRMATION_MESSAGE:
        $this
          ->getMessageManager()
          ->display(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION_MESSAGE);
        return;
      case WebformInterface::CONFIRMATION_MODAL:
        $message = $this
          ->getMessageManager()
          ->build(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION_MESSAGE);
        if ($message) {

          // Set webform confirmation modal in $form_state.
          $form_state
            ->set('webform_confirmation_modal', [
            'title' => $this
              ->getWebformSetting('confirmation_title', ''),
            'content' => $message,
          ]);
        }
        return;
      case WebformInterface::CONFIRMATION_NONE:
        return;
      case WebformInterface::CONFIRMATION_DEFAULT:
      default:
        $this
          ->getMessageManager()
          ->display(WebformMessageManagerInterface::SUBMISSION_DEFAULT_CONFIRMATION);
        return;
    }
  }

  /**
   * Get the webform's confrmation URL.
   *
   * @return \Drupal\Core\Url|false
   *   The url object, or FALSE if the path is not valid.
   *
   * @see \Drupal\Core\Path\PathValidatorInterface::getUrlIfValid
   */
  protected function getConfirmationUrl() {
    $confirmation_url = trim($this
      ->getWebformSetting('confirmation_url', ''));
    if (strpos($confirmation_url, '/') === 0) {

      // Get redirect URL using an absolute URL for the absolute  path.
      $redirect_url = Url::fromUri($this
        ->getRequest()
        ->getSchemeAndHttpHost() . $confirmation_url);
    }
    elseif (preg_match('#^[a-z]+(?:://|:)#', $confirmation_url)) {

      // Get redirect URL from URI (i.e. http://, https:// or ftp://)
      // and Drupal custom URIs (i.e internal:).
      $redirect_url = Url::fromUri($confirmation_url);
    }
    elseif (strpos($confirmation_url, '<') === 0) {

      // Get redirect URL from special paths: '<front>' and '<none>'.
      $redirect_url = $this->pathValidator
        ->getUrlIfValid($confirmation_url);
    }
    else {

      // Get redirect URL by validating the Drupal relative path which does not
      // begin with a forward slash (/).
      $confirmation_url = $this->aliasManager
        ->getPathByAlias('/' . $confirmation_url);
      $redirect_url = $this->pathValidator
        ->getUrlIfValid($confirmation_url);
    }

    // If redirect url is FALSE, display and log a warning.
    if (!$redirect_url) {
      $webform = $this
        ->getWebform();
      $t_args = [
        '@webform' => $webform
          ->label(),
        '%url' => $this
          ->getWebformSetting('confirmation_url'),
      ];

      // Display warning to use who can update the webform.
      if ($webform
        ->access('update')) {
        $this
          ->messenger()
          ->addWarning($this
          ->t('Confirmation URL %url is not valid.', $t_args));
      }

      // Log warning.
      $this
        ->getLogger('webform')
        ->warning('@webform: Confirmation URL %url is not valid.', $t_args);
    }
    return $redirect_url;
  }

  /**
   * Hide confirmation modal during form validation.
   *
   * This prevent duplicate modal dialog from appearing.
   */
  public static function removeConfirmationModal(&$element, FormStateInterface $form_state, &$complete_form) {

    // Reset confirmation modal.
    $storage = $form_state
      ->getStorage();
    unset($storage['webform_confirmation_modal']);
    $form_state
      ->setStorage($storage);

    // Remove modal from form.
    unset($complete_form['webform_confirmation_modal']);
  }

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

  // Elements functions

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

  /**
   * Prepare webform elements.
   *
   * @param array $elements
   *   An render array representing elements.
   * @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.
   */
  protected function prepareElements(array &$elements, array &$form, FormStateInterface $form_state) {
    foreach ($elements as $key => &$element) {
      if (!WebformElementHelper::isElement($element, $key)) {
        continue;
      }

      // Build the webform element.
      $this->elementManager
        ->buildElement($element, $form, $form_state);
      if (isset($element['#states'])) {
        $element['#states'] = $this
          ->addStatesPrefix($element['#states']);
      }

      // Recurse and prepare nested elements.
      $this
        ->prepareElements($element, $form, $form_state);
    }
  }

  /**
   * Alter webform elements form.
   *
   * @param array $elements
   *   An render array representing elements.
   * @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.
   */
  protected function alterElementsForm(array &$elements, array &$form, FormStateInterface $form_state) {
    foreach ($elements as $key => &$element) {
      if (!WebformElementHelper::isElement($element, $key)) {
        continue;
      }
      $element_plugin = $this->elementManager
        ->getElementInstance($element);
      if ($element_plugin) {
        $element_plugin
          ->alterForm($element, $form, $form_state);
      }

      // Recurse and alter nested elements forms.
      $this
        ->alterElementsForm($element, $form, $form_state);
    }
  }

  /**
   * Add unique class prefix to all :input #states selectors.
   *
   * @param array $array
   *   An associative array.
   *
   * @return array
   *   An associative array with unique class prefix added to all :input
   *   #states selectors.
   */
  protected function addStatesPrefix(array $array) {
    $prefixed_array = [];
    foreach ($array as $key => $value) {
      if (strpos($key, ':input') === 0) {
        $key = $this->statesPrefix . ' ' . $key;
        $prefixed_array[$key] = $value;
      }
      elseif (is_array($value)) {
        $prefixed_array[$key] = $this
          ->addStatesPrefix($value);
      }
      else {
        $prefixed_array[$key] = $value;
      }
    }
    return $prefixed_array;
  }

  /**
   * Prepopulate element data.
   *
   * @param array $data
   *   An array of default.
   */
  protected function prepopulateData(array &$data) {

    // Get prepopulate data.
    if ($this
      ->getWebformSetting('form_prepopulate')) {
      $prepopulate_data = $this
        ->getRequest()->query
        ->all();
    }
    else {
      $prepopulate_data = [];
      $elements = $this
        ->getWebform()
        ->getElementsPrepopulate();
      foreach ($elements as $element_key) {
        if ($this
          ->getRequest()->query
          ->has($element_key)) {
          $prepopulate_data[$element_key] = $this
            ->getRequest()->query
            ->get($element_key);
        }
      }
    }

    // Validate prepopulate data.
    foreach ($prepopulate_data as $element_key => &$value) {
      if ($this
        ->checkPrepopulateDataValid($element_key, $value) === FALSE) {
        unset($prepopulate_data[$element_key]);
      }
    }

    // Set prepopulate data.
    $data = $prepopulate_data + $data;
  }

  /**
   * Determine if element prepopulate data is valid.
   *
   * @param string $element_key
   *   An element key.
   * @param string|array &$value
   *   A value.
   *
   * @return bool
   *   TRUE if element prepopulate data is valid.
   */
  protected function checkPrepopulateDataValid($element_key, &$value) {

    // Make sure the element exists.
    $element = $this
      ->getWebform()
      ->getElement($element_key);
    if (!$element) {
      return FALSE;
    }

    // Make sure the element is an input.
    $element_plugin = $this->elementManager
      ->getElementInstance($element);
    if (!$element_plugin
      ->isInput($element)) {
      return FALSE;
    }

    // Validate entity references.
    // @see \Drupal\Core\Entity\Element\EntityAutocomplete::validateEntityAutocomplete
    // @see \Drupal\webform\Plugin\WebformElement\WebformTermReferenceTrait
    if ($element_plugin instanceof WebformElementEntityReferenceInterface) {
      if (isset($element['#vocabulary'])) {
        $vocabulary_id = $element['#vocabulary'];
        $options = [
          'target_type' => 'taxonomy_term',
          'handler' => 'default:taxonomy_term',
          'target_bundles' => [
            $vocabulary_id => $vocabulary_id,
          ],
        ];
      }
      elseif (isset($element['#selection_settings'])) {
        $options = $element['#selection_settings'] + [
          'target_type' => $element['#target_type'],
          'handler' => $element['#selection_handler'],
        ];
      }
      else {
        return TRUE;
      }

      /** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface $handler */
      $handler = $this->selectionManager
        ->getInstance($options);
      $valid_ids = $handler
        ->validateReferenceableEntities((array) $value);
      if (empty($valid_ids)) {
        return FALSE;
      }
      else {
        $value = $element_plugin
          ->hasMultipleValues($element) ? $valid_ids : reset($valid_ids);
        return TRUE;
      }
    }

    // Validate options.
    $is_options_element = isset($element['#options']) && $element_plugin instanceof OptionsBase && !$element_plugin instanceof WebformElementOtherInterface;
    if ($is_options_element) {
      $option_values = WebformOptionsHelper::validateOptionValues($element['#options'], (array) $value);
      if (empty($option_values)) {
        return FALSE;
      }
      else {
        $value = $element_plugin
          ->hasMultipleValues($element) ? $option_values : reset($option_values);
        return TRUE;
      }
    }
    return TRUE;
  }

  /**
   * Populate webform elements.
   *
   * @param array $elements
   *   An render array representing elements.
   * @param array $values
   *   An array of values used to populate the elements.
   */
  protected function populateElements(array &$elements, array $values) {
    foreach ($elements as $key => &$element) {
      if (!WebformElementHelper::isElement($element, $key)) {
        continue;
      }

      // If value is not set, continue to populate sub-elements.
      if (!isset($values[$key])) {
        $this
          ->populateElements($element, $values);
        continue;
      }

      // Get the element's plugin.
      $element_plugin = $this->elementManager
        ->getElementInstance($element);

      // If not input, populate sub-elements and continue.
      if (!$element_plugin || !$element_plugin
        ->isInput($element)) {
        $this
          ->populateElements($element, $values);
        continue;
      }

      // If input does not support prepopulate, populate sub-elements and continue.
      if ($this
        ->getRequest()->query
        ->has($key) && !$element_plugin
        ->hasProperty('prepopulate')) {
        $this
          ->populateElements($element, $values);
        continue;
      }

      // Determine if this is a hidden element.
      // Hidden elements use #value but need to use #default_value to
      // be populated.
      $is_hidden = $element_plugin instanceof Hidden;

      // Populate default value or value.
      if ($element_plugin
        ->hasProperty('default_value') || $is_hidden) {
        $element['#default_value'] = $values[$key];
      }
      elseif ($element_plugin
        ->hasProperty('value')) {
        $element['#value'] = $values[$key];
      }

      // API values need to trigger validation.
      if ($this->operation === 'api') {
        $element['#needs_validation'] = TRUE;
      }

      // Populate sub-elements.
      $this
        ->populateElements($element, $values);
    }
  }

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

  // Cache related functions.

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

  /**
   * Add cache dependency to the form's render array.
   *
   * @param array &$form
   *   The form's render array to update.
   */
  protected function addCacheableDependency(array &$form) {

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

    // All anonymous submissions are tracked in the $_SESSION.
    // @see \Drupal\webform\WebformSubmissionStorage::setAnonymousSubmission

    /** @var \Drupal\webform\WebformSubmissionStorageInterface $submission_storage */
    $submission_storage = $this->entityTypeManager
      ->getStorage('webform_submission');
    if ($this
      ->currentUser()
      ->isAnonymous() && $submission_storage
      ->hasAnonymousSubmissionTracking($webform_submission)) {
      $form['#cache']['contexts'][] = 'session';
    }

    // Allow all form elements to be prepopulated via the URL.
    if ($this
      ->getWebformSetting('form_prepopulate')) {
      $form['#cache']['contexts'][] = 'url.query_args';
    }
    else {

      // Allow specific form elements to be prepopulated via the URL.
      $elements_prepopulate = $this
        ->getWebform()
        ->getElementsPrepopulate();
      if ($elements_prepopulate) {
        foreach ($elements_prepopulate as $element_key) {
          $form['#cache']['contexts'][] = 'url.query_args:' . $element_key;
        }
      }

      // Allow source entity type and id to be passed via the URL.
      if ($this
        ->getWebformSetting('form_prepopulate_source_entity')) {
        $form['#cache']['contexts'][] = 'url.query_args:source_entity_type';
        $form['#cache']['contexts'][] = 'url.query_args:source_entity_id';
      }

      // Allow variants to be passed.
      if ($this
        ->getWebform()
        ->hasVariants()) {
        $form['#cache']['contexts'][] = 'url.query_args:_webform_variant';
      }
    }
  }

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

  // Account related functions

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

  /**
   * Check webform submission total limits.
   *
   * @return bool
   *   TRUE if webform submission total limit have been met.
   */
  protected function checkTotalLimit() {
    $webform = $this
      ->getWebform();

    // Get limit total to unique submission per webform/source entity.
    $limit_total_unique = $this
      ->getWebformSetting('limit_total_unique');

    // Check per source entity total limit.
    $entity_limit_total = $this
      ->getWebformSetting('entity_limit_total');
    $entity_limit_total_interval = $this
      ->getWebformSetting('entity_limit_total_interval');
    if ($limit_total_unique) {
      $entity_limit_total = 1;
      $entity_limit_total_interval = NULL;
    }
    if ($entity_limit_total && ($source_entity = $this
      ->getLimitSourceEntity())) {
      if ($this
        ->getStorage()
        ->getTotal($webform, $source_entity, NULL, [
        'interval' => $entity_limit_total_interval,
      ]) >= $entity_limit_total) {
        return TRUE;
      }
    }

    // Check total limit.
    $limit_total = $this
      ->getWebformSetting('limit_total');
    $limit_total_interval = $this
      ->getWebformSetting('limit_total_interval');
    if ($limit_total_unique) {
      $limit_total = 1;
      $limit_total_interval = NULL;
    }
    if ($limit_total && $this
      ->getStorage()
      ->getTotal($webform, NULL, NULL, [
      'interval' => $limit_total_interval,
    ]) >= $limit_total) {
      return TRUE;
    }
    return FALSE;
  }

  /**
   * Check webform submission user limit.
   *
   * @return bool
   *   TRUE if webform submission user limit have been met.
   */
  protected function checkUserLimit() {

    // Allow anonymous and authenticated users edit own submission.

    /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */
    $webform_submission = $this
      ->getEntity();
    if ($webform_submission
      ->id() && $webform_submission
      ->isOwner($this
      ->currentUser())) {
      return FALSE;
    }

    // Get the submission owner and not current user.
    // This takes into account when an API submission changes the owner id.
    // @see \Drupal\webform\WebformSubmissionForm::submitFormValues
    $account = $this->entity
      ->getOwner();
    $webform = $this
      ->getWebform();

    // Check per source entity user limit.
    $entity_limit_user = $this
      ->getWebformSetting('entity_limit_user');
    $entity_limit_user_interval = $this
      ->getWebformSetting('entity_limit_user_interval');
    if ($entity_limit_user && ($source_entity = $this
      ->getLimitSourceEntity())) {
      if ($this
        ->getStorage()
        ->getTotal($webform, $source_entity, $account, [
        'interval' => $entity_limit_user_interval,
      ]) >= $entity_limit_user) {
        return TRUE;
      }
    }

    // Check user limit.
    $limit_user = $this
      ->getWebformSetting('limit_user');
    $limit_user_interval = $this
      ->getWebformSetting('limit_user_interval');
    if ($limit_user && $this
      ->getStorage()
      ->getTotal($webform, NULL, $account, [
      'interval' => $limit_user_interval,
    ]) >= $limit_user) {
      return TRUE;
    }
    return FALSE;
  }

  /**
   * Determine if drafts are enabled.
   *
   * @return bool
   *   TRUE if drafts are enabled.
   */
  protected function draftEnabled() {

    // Can't saved drafts when saving results is disabled.
    if ($this
      ->getWebformSetting('results_disabled')) {
      return FALSE;
    }

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

    // Once a form is completed drafts are no longer applicable.
    if ($webform_submission
      ->isCompleted()) {
      return FALSE;
    }
    switch ($this
      ->getWebformSetting('draft')) {
      case WebformInterface::DRAFT_ALL:
        return TRUE;
      case WebformInterface::DRAFT_AUTHENTICATED:
        return $webform_submission
          ->getOwner()
          ->isAuthenticated();
      case WebformInterface::DRAFT_NONE:
      default:
        return FALSE;
    }
  }

  /**
   * Determine if reset is enabled.
   *
   * @return bool
   *   TRUE if reset is enabled.
   */
  protected function resetEnabled() {
    return $this
      ->getWebformSetting('form_reset', FALSE);
  }

  /**
   * Returns the webform confidential indicator.
   *
   * @return bool
   *   TRUE if the webform is confidential.
   */
  protected function isConfidential() {
    return $this
      ->getWebformSetting('form_confidential', FALSE);
  }

  /**
   * Is client side validation disabled (using the webform novalidate attribute).
   *
   * @return bool
   *   TRUE if the client side validation disabled.
   */
  protected function isFormNoValidate() {
    return $this
      ->getWebformSetting('form_novalidate', FALSE);
  }

  /**
   * Is the webform being initially loaded via GET method.
   *
   * @return bool
   *   TRUE if the webform is being initially loaded via GET method.
   */
  protected function isGet() {
    return $this
      ->getRequest()
      ->getMethod() === 'GET' ? TRUE : FALSE;
  }

  /**
   * Determine if the current request is a specific route (name).
   *
   * @param string $route_name
   *   A route name.
   *
   * @return bool
   *   TRUE if the current request is a specific route (name).
   */
  protected function isRoute($route_name) {
    return $this->requestHandler
      ->getRouteName($this
      ->getEntity(), $this
      ->getSourceEntity(), $route_name) === $this
      ->getRouteMatch()
      ->getRouteName() ? TRUE : FALSE;
  }

  /**
   * Is the current webform an entity reference from the source entity.
   *
   * @return bool
   *   TRUE is the current webform an entity reference from the source entity.
   */
  protected function isWebformEntityReferenceFromSourceEntity() {
    if (!$this->sourceEntity) {
      return FALSE;
    }
    $webform = $this->webformEntityReferenceManager
      ->getWebform($this->sourceEntity);
    if (!$webform) {
      return FALSE;
    }
    return $webform
      ->id() === $this
      ->getWebform()
      ->id() ? TRUE : FALSE;
  }

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

  // Helper functions

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

  /**
   * Get the webform submission's webform.
   *
   * @return \Drupal\webform\WebformInterface
   *   A webform.
   */
  public function getWebform() {

    /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */
    $webform_submission = $this
      ->getEntity();
    return $webform_submission
      ->getWebform();
  }

  /**
   * Get the webform submission's source entity.
   *
   * @return \Drupal\Core\Entity\EntityInterface
   *   The webform submission's source entity.
   */
  protected function getSourceEntity() {
    return $this->sourceEntity;
  }

  /**
   * Get the webform submission entity storage.
   *
   * @return \Drupal\Webform\WebformSubmissionStorageInterface
   *   The webform submission entity storage.
   */
  protected function getStorage() {
    return $this->entityTypeManager
      ->getStorage('webform_submission');
  }

  /**
   * Get the message manager.
   *
   * We need to wrap the message manager service because the webform submission
   * entity is being continuous cloned and updated during form processing.
   *
   * @see \Drupal\Core\Entity\EntityForm::buildEntity
   */
  protected function getMessageManager() {
    $this->messageManager
      ->setWebformSubmission($this
      ->getEntity());
    return $this->messageManager;
  }

  /**
   * Get source entity for use with entity limit total and user submissions.
   *
   * @return \Drupal\Core\Entity\EntityInterface|null
   *   The webform submission's source entity.
   */
  protected function getLimitSourceEntity() {

    /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */
    $webform_submission = $this
      ->getEntity();
    $source_entity = $webform_submission
      ->getSourceEntity();
    if ($source_entity && $source_entity
      ->getEntityTypeId() !== 'webform') {
      return $source_entity;
    }
    return NULL;
  }

  /**
   * Get a webform submission's webform setting.
   *
   * @param string $name
   *   Setting name.
   * @param null|mixed $default_value
   *   Default value.
   *
   * @return mixed
   *   A webform setting.
   */
  protected function getWebformSetting($name, $default_value = NULL) {
    $value = $this
      ->getWebform()
      ->getSetting($name) ?: $this
      ->config('webform.settings')
      ->get('settings.default_' . $name) ?: NULL;
    if ($value !== NULL) {
      return $this->tokenManager
        ->replace($value, $this
        ->getEntity(), [], [], $this->bubbleableMetadata);
    }
    else {
      return $default_value;
    }
  }

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

  // Share functions.

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

  /**
   * Determine if the submission form is being embedded in a share page.
   *
   * @return bool
   *   TRUE the submission form is being embedded in a share page.
   */
  protected function isSharePage() {
    return strpos($this
      ->getRouteMatch()
      ->getRouteName(), 'entity.webform.share_page') === 0;
  }

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

  // Ajax functions.
  // @see \Drupal\webform\Form\WebformAjaxFormTrait

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

  /**
   * {@inheritdoc}
   */
  protected function isAjax() {
    if ($this->operation === 'api') {
      return FALSE;
    }

    // Disable Ajax if the form has its #method set to 'get'.
    $elements = $this
      ->getWebform()
      ->getElementsInitialized();
    if (isset($elements['#method']) && $elements['#method'] === 'get') {
      return FALSE;
    }
    return $this
      ->getWebformSetting('ajax', FALSE);
  }

  /**
   * {@inheritdoc}
   */
  public function cancelAjaxForm(array &$form, FormStateInterface $form_state) {
    throw new \Exception('Webform submission Ajax form should never be cancelled. Only ::reset should be called.');
  }

  /**
   * {@inheritdoc}
   */
  public function validateAjaxForm(array &$form, FormStateInterface $form_state) {
    if (!$this
      ->isCallableAjaxCallback($form, $form_state)) {

      // Invalidate cache tags to prevent any caching issues.
      // @see https://www.drupal.org/project/drupal/issues/2352009
      Cache::invalidateTags([
        'webform:' . $this
          ->getWebform()
          ->id(),
      ]);
      $this
        ->missingAjaxCallback($form, $form_state);
    }
  }

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

  // API helper functions.

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

  /**
   * Programmatically check that a webform is open to new submissions.
   *
   * @param \Drupal\webform\WebformInterface $webform
   *   A webform.
   *
   * @return array|bool
   *   Return TRUE if the webform is open to new submissions else returns
   *   an error message.
   *
   * @see \Drupal\webform\WebformSubmissionForm::getCustomForm
   */
  public static function isOpen(WebformInterface $webform) {
    $webform_submission = WebformSubmission::create([
      'webform_id' => $webform
        ->id(),
    ]);

    /** @var \Drupal\webform\WebformSubmissionForm $form_object */
    $form_object = \Drupal::entityTypeManager()
      ->getFormObject('webform_submission', 'add');
    $form_object
      ->setEntity($webform_submission);

    /** @var \Drupal\webform\WebformMessageManagerInterface $message_manager */
    $message_manager = \Drupal::service('webform.message_manager');
    $message_manager
      ->setWebformSubmission($webform_submission);

    // Check form is open.
    if ($webform
      ->isClosed()) {
      if ($webform
        ->isOpening()) {
        return $message_manager
          ->get(WebformMessageManagerInterface::FORM_OPEN_MESSAGE);
      }
      else {
        return $message_manager
          ->get(WebformMessageManagerInterface::FORM_CLOSE_MESSAGE);
      }
    }

    // Check total limit.
    if ($form_object
      ->checkTotalLimit()) {
      return $message_manager
        ->get(WebformMessageManagerInterface::LIMIT_TOTAL_MESSAGE);
    }

    // Check user limit.
    if ($form_object
      ->checkUserLimit()) {
      return $message_manager
        ->get(WebformMessageManagerInterface::LIMIT_USER_MESSAGE);
    }
    return TRUE;
  }

  /**
   * Programmatically validate form values and submit a webform submission.
   *
   * @param array $values
   *   An array of submission form values and data.
   *
   * @return array|null
   *   An array of error messages if validation fails
   *   or NULL if there are no validation errors.
   */
  public static function validateFormValues(array $values) {
    return static::submitFormValues($values, TRUE);
  }

  /**
   * Programmatically validate form values and submit a webform submission.
   *
   * @param array $values
   *   An array of submission form values and data.
   * @param bool $validate_only
   *   Flag to trigger only webform validation. Defaults to FALSE.
   *
   * @return array|\Drupal\webform\WebformSubmissionInterface|null
   *   An array of error messages if validation fails
   *   or a webform submission (when $validate_only is FALSE)
   *   or NULL (when $validate_only is TRUE) if there are no validation errors.
   */
  public static function submitFormValues(array $values, $validate_only = FALSE) {
    $webform_submission = WebformSubmission::create($values);
    return static::submitWebformSubmission($webform_submission, $validate_only);
  }

  /**
   * Programmatically validate and submit a webform submission.
   *
   * @param \Drupal\webform\WebformSubmissionInterface $webform_submission
   *   WebformSubmission with values and data.
   *
   * @return array|null
   *   An array of error messages if validation fails or
   *   NULL if there are no validation errors.
   */
  public static function validateWebformSubmission(WebformSubmissionInterface $webform_submission) {
    return static::submitWebformSubmission($webform_submission, TRUE);
  }

  /**
   * Programmatically validate and submit a webform submission.
   *
   * @param \Drupal\webform\WebformSubmissionInterface $webform_submission
   *   WebformSubmission with values and data.
   * @param bool $validate_only
   *   Flag to trigger only webform validation. Defaults to FALSE.
   *
   * @return array|\Drupal\webform\WebformSubmissionInterface|null
   *   An array of error messages if validation fails
   *   or a webform submission (when $validate_only is FALSE)
   *   or NULL (when $validate_only is TRUE) if there are no validation errors.
   */
  public static function submitWebformSubmission(WebformSubmissionInterface $webform_submission, $validate_only = FALSE) {

    /** @var \Drupal\webform\WebformSubmissionForm $form_object */
    $form_object = \Drupal::entityTypeManager()
      ->getFormObject('webform_submission', 'api');
    $form_object
      ->setEntity($webform_submission);

    // Create an empty form state which will be populated when the submission
    // form is submitted.
    $form_state = new FormState();

    // Set the triggering element to an empty element to prevent
    // errors from managed files.
    // @see \Drupal\file\Element\ManagedFile::validateManagedFile
    $form_state
      ->setTriggeringElement([
      '#parents' => [],
    ]);

    // Get existing error messages.
    $error_messages = \Drupal::messenger()
      ->messagesByType(MessengerInterface::TYPE_ERROR);

    // Submit the form.
    \Drupal::formBuilder()
      ->submitForm($form_object, $form_state);

    // Get the errors but skip drafts.
    $errors = $webform_submission
      ->isDraft() && !$validate_only ? [] : $form_state
      ->getErrors();

    // Delete all form related error messages.
    \Drupal::messenger()
      ->deleteByType(MessengerInterface::TYPE_ERROR);

    // Restore existing error message.
    foreach ($error_messages as $error_message) {
      \Drupal::messenger()
        ->addError($error_message);
    }
    if ($errors) {
      return $errors;
    }
    elseif ($validate_only) {
      return NULL;
    }
    else {
      $webform_submission
        ->save();
      return $webform_submission;
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ContentEntityForm::$entityRepository protected property The entity repository service.
ContentEntityForm::$entityTypeBundleInfo protected property The entity type bundle info service.
ContentEntityForm::$time protected property The time service.
ContentEntityForm::addRevisionableFormFields protected function Add revision form fields if the entity enabled the UI.
ContentEntityForm::flagViolations protected function Flags violations for the current form. 4
ContentEntityForm::getBundleEntity protected function Returns the bundle entity of the entity, or NULL if there is none.
ContentEntityForm::getEditedFieldNames protected function Gets the names of all fields edited in the form. 4
ContentEntityForm::getFormDisplay public function Gets the form display. Overrides ContentEntityFormInterface::getFormDisplay
ContentEntityForm::getFormLangcode public function Gets the code identifying the active form language. Overrides ContentEntityFormInterface::getFormLangcode
ContentEntityForm::getNewRevisionDefault protected function Should new revisions created on default.
ContentEntityForm::initFormLangcodes protected function Initializes form language code values.
ContentEntityForm::isDefaultFormLangcode public function Checks whether the current form language matches the entity one. Overrides ContentEntityFormInterface::isDefaultFormLangcode
ContentEntityForm::prepareEntity protected function Prepares the entity object before the form is built first. Overrides EntityForm::prepareEntity 1
ContentEntityForm::setFormDisplay public function Sets the form display. Overrides ContentEntityFormInterface::setFormDisplay
ContentEntityForm::showRevisionUi protected function Checks whether the revision form fields should be added to the form.
ContentEntityForm::updateChangedTime public function Updates the changed time of the entity.
ContentEntityForm::updateFormLangcode public function Updates the form language to reflect any change to the entity language.
ContentEntityForm::__construct public function Constructs a ContentEntityForm object. 9
DependencySerializationTrait::$_entityStorages protected property
DependencySerializationTrait::$_serviceIds protected property
DependencySerializationTrait::__sleep public function 2
DependencySerializationTrait::__wakeup public function 2
EntityForm::$entityTypeManager protected property The entity type manager. 3
EntityForm::$moduleHandler protected property The module handler service.
EntityForm::$operation protected property The name of the current operation.
EntityForm::getEntity public function Gets the form entity. Overrides EntityFormInterface::getEntity
EntityForm::getEntityFromRouteMatch public function Determines which entity will be used by this form from a RouteMatch object. Overrides EntityFormInterface::getEntityFromRouteMatch 3
EntityForm::getOperation public function Gets the operation identifying the form. Overrides EntityFormInterface::getOperation
EntityForm::prepareInvokeAll protected function Invokes the specified prepare hook variant.
EntityForm::processForm public function Process callback: assigns weights and hides extra fields.
EntityForm::setEntityTypeManager public function Sets the entity type manager for this form. Overrides EntityFormInterface::setEntityTypeManager
EntityForm::setModuleHandler public function Sets the module handler for this form. Overrides EntityFormInterface::setModuleHandler
EntityForm::setOperation public function Sets the operation for this form. Overrides EntityFormInterface::setOperation
FormBase::$requestStack protected property The request stack. 1
FormBase::$routeMatch protected property The route match.
FormBase::config protected function Retrieves a configuration object.
FormBase::configFactory protected function Gets the config factory for this form. 3
FormBase::container private function Returns the service container.
FormBase::currentUser protected function Gets the current user.
FormBase::getRequest protected function Gets the request object.
FormBase::getRouteMatch protected function Gets the route match.
FormBase::logger protected function Gets the logger for a specific channel.
FormBase::redirect protected function Returns a redirect response object for the specified route.
FormBase::resetConfigFactory public function Resets the configuration factory.
FormBase::setConfigFactory public function Sets the config factory for this form.
FormBase::setRequestStack public function Sets the request stack object to use.
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 27
MessengerTrait::messenger public function Gets the messenger. 27
MessengerTrait::setMessenger public function Sets the messenger.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 1
RedirectDestinationTrait::getDestinationArray protected function Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
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.
WebformAjaxFormTrait::announce protected function Queue announcement with Ajax response.
WebformAjaxFormTrait::buildAjaxForm protected function Add Ajax support to a form.
WebformAjaxFormTrait::createAjaxResponse protected function Create an AjaxResponse or WebformAjaxResponse object.
WebformAjaxFormTrait::getAnnouncements protected function Get announcements.
WebformAjaxFormTrait::getDefaultAjaxSettings protected function Get default ajax callback settings. 1
WebformAjaxFormTrait::getFormStateRedirectUrl protected function Get redirect URL from the form's state.
WebformAjaxFormTrait::isCallableAjaxCallback protected function Determine if Ajax callback is callable.
WebformAjaxFormTrait::isDialog protected function Is the current request for an Ajax modal/dialog.
WebformAjaxFormTrait::isOffCanvasDialog protected function Is the current request for an off canvas dialog.
WebformAjaxFormTrait::missingAjaxCallback protected function Handle missing Ajax callback.
WebformAjaxFormTrait::replaceForm protected function Replace form via an Ajax response. 1
WebformAjaxFormTrait::resetAnnouncements protected function Reset announcements.
WebformAjaxFormTrait::setAnnouncements protected function Set announcements.
WebformAjaxFormTrait::submitAjaxForm public function Submit form #ajax callback. 1
WebformDialogFormTrait::buildDialogConfirmForm protected function Add modal dialog support to a confirm form.
WebformDialogFormTrait::buildDialogDeleteAction protected function Build webform dialog delete link.
WebformDialogFormTrait::buildDialogForm protected function Add modal dialog support to a form.
WebformDialogFormTrait::closeDialog public function Close dialog.
WebformDialogFormTrait::noSubmit public function Empty submit callback used to only have the submit button to use an #ajax submit callback. Overrides WebformAjaxFormTrait::noSubmit
WebformSubmissionForm::$aliasManager protected property The path alias manager.
WebformSubmissionForm::$bubbleableMetadata protected property Bubbleable metadata.
WebformSubmissionForm::$conditionsValidator protected property The webform submission conditions (#states) validator.
WebformSubmissionForm::$configFactory protected property The configuration object factory. Overrides FormBase::$configFactory
WebformSubmissionForm::$elementManager protected property The webform element plugin manager.
WebformSubmissionForm::$entity protected property The webform submission. Overrides ContentEntityForm::$entity
WebformSubmissionForm::$entityFieldManager protected property The entity field manager.
WebformSubmissionForm::$formBuilder protected property The form builder.
WebformSubmissionForm::$generate protected property The webform submission generation service.
WebformSubmissionForm::$killSwitch protected property The kill switch.
WebformSubmissionForm::$messageManager protected property The webform message manager.
WebformSubmissionForm::$originalData protected property Stores the original submission data passed via the EntityFormBuilder.
WebformSubmissionForm::$pathValidator protected property The path validator service.
WebformSubmissionForm::$renderer protected property The renderer service.
WebformSubmissionForm::$requestHandler protected property The webform request handler.
WebformSubmissionForm::$selectionManager protected property The selection plugin manager service.
WebformSubmissionForm::$sourceEntity protected property The source entity.
WebformSubmissionForm::$statesPrefix protected property States API prefix.
WebformSubmissionForm::$thirdPartySettingsManager protected property The webform third party settings manager.
WebformSubmissionForm::$tokenManager protected property The webform token manager.
WebformSubmissionForm::$webformEntityReferenceManager protected property The webform entity reference manager.
WebformSubmissionForm::actions protected function Returns an array of supported actions for the current entity form. Overrides EntityForm::actions
WebformSubmissionForm::actionsElement protected function Returns the action form element for the current entity form. Overrides EntityForm::actionsElement
WebformSubmissionForm::addCacheableDependency protected function Add cache dependency to the form's render array.
WebformSubmissionForm::addStatesPrefix protected function Add unique class prefix to all :input #states selectors.
WebformSubmissionForm::afterBuild public function Form element #after_build callback: Updates the entity with submitted data. Overrides EntityForm::afterBuild
WebformSubmissionForm::alterElementsForm protected function Alter webform elements form.
WebformSubmissionForm::attachBehaviors protected function Attach behaviors with libraries to the form.
WebformSubmissionForm::attachLibraries protected function Attach libraries to the form.
WebformSubmissionForm::autosave public function Webform submission handler to autosave when there are validation errors.
WebformSubmissionForm::buildEntity public function Builds an updated entity object based upon the submitted form values. Overrides ContentEntityForm::buildEntity
WebformSubmissionForm::buildForm public function Form constructor. Overrides EntityForm::buildForm 1
WebformSubmissionForm::cancelAjaxForm public function Cancel form #ajax callback. Overrides WebformDialogFormTrait::cancelAjaxForm
WebformSubmissionForm::checkPrepopulateDataValid protected function Determine if element prepopulate data is valid.
WebformSubmissionForm::checkTotalLimit protected function Check webform submission total limits.
WebformSubmissionForm::checkUserLimit protected function Check webform submission user limit.
WebformSubmissionForm::complete public function Webform submission handler for the 'complete' action.
WebformSubmissionForm::confirmForm public function Webform confirm(ation) handler.
WebformSubmissionForm::copyFormValuesToEntity public function Copies top-level form values to entity properties. Overrides ContentEntityForm::copyFormValuesToEntity
WebformSubmissionForm::create public static function Instantiates a new instance of this class. Overrides ContentEntityForm::create
WebformSubmissionForm::displayCurrentPage protected function Set webform wizard current page.
WebformSubmissionForm::displayMessages protected function Display draft, previous submission, and autofill status messages for this webform submission.
WebformSubmissionForm::draft public function Webform submission handler for the 'draft' action.
WebformSubmissionForm::draftEnabled protected function Determine if drafts are enabled.
WebformSubmissionForm::form public function Gets the actual form array to be built. Overrides ContentEntityForm::form
WebformSubmissionForm::getBaseFormId public function Returns a string identifying the base form. Overrides EntityForm::getBaseFormId
WebformSubmissionForm::getConfirmationUrl protected function Get the webform's confrmation URL.
WebformSubmissionForm::getCurrentPage protected function Get the current page's key.
WebformSubmissionForm::getCustomForm protected function Get custom webform which is displayed instead of the webform's elements.
WebformSubmissionForm::getFirstPage protected function Get first page's key.
WebformSubmissionForm::getFormId public function Returns a unique string identifying the form. Overrides EntityForm::getFormId
WebformSubmissionForm::getLastPage protected function Get last page's key.
WebformSubmissionForm::getLimitSourceEntity protected function Get source entity for use with entity limit total and user submissions.
WebformSubmissionForm::getMessageManager protected function Get the message manager.
WebformSubmissionForm::getNextPage protected function Get next page's key.
WebformSubmissionForm::getPages protected function Get visible wizard pages.
WebformSubmissionForm::getPreviousPage protected function Get previous page's key.
WebformSubmissionForm::getSourceEntity protected function Get the webform submission's source entity.
WebformSubmissionForm::getStorage protected function Get the webform submission entity storage.
WebformSubmissionForm::getUploadedManagedFileIds protected function Get uploaded managed file ids.
WebformSubmissionForm::getWebform public function Get the webform submission's webform.
WebformSubmissionForm::getWebformSetting protected function Get a webform submission's webform setting.
WebformSubmissionForm::getWrapperId protected function Get the form's Ajax wrapper id. Overrides WebformAjaxFormTrait::getWrapperId
WebformSubmissionForm::gotoPage public function Webform submission handler for the 'goto' action.
WebformSubmissionForm::hasPages protected function Determine if this is a multi-step wizard form.
WebformSubmissionForm::init protected function Initializes the form state and the entity before the first form build. Overrides ContentEntityForm::init
WebformSubmissionForm::isAjax protected function Returns if webform is using Ajax. Overrides WebformDialogFormTrait::isAjax
WebformSubmissionForm::isConfidential protected function Returns the webform confidential indicator.
WebformSubmissionForm::isFormNoValidate protected function Is client side validation disabled (using the webform novalidate attribute).
WebformSubmissionForm::isGet protected function Is the webform being initially loaded via GET method.
WebformSubmissionForm::isOpen public static function Programmatically check that a webform is open to new submissions.
WebformSubmissionForm::isRoute protected function Determine if the current request is a specific route (name).
WebformSubmissionForm::isSharePage protected function Determine if the submission form is being embedded in a share page.
WebformSubmissionForm::isWebformEntityReferenceFromSourceEntity protected function Is the current webform an entity reference from the source entity.
WebformSubmissionForm::next public function Webform submission handler for the 'next' action.
WebformSubmissionForm::noValidate public function Webform submission validation that does nothing but clear validation errors. Overrides WebformDialogFormTrait::noValidate
WebformSubmissionForm::overrideSettings protected function Override webform settings for the webform submission.
WebformSubmissionForm::pagesElement protected function Returns the wizard page submit buttons for the current entity form.
WebformSubmissionForm::populateElements protected function Populate webform elements.
WebformSubmissionForm::prepareElements protected function Prepare webform elements.
WebformSubmissionForm::prepopulateData protected function Prepopulate element data.
WebformSubmissionForm::previous public function Webform submission handler for the 'previous' action.
WebformSubmissionForm::rebuild public function Webform submission handler for the 'rebuild' action.
WebformSubmissionForm::removeConfirmationModal public static function Hide confirmation modal during form validation.
WebformSubmissionForm::reset public function Webform submission handler for the 'reset' action.
WebformSubmissionForm::resetEnabled protected function Determine if reset is enabled.
WebformSubmissionForm::save public function Form submission handler for the 'save' action. Overrides EntityForm::save
WebformSubmissionForm::setConfirmation protected function Set webform state confirmation redirect and message. 1
WebformSubmissionForm::setEntity public function This is the best place to override an entity form's default settings because it is called immediately after the form object is initialized. Overrides EntityForm::setEntity
WebformSubmissionForm::setFormPropertiesFromElements protected function Set the webform properties from the elements.
WebformSubmissionForm::setTrustedRedirectUrl protected function Set webform state to redirect to a trusted redirect response.
WebformSubmissionForm::submit public function Webform submission handler for the 'submit' action.
WebformSubmissionForm::submitForm public function This is the default entity object builder function. It is called before any other submit handler to build the new entity object to be used by the following submit handlers. At this point of the form workflow the entity is validated and the form state… Overrides ContentEntityForm::submitForm
WebformSubmissionForm::submitFormValues public static function Programmatically validate form values and submit a webform submission.
WebformSubmissionForm::submitWebformSubmission public static function Programmatically validate and submit a webform submission.
WebformSubmissionForm::validateAjaxForm public function Validate form #ajax callback. Overrides WebformAjaxFormTrait::validateAjaxForm
WebformSubmissionForm::validateForm public function Button-level validation handlers are highly discouraged for entity forms, as they will prevent entity validation from running. If the entity is going to be saved during the form submission, this method should be manually invoked from the button-level… Overrides ContentEntityForm::validateForm 1
WebformSubmissionForm::validateFormValues public static function Programmatically validate form values and submit a webform submission.
WebformSubmissionForm::validateUploadedManagedFiles protected function Validate uploaded managed file limits.
WebformSubmissionForm::validateWebformSubmission public static function Programmatically validate and submit a webform submission.
WebformSubmissionForm::wizardSubmit protected function Webform submission handler for the wizard submit action.