class WebformSubmissionForm in Webform 6.x
Same name and namespace in other branches
- 8.5 src/WebformSubmissionForm.php \Drupal\webform\WebformSubmissionForm
Provides a webform to collect and edit submissions.
Hierarchy
- class \Drupal\Core\Form\FormBase implements ContainerInjectionInterface, FormInterface uses DependencySerializationTrait, LoggerChannelTrait, MessengerTrait, RedirectDestinationTrait, StringTranslationTrait
- class \Drupal\Core\Entity\EntityForm implements EntityFormInterface
- class \Drupal\Core\Entity\ContentEntityForm implements ContentEntityFormInterface
- class \Drupal\webform\WebformSubmissionForm uses WebformDialogFormTrait
- class \Drupal\Core\Entity\ContentEntityForm implements ContentEntityFormInterface
- class \Drupal\Core\Entity\EntityForm implements EntityFormInterface
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
File
- src/
WebformSubmissionForm.php, line 42
Namespace
Drupal\webformView 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
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
ContentEntityForm:: |
protected | property | The entity repository service. | |
ContentEntityForm:: |
protected | property | The entity type bundle info service. | |
ContentEntityForm:: |
protected | property | The time service. | |
ContentEntityForm:: |
protected | function | Add revision form fields if the entity enabled the UI. | |
ContentEntityForm:: |
protected | function | Flags violations for the current form. | 4 |
ContentEntityForm:: |
protected | function | Returns the bundle entity of the entity, or NULL if there is none. | |
ContentEntityForm:: |
protected | function | Gets the names of all fields edited in the form. | 4 |
ContentEntityForm:: |
public | function |
Gets the form display. Overrides ContentEntityFormInterface:: |
|
ContentEntityForm:: |
public | function |
Gets the code identifying the active form language. Overrides ContentEntityFormInterface:: |
|
ContentEntityForm:: |
protected | function | Should new revisions created on default. | |
ContentEntityForm:: |
protected | function | Initializes form language code values. | |
ContentEntityForm:: |
public | function |
Checks whether the current form language matches the entity one. Overrides ContentEntityFormInterface:: |
|
ContentEntityForm:: |
protected | function |
Prepares the entity object before the form is built first. Overrides EntityForm:: |
1 |
ContentEntityForm:: |
public | function |
Sets the form display. Overrides ContentEntityFormInterface:: |
|
ContentEntityForm:: |
protected | function | Checks whether the revision form fields should be added to the form. | |
ContentEntityForm:: |
public | function | Updates the changed time of the entity. | |
ContentEntityForm:: |
public | function | Updates the form language to reflect any change to the entity language. | |
ContentEntityForm:: |
public | function | Constructs a ContentEntityForm object. | 9 |
DependencySerializationTrait:: |
protected | property | ||
DependencySerializationTrait:: |
protected | property | ||
DependencySerializationTrait:: |
public | function | 2 | |
DependencySerializationTrait:: |
public | function | 2 | |
EntityForm:: |
protected | property | The entity type manager. | 3 |
EntityForm:: |
protected | property | The module handler service. | |
EntityForm:: |
protected | property | The name of the current operation. | |
EntityForm:: |
public | function |
Gets the form entity. Overrides EntityFormInterface:: |
|
EntityForm:: |
public | function |
Determines which entity will be used by this form from a RouteMatch object. Overrides EntityFormInterface:: |
3 |
EntityForm:: |
public | function |
Gets the operation identifying the form. Overrides EntityFormInterface:: |
|
EntityForm:: |
protected | function | Invokes the specified prepare hook variant. | |
EntityForm:: |
public | function | Process callback: assigns weights and hides extra fields. | |
EntityForm:: |
public | function |
Sets the entity type manager for this form. Overrides EntityFormInterface:: |
|
EntityForm:: |
public | function |
Sets the module handler for this form. Overrides EntityFormInterface:: |
|
EntityForm:: |
public | function |
Sets the operation for this form. Overrides EntityFormInterface:: |
|
FormBase:: |
protected | property | The request stack. | 1 |
FormBase:: |
protected | property | The route match. | |
FormBase:: |
protected | function | Retrieves a configuration object. | |
FormBase:: |
protected | function | Gets the config factory for this form. | 3 |
FormBase:: |
private | function | Returns the service container. | |
FormBase:: |
protected | function | Gets the current user. | |
FormBase:: |
protected | function | Gets the request object. | |
FormBase:: |
protected | function | Gets the route match. | |
FormBase:: |
protected | function | Gets the logger for a specific channel. | |
FormBase:: |
protected | function | Returns a redirect response object for the specified route. | |
FormBase:: |
public | function | Resets the configuration factory. | |
FormBase:: |
public | function | Sets the config factory for this form. | |
FormBase:: |
public | function | Sets the request stack object to use. | |
LoggerChannelTrait:: |
protected | property | The logger channel factory service. | |
LoggerChannelTrait:: |
protected | function | Gets the logger for a specific channel. | |
LoggerChannelTrait:: |
public | function | Injects the logger channel factory. | |
MessengerTrait:: |
protected | property | The messenger. | 27 |
MessengerTrait:: |
public | function | Gets the messenger. | 27 |
MessengerTrait:: |
public | function | Sets the messenger. | |
RedirectDestinationTrait:: |
protected | property | The redirect destination service. | 1 |
RedirectDestinationTrait:: |
protected | function | Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url. | |
RedirectDestinationTrait:: |
protected | function | Returns the redirect destination service. | |
RedirectDestinationTrait:: |
public | function | Sets the redirect destination service. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 4 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. | |
WebformAjaxFormTrait:: |
protected | function | Queue announcement with Ajax response. | |
WebformAjaxFormTrait:: |
protected | function | Add Ajax support to a form. | |
WebformAjaxFormTrait:: |
protected | function | Create an AjaxResponse or WebformAjaxResponse object. | |
WebformAjaxFormTrait:: |
protected | function | Get announcements. | |
WebformAjaxFormTrait:: |
protected | function | Get default ajax callback settings. | 1 |
WebformAjaxFormTrait:: |
protected | function | Get redirect URL from the form's state. | |
WebformAjaxFormTrait:: |
protected | function | Determine if Ajax callback is callable. | |
WebformAjaxFormTrait:: |
protected | function | Is the current request for an Ajax modal/dialog. | |
WebformAjaxFormTrait:: |
protected | function | Is the current request for an off canvas dialog. | |
WebformAjaxFormTrait:: |
protected | function | Handle missing Ajax callback. | |
WebformAjaxFormTrait:: |
protected | function | Replace form via an Ajax response. | 1 |
WebformAjaxFormTrait:: |
protected | function | Reset announcements. | |
WebformAjaxFormTrait:: |
protected | function | Set announcements. | |
WebformAjaxFormTrait:: |
public | function | Submit form #ajax callback. | 1 |
WebformDialogFormTrait:: |
protected | function | Add modal dialog support to a confirm form. | |
WebformDialogFormTrait:: |
protected | function | Build webform dialog delete link. | |
WebformDialogFormTrait:: |
protected | function | Add modal dialog support to a form. | |
WebformDialogFormTrait:: |
public | function | Close dialog. | |
WebformDialogFormTrait:: |
public | function |
Empty submit callback used to only have the submit button to use an #ajax submit callback. Overrides WebformAjaxFormTrait:: |
|
WebformSubmissionForm:: |
protected | property | The path alias manager. | |
WebformSubmissionForm:: |
protected | property | Bubbleable metadata. | |
WebformSubmissionForm:: |
protected | property | The webform submission conditions (#states) validator. | |
WebformSubmissionForm:: |
protected | property |
The configuration object factory. Overrides FormBase:: |
|
WebformSubmissionForm:: |
protected | property | The webform element plugin manager. | |
WebformSubmissionForm:: |
protected | property |
The webform submission. Overrides ContentEntityForm:: |
|
WebformSubmissionForm:: |
protected | property | The entity field manager. | |
WebformSubmissionForm:: |
protected | property | The form builder. | |
WebformSubmissionForm:: |
protected | property | The webform submission generation service. | |
WebformSubmissionForm:: |
protected | property | The kill switch. | |
WebformSubmissionForm:: |
protected | property | The webform message manager. | |
WebformSubmissionForm:: |
protected | property | Stores the original submission data passed via the EntityFormBuilder. | |
WebformSubmissionForm:: |
protected | property | The path validator service. | |
WebformSubmissionForm:: |
protected | property | The renderer service. | |
WebformSubmissionForm:: |
protected | property | The webform request handler. | |
WebformSubmissionForm:: |
protected | property | The selection plugin manager service. | |
WebformSubmissionForm:: |
protected | property | The source entity. | |
WebformSubmissionForm:: |
protected | property | States API prefix. | |
WebformSubmissionForm:: |
protected | property | The webform third party settings manager. | |
WebformSubmissionForm:: |
protected | property | The webform token manager. | |
WebformSubmissionForm:: |
protected | property | The webform entity reference manager. | |
WebformSubmissionForm:: |
protected | function |
Returns an array of supported actions for the current entity form. Overrides EntityForm:: |
|
WebformSubmissionForm:: |
protected | function |
Returns the action form element for the current entity form. Overrides EntityForm:: |
|
WebformSubmissionForm:: |
protected | function | Add cache dependency to the form's render array. | |
WebformSubmissionForm:: |
protected | function | Add unique class prefix to all :input #states selectors. | |
WebformSubmissionForm:: |
public | function |
Form element #after_build callback: Updates the entity with submitted data. Overrides EntityForm:: |
|
WebformSubmissionForm:: |
protected | function | Alter webform elements form. | |
WebformSubmissionForm:: |
protected | function | Attach behaviors with libraries to the form. | |
WebformSubmissionForm:: |
protected | function | Attach libraries to the form. | |
WebformSubmissionForm:: |
public | function | Webform submission handler to autosave when there are validation errors. | |
WebformSubmissionForm:: |
public | function |
Builds an updated entity object based upon the submitted form values. Overrides ContentEntityForm:: |
|
WebformSubmissionForm:: |
public | function |
Form constructor. Overrides EntityForm:: |
1 |
WebformSubmissionForm:: |
public | function |
Cancel form #ajax callback. Overrides WebformDialogFormTrait:: |
|
WebformSubmissionForm:: |
protected | function | Determine if element prepopulate data is valid. | |
WebformSubmissionForm:: |
protected | function | Check webform submission total limits. | |
WebformSubmissionForm:: |
protected | function | Check webform submission user limit. | |
WebformSubmissionForm:: |
public | function | Webform submission handler for the 'complete' action. | |
WebformSubmissionForm:: |
public | function | Webform confirm(ation) handler. | |
WebformSubmissionForm:: |
public | function |
Copies top-level form values to entity properties. Overrides ContentEntityForm:: |
|
WebformSubmissionForm:: |
public static | function |
Instantiates a new instance of this class. Overrides ContentEntityForm:: |
|
WebformSubmissionForm:: |
protected | function | Set webform wizard current page. | |
WebformSubmissionForm:: |
protected | function | Display draft, previous submission, and autofill status messages for this webform submission. | |
WebformSubmissionForm:: |
public | function | Webform submission handler for the 'draft' action. | |
WebformSubmissionForm:: |
protected | function | Determine if drafts are enabled. | |
WebformSubmissionForm:: |
public | function |
Gets the actual form array to be built. Overrides ContentEntityForm:: |
|
WebformSubmissionForm:: |
public | function |
Returns a string identifying the base form. Overrides EntityForm:: |
|
WebformSubmissionForm:: |
protected | function | Get the webform's confrmation URL. | |
WebformSubmissionForm:: |
protected | function | Get the current page's key. | |
WebformSubmissionForm:: |
protected | function | Get custom webform which is displayed instead of the webform's elements. | |
WebformSubmissionForm:: |
protected | function | Get first page's key. | |
WebformSubmissionForm:: |
public | function |
Returns a unique string identifying the form. Overrides EntityForm:: |
|
WebformSubmissionForm:: |
protected | function | Get last page's key. | |
WebformSubmissionForm:: |
protected | function | Get source entity for use with entity limit total and user submissions. | |
WebformSubmissionForm:: |
protected | function | Get the message manager. | |
WebformSubmissionForm:: |
protected | function | Get next page's key. | |
WebformSubmissionForm:: |
protected | function | Get visible wizard pages. | |
WebformSubmissionForm:: |
protected | function | Get previous page's key. | |
WebformSubmissionForm:: |
protected | function | Get the webform submission's source entity. | |
WebformSubmissionForm:: |
protected | function | Get the webform submission entity storage. | |
WebformSubmissionForm:: |
protected | function | Get uploaded managed file ids. | |
WebformSubmissionForm:: |
public | function | Get the webform submission's webform. | |
WebformSubmissionForm:: |
protected | function | Get a webform submission's webform setting. | |
WebformSubmissionForm:: |
protected | function |
Get the form's Ajax wrapper id. Overrides WebformAjaxFormTrait:: |
|
WebformSubmissionForm:: |
public | function | Webform submission handler for the 'goto' action. | |
WebformSubmissionForm:: |
protected | function | Determine if this is a multi-step wizard form. | |
WebformSubmissionForm:: |
protected | function |
Initializes the form state and the entity before the first form build. Overrides ContentEntityForm:: |
|
WebformSubmissionForm:: |
protected | function |
Returns if webform is using Ajax. Overrides WebformDialogFormTrait:: |
|
WebformSubmissionForm:: |
protected | function | Returns the webform confidential indicator. | |
WebformSubmissionForm:: |
protected | function | Is client side validation disabled (using the webform novalidate attribute). | |
WebformSubmissionForm:: |
protected | function | Is the webform being initially loaded via GET method. | |
WebformSubmissionForm:: |
public static | function | Programmatically check that a webform is open to new submissions. | |
WebformSubmissionForm:: |
protected | function | Determine if the current request is a specific route (name). | |
WebformSubmissionForm:: |
protected | function | Determine if the submission form is being embedded in a share page. | |
WebformSubmissionForm:: |
protected | function | Is the current webform an entity reference from the source entity. | |
WebformSubmissionForm:: |
public | function | Webform submission handler for the 'next' action. | |
WebformSubmissionForm:: |
public | function |
Webform submission validation that does nothing but clear validation errors. Overrides WebformDialogFormTrait:: |
|
WebformSubmissionForm:: |
protected | function | Override webform settings for the webform submission. | |
WebformSubmissionForm:: |
protected | function | Returns the wizard page submit buttons for the current entity form. | |
WebformSubmissionForm:: |
protected | function | Populate webform elements. | |
WebformSubmissionForm:: |
protected | function | Prepare webform elements. | |
WebformSubmissionForm:: |
protected | function | Prepopulate element data. | |
WebformSubmissionForm:: |
public | function | Webform submission handler for the 'previous' action. | |
WebformSubmissionForm:: |
public | function | Webform submission handler for the 'rebuild' action. | |
WebformSubmissionForm:: |
public static | function | Hide confirmation modal during form validation. | |
WebformSubmissionForm:: |
public | function | Webform submission handler for the 'reset' action. | |
WebformSubmissionForm:: |
protected | function | Determine if reset is enabled. | |
WebformSubmissionForm:: |
public | function |
Form submission handler for the 'save' action. Overrides EntityForm:: |
|
WebformSubmissionForm:: |
protected | function | Set webform state confirmation redirect and message. | 1 |
WebformSubmissionForm:: |
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:: |
|
WebformSubmissionForm:: |
protected | function | Set the webform properties from the elements. | |
WebformSubmissionForm:: |
protected | function | Set webform state to redirect to a trusted redirect response. | |
WebformSubmissionForm:: |
public | function | Webform submission handler for the 'submit' action. | |
WebformSubmissionForm:: |
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:: |
|
WebformSubmissionForm:: |
public static | function | Programmatically validate form values and submit a webform submission. | |
WebformSubmissionForm:: |
public static | function | Programmatically validate and submit a webform submission. | |
WebformSubmissionForm:: |
public | function |
Validate form #ajax callback. Overrides WebformAjaxFormTrait:: |
|
WebformSubmissionForm:: |
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:: |
1 |
WebformSubmissionForm:: |
public static | function | Programmatically validate form values and submit a webform submission. | |
WebformSubmissionForm:: |
protected | function | Validate uploaded managed file limits. | |
WebformSubmissionForm:: |
public static | function | Programmatically validate and submit a webform submission. | |
WebformSubmissionForm:: |
protected | function | Webform submission handler for the wizard submit action. |