Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
novicell/custom_forms / src / Form / CustomForm.php
Size: Mime:
<?php

namespace Drupal\custom_forms\Form;

use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Ajax\RedirectCommand;
use Drupal\Core\Entity\EntityMalformedException;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Render\Element\StatusMessages;
use Drupal\Core\TempStore\TempStoreException;
use Drupal\Core\Url;
use Drupal\custom_forms\Ajax\CustomFormsErrorCommand;
use Drupal\custom_forms\Controller\SubmissionValidationController;
use Drupal\custom_forms\CustomFormItemFactory;
use Drupal\custom_forms\Entity\CustomForm as CustomFormEntity;
use Drupal\custom_forms\Entity\CustomFormSubmission;
use Drupal\custom_forms\Entity\CustomFormType;
use Drupal\dds\Service\SessionService;
use Drupal\node\Entity\Node;
use NumberFormatter;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;

/**
 * Class CustomForm
 *
 * This is the form that is in charge for actually rendering the custom form.
 * It makes sure all fields and groups are properly renderes as well as handle
 * the submission events.
 *
 * @package Drupal\custom_forms\Form
 */
class CustomForm extends FormBase {

  /**
   * The event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected $eventDispatcher;

  /**
   * The language the form is displayed in.
   *
   * @var \Drupal\Core\Language\LanguageInterface
   */
  protected $language;

  /**
   * The logger channel to send log messages to.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  protected $log;

  /**
   * The factory in charge of CRUD operations for custom form items.
   *
   * @var \Drupal\custom_forms\CustomFormItemFactory
   */
  protected $itemFactory;

  /**
   * The validator used for validating submissions.
   *
   * @var \Drupal\custom_forms\Controller\SubmissionValidationController
   */
  protected $validator;

  protected $sessionService;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('event_dispatcher'),
      $container->get('custom_forms.factory.item'),
      $container->get('custom_forms.submission.validator'),
      $container->get('language_manager'),
      $container->get('dds.session')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function __construct(
    EventDispatcherInterface        $event_dispatcher,
    CustomFormItemFactory           $item_factory,
    SubmissionValidationController  $validator,
    LanguageManager                 $language_manager,
    SessionService                  $session_service
  ) {
    $this->eventDispatcher  = $event_dispatcher;
    $this->language         = $language_manager->getCurrentLanguage();
    $this->log              = $this->logger('Custom Forms');
    $this->itemFactory      = $item_factory;
    $this->validator        = $validator;
    $this->sessionService   = $session_service;
  }

  /**
   * Returns a unique string identifying the form.
   *
   * The returned ID should be a unique string that can be a valid PHP function
   * name, since it's used in hook implementation names such as
   * hook_form_FORM_ID_alter().
   *
   * @return string
   *   The unique string identifying the form.
   */
  public function getFormId() : string {
    return 'custom_forms';
  }

  /**
   * Form constructor.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @return array
   *   The form structure.
   */
  public function buildForm(array $form, FormStateInterface $form_state) : array {
    $form_arguments = $form_state->getBuildInfo()['args'][0];
    $request = $this->getRequest();
    /** @var \Drupal\custom_forms\Entity\CustomForm $custom_form */
    $custom_form = $form_arguments['custom_form'];
    $custom_form_type = CustomFormType::load($custom_form->bundle());

    // Get translated form if it exists.
    if ($custom_form->hasTranslation($this->language->getId())) {
      $custom_form = $custom_form->getTranslation($this->language->getId());
    }

    // Disable caching if it's been configured as such.
    if (!$custom_form_type->isCachingEnabled()) {
      $form['#cache']['max-age'] = 0;
    }

    // General styling.
    $form['#attached']['library'][] = 'custom_forms/form-style';
    // Add error handling javascript library.
    $form['#attached']['library'][] = 'custom_forms/field-errors';

    // Disable HTML5 validation as we have no control over it.
    $form['#attributes']['novalidate'] = 'novalidate';
    $form['#attributes']['data-custom-form-id'] = $custom_form->id();

    $form['custom_form'] = [
      '#type' => 'value',
      '#value' => $custom_form,
    ];

    if ((boolean) $custom_form->get('use_ajax')->value) {
      $form['#attached']['library'][] = 'custom_forms/prevent-double-submit';
      $form['_custom_form_detector'] = [
        '#type' => 'hidden',
        '#value' => 1,
      ];
    }

    // Add the receipt page id to the form so it's saved per submission.
    if ($custom_form->hasField('receipt_page') && !$custom_form->get('receipt_page')->isEmpty()) {
      $form['_receipt_page_id'] = [
        '#type' => 'value',
        '#value' => $custom_form->get('receipt_page')->target_id,
      ];
    }

    // Add the form URL if it's not been set yet.
    if (empty($form_state->getValue('_form_url'))) {
      $form['_form_url'] = [
        '#type' => 'value',
        '#value' => $this->generateFormUrl($request),
      ];
    }

    /** @var \Drupal\custom_forms\CustomFormItem[] $items */
    $items = $this->itemFactory->getFormItemsAsTree($custom_form, TRUE, TRUE);

    foreach ($items as $id => $item) {
      $form += $item->buildForm($form, $form_state, $request);
    }

    return $form;
  }

  /**
   * Form submission 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 submitForm(array &$form, FormStateInterface $form_state) : void {
    $request = $this->getRequest();
    if ($request->isXmlHttpRequest()) {
      return;
    }

    /** @var \Drupal\custom_forms\Entity\CustomForm $custom_form */
    $custom_form = $form_state->getValue('custom_form');
    /** @var \Drupal\custom_forms\CustomFormItem[] $items */
    $items = $this->itemFactory->getFormItems($custom_form, TRUE, TRUE);
    $submission = $this->createSubmission($form_state);

    $validation = $this->validateValues($submission, $items, $form_state);
    if (!$validation) {
      return;
    }

    $processed = $this->processValues($submission, $items, $form_state);
    if (!$processed) {
      return;
    }

    $initialized = $this->initializeSubmission($submission, $items, $form_state);
    if (!$initialized) {
      return;
    }
  }

  /**
   * Submits the form through ajax.
   *
   * @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.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request that is calling this function.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   Returns an AjaxResponse for usage within Drupal's ajax system.
   */
  public function ajaxSubmitForm(array &$form, FormStateInterface $form_state, Request $request) : AjaxResponse {
    $response = new AjaxResponse();
    // If this isn't an ajax request, we simply return the empty ajax response.
    if (!$request->isXmlHttpRequest()) {
      return $response;
    }
    /** @var \Drupal\custom_forms\Entity\CustomForm $custom_form */
    $custom_form = $form_state->getValue('custom_form');
    /** @var \Drupal\custom_forms\CustomFormItem[] $items */
    $items = $this->itemFactory->getFormItems($custom_form, TRUE, TRUE);
    $submission = $this->createSubmission($form_state);

    $validation = $this->validateValues($submission, $items, $form_state, $response);
    // If validation failed we return the response.
    if (!$validation) {
      return $response;
    }

    $processed = $this->processValues($submission, $items, $form_state, $response);
    if (!$processed) {
      return $response;
    }

    $initialized = $this->initializeSubmission($submission, $items, $form_state, $response);
    if (!$initialized) {
      return $response;
    }

    return $response;
  }

  /**
   * Create a submission from the form state.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state used to populate the submission.
   *
   * @return \Drupal\custom_forms\Entity\CustomFormSubmission
   *   Returns the created Custom Form Submission.
   */
  private function createSubmission(FormStateInterface $form_state) : CustomFormSubmission {
    // Get the cleaned values from the form.
    $values = $form_state->cleanValues()->getValues();
    /** @var \Drupal\custom_forms\Entity\CustomForm $custom_form */
    $custom_form = $form_state->getValue('custom_form');

    /** @var \Drupal\custom_forms\Entity\CustomFormSubmission $submission */
    $submission = CustomFormSubmission::create();
    $submission->setFormId($custom_form->id());
    // Unset the custom form and form detector as we do not want to serialize them.
    unset($values['custom_form'], $values['_custom_form_detector']);
    $submission->setDataArray($values);

    return $submission;
  }

  /**
   * Validate the submitted values.
   *
   * @param \Drupal\custom_forms\Entity\CustomFormSubmission $submission
   *   The submission that was created.
   * @param \Drupal\custom_forms\CustomFormItem[] $items
   *   The items used for validation.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param \Drupal\Core\Ajax\AjaxResponse|NULL $response
   *   (Optional) The response to return. Only used for ajax requests.
   *
   * @return bool
   *   Returns TRUE if the values successfully validate, otherwise FALSE.
   */
  private function validateValues(CustomFormSubmission $submission, array $items, FormStateInterface $form_state, AjaxResponse $response = NULL): bool {
    /** @var \Drupal\Core\Language\LanguageInterface $language */
    $language = $submission->getForm()->language();
    $number_formatter = new NumberFormatter($language->getId(), NumberFormatter::DECIMAL);
    // Trigger the pre-transition
    $submission->getState()->applyTransitionById('validate');

    // Get any errors applied to the submission from the pre-transition.
    $errors = $submission->getErrors();

    // Catch any errors.
    $status = $this->handleErrors($submission->getForm(), $errors, $items, $form_state, $response);
    if (!$status) {
      // We only return FALSE if the display error check returned false.
      return FALSE;
    }

    // Run the validation.
    $this->validator->validateItems($submission, $items, $number_formatter);

    // Get any errors applied to the submission during validation.
    $errors = $submission->getErrors();

    // Catch any errors.
    $status = $this->handleErrors($submission->getForm(), $errors, $items, $form_state, $response);
    if (!$status) {
      // We only return FALSE if the display error check returned false.
      return FALSE;
    }

    // Only save the submission if validation was successful.
    try {
      // Clear any errors since validation was successful, to avoid confusion
      // when troubleshooting.
      $submission->setErrors([]);

      // Save the submission.
      $submission->save();

      // Get any errors during post-transition.
      $errors = $submission->getErrors();

      // Catch any errors.
      $status = $this->handleErrors($submission->getForm(), $errors, $items, $form_state, $response);
      if (!$status) {
        // We only return FALSE if the display error check returned false.
        return FALSE;
      }

      // Return true as validation was successful and submission is now ready
      // for processing.
      return TRUE;
    }
    catch (EntityStorageException $e) {

      if ($response instanceof AjaxResponse) {
        $message = $this->t('An error occurred when saving the submission after validation, please try again.');
        $response->addCommand(new InvokeCommand(
          'form.custom-forms[data-custom-form-id="' . $submission->getForm()->id() . '"]',
          'prepend',
          [$this->renderCheck($message)]
        ));
      }

      $error_message = $this->t('An error occurred when saving the submission after validation. Message: @message', ['@message' => $e->getMessage()]);
      $this->log->error($error_message);

      return FALSE;
    }
  }

  /**
   * Process the submitted values by their item plugins.
   *
   * @param \Drupal\custom_forms\Entity\CustomFormSubmission $submission
   *   The submission that was created.
   * @param \Drupal\custom_forms\CustomFormItem[] $items
   *   The items used for processing.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param \Drupal\Core\Ajax\AjaxResponse|NULL $response
   *   (Optional) The response to return. Only used for ajax requests.
   *
   * @return bool
   *   Returns TRUE if processing succeeds, otherwise returns FALSE.
   */
  private function processValues(CustomFormSubmission $submission, array $items, FormStateInterface $form_state, AjaxResponse $response = NULL): bool {
    // Trigger the pre-transition
    $submission->getState()->applyTransitionById('process');
    $values = $submission->getData();

    // Get any errors during pre-transition.
    $errors = $submission->getErrors();

    // Catch any errors.
    $status = $this->handleErrors($submission->getForm(), $errors, $items, $form_state, $response);
    if (!$status) {
      // We only return FALSE if the display error check returned false.
      return FALSE;
    }

    // Enable finalization step.
    // This is primarily used so that other plugins can prevent the default
    // finalization from happening if they want to do that themselves.
    $values['_finalize'] = TRUE;

    /** @var \Drupal\custom_forms\CustomFormItem $item */
    foreach ($items as $item) {
      try {
        $item->getPlugin()->process($values, $form_state, $item);

        $child_items = $item->getChildren();
        foreach ($child_items as $child_item) {
          $child_item->getPlugin()->process($values, $form_state, $item);
        }
      }
      catch (PluginException $e) {
        $error_message = t('PluginException during field processing. Message = %message', [
          '%message' => $e->getMessage(),
        ]);
        $this->log->error($error_message);
        return FALSE;
      }
    }

    // Only save the submission if processing was successful.
    try {
      // Clear any errors since processing was successful, to avoid confusion
      // when troubleshooting.
      $submission->setErrors([]);

      // Set the processed values
      $submission->setDataArray($values);

      // Save the submission, this also triggers post-transition.
      $submission->save();

      // Get any errors during post-transition.
      $errors = $submission->getErrors();

      // Catch any errors.
      $status = $this->handleErrors($submission->getForm(), $errors, $items, $form_state, $response);
      if (!$status) {
        // We only return FALSE if the display error check returned false.
        return FALSE;
      }

      // Return true as processing was successful and submission is now ready
      // for initialisation.
      return TRUE;
    }
    catch (EntityStorageException $e) {

      if ($response instanceof AjaxResponse) {
        $message = $this->t('An error occurred when saving the submission after processing, please try again.');
        $response->addCommand(new InvokeCommand(
          'form.custom-forms[data-custom-form-id="' . $submission->getForm()->id() . '"]',
          'prepend',
          [$this->renderCheck($message)]
        ));
      }

      $error_message = $this->t('An error occurred when saving the submission after processing. Message: @message', ['@message' => $e->getMessage()]);
      $this->log->error($error_message);

      return FALSE;
    }
  }

  /**
   * Initialize the submission.
   *
   * @param \Drupal\custom_forms\Entity\CustomFormSubmission $submission
   * @param \Drupal\custom_forms\CustomFormItem[] $items
   *   The items used for initialization.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param \Drupal\Core\Ajax\AjaxResponse|NULL $response
   *   (Optional) The response to return. Only used for ajax requests.
   *
   * @return bool
   */
  private function initializeSubmission(CustomFormSubmission $submission, array $items, FormStateInterface $form_state, AjaxResponse $response = NULL): bool {
    // Trigger the pre-transition
    $submission->getState()->applyTransitionById('initialize');
    $values = $submission->getData();
    $errors = $submission->getErrors();

    // Catch any errors.
    $status = $this->handleErrors($submission->getForm(), $errors, $items, $form_state, $response);
    if (!$status) {
      // We only return FALSE if the display error check returned false.
      return FALSE;
    }

    // Store a unique value in the private temp store, so that we ensure that
    // only the user who submitted the form can view the receipt.
    try {
      $unique_token = $this->generateToken(64);
      /** @var \Drupal\Core\TempStore\PrivateTempStore $tempstore */
      $tempstore = $this->sessionService->getTempStore('private', 'custom_form_submission_'.$submission->id());
      if ($tempstore) {
        $tempstore->set('submission_token', $unique_token);
        $values['_submission_token'] = $unique_token;
      }
    } catch (\Throwable $t) {
      $error_message = $this->t('An error occurred when handling submission token. Message: @message', ['@message' => $t->getMessage()]);
      $this->log->error($error_message);
    }

    // Set redirect if it's enabled and a receipt page has been set.
    if (!empty($values['_receipt_page_id'])) {
      // Attempt to load the receipt page node.
      $receipt_page = Node::load($values['_receipt_page_id']);
      // Only proceed in setting the receipt redirect url if the node was
      // properly loaded.
      if ($receipt_page !== NULL) {
        try {
          $url = $receipt_page->toUrl('canonical', ['query' => ['S' => $submission->getUniqueId()]]);
          $url->setAbsolute();
          $values['_redirect_url'] = $url->toString();
          $submission->setDataArray($values);
        } catch (EntityMalformedException $e) {
          if ($response instanceof AjaxResponse) {
            $message = $this->t('An error occurred when saving the submission during initialization, please try again.');
            $response->addCommand(new InvokeCommand(
              'form.custom-forms[data-custom-form-id="' . $submission->getForm()->id() . '"]',
              'prepend',
              [$this->renderCheck($message)]
            ));
          }

          $error_message = $this->t('An error occurred when saving the submission during initialization. Message: @message', ['@message' => $e->getMessage()]);
          $this->log->error($error_message);
        }
      }
    }

    // Only save the submission if initialization was successful.
    try {
      // Clear any errors since initialization was successful, to avoid confusion
      // when troubleshooting.
      $submission->setErrors([]);

      // Save the submission, this also triggers post-transition.
      $submission->save();

      // Get any errors during post-transition.
      $errors = $submission->getErrors();

      // Catch any errors.
      $status = $this->handleErrors($submission->getForm(), $errors, $items, $form_state, $response);
      if (!$status) {
        // We only return FALSE if the display error check returned false.
        return FALSE;
      }
    }
    catch (EntityStorageException $e) {
      if ($response instanceof AjaxResponse) {
        $message = $this->t('An error occurred when saving the submission after initialization, please try again.');
        $response->addCommand(new InvokeCommand(
          'form.custom-forms[data-custom-form-id="' . $submission->getForm()->id() . '"]',
          'prepend',
          [$this->renderCheck($message)]
        ));
      }

      $error_message = $this->t('An error occurred when saving the submission after initialization. Message: @message', ['@message' => $e->getMessage()]);
      $this->log->error($error_message);

      return FALSE;
    }

    // Get the updated values
    $values = $submission->getData();

    // If a redirect URL has been defined, use it.
    if (!empty($values['_redirect_url'])) {
      if ($response instanceof AjaxResponse) {
        $response->addCommand(new RedirectCommand($values['_redirect_url']));
      }
      else {
        $form_state->setRedirectUrl($values['_redirect_url']);
      }
    }

    // We only want to trigger the finalize event if it has been enabled.
    if ((bool) $values['_finalize']) {
      // Trigger the pre-transition
      $submission->getState()->applyTransitionById('finalize');

      // Get any errors during pre-transition.
      $errors = $submission->getErrors();

      // Catch any errors.
      $status = $this->handleErrors($submission->getForm(), $errors, $items, $form_state, $response);
      if (!$status) {
        // We only return FALSE if the display error check returned false.
        return FALSE;
      }

      try {
        // Clear any errors since finalization was successful, to avoid confusion
        // when troubleshooting.
        $submission->setErrors([]);

        // Save the submission, this also triggers post-transition.
        $submission->save();

        // Get any errors during post-transition.
        $errors = $submission->getErrors();

        // Catch any errors.
        $status = $this->handleErrors($submission->getForm(), $errors, $items, $form_state, $response);
        if (!$status) {
          // We only return FALSE if the display error check returned false.
          return FALSE;
        }
      }
      catch (EntityStorageException $e) {
        $message = $this->t('An error occurred when saving the submission after finalization, please contact an administrator.');
        $this->messenger()->addError($message);
        $error_message = $this->t('An error occurred when saving the submission after finalization. Message: @message', ['@message' => $e->getMessage()]);
        $this->log->error($error_message);

        return FALSE;
      }
    }

    return TRUE;
  }

  /**
   * Handle any errors from the form.
   *
   * @param \Drupal\custom_forms\Entity\CustomForm $custom_form
   * @param array $errors
   * @param \Drupal\custom_forms\CustomFormItem[] $items
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   * @param \Drupal\Core\Ajax\AjaxResponse $response
   *
   * @return bool
   *   Returns TRUE if no errors needed to be handled, otherwise FALSE.
   */
  private function handleErrors(CustomFormEntity $custom_form, array $errors, array $items, FormStateInterface $form_state, AjaxResponse $response = NULL): bool {
    if (!empty($errors)) {

      // First check for log error, log them and remove them from the
      // errors array.
      if (!empty($errors['_log'])) {
        foreach ($errors['_log'] as $type => $type_errors) {
          foreach ($type_errors as $error_message) {
            switch ($type) {
              case 'error':
                $this->log->error($error_message);
                break;
              case 'warning':
                $this->log->warning($error_message);
                break;
              case 'critical':
                $this->log->critical($error_message);
                break;
              case 'emergency':
                $this->log->emergency($error_message);
                break;
              case 'alert':
                $this->log->alert($error_message);
                break;
              case 'notice':
                $this->log->notice($error_message);
                break;
              case 'debug':
                $this->log->debug($error_message);
                break;
              case 'info':
              default:
                $this->log->info($error_message);
                break;
            }
          }
        }
      }
      unset($errors['_log']);

      if ($response !== NULL) {
        // Get all error messages so they don't get rendered, as we render
        // them differently.
        StatusMessages::renderMessages('error');
        // Remove the current messages to avoid showing any messages double.
        $response->addCommand(new InvokeCommand(
          'form.custom-forms[data-custom-form-id="' . $custom_form->id() . '"] > [data-drupal-messages]',
          'remove'
        ));

        // Append the new message to the form.
        $message = [
          '#theme' => 'status_messages',
          '#message_list' => [
            'error' => [
              $this->t('There were some errors with your submission, please check the form and try again.'),
            ],
          ],
          '#status_headings' => [
            'status' => t('Status message'),
            'error' => t('Error message'),
            'warning' => t('Warning message'),
          ],
        ];

        $response->addCommand(new InvokeCommand(
          'form.custom-forms[data-custom-form-id="' . $custom_form->id() . '"]',
          'prepend',
          [$this->renderCheck($message)]
        ));
      }

      // Display inline errors.
      $this->generateInlineErrors($custom_form, $errors, $items, $form_state, $response);

      return FALSE;
    }

    return TRUE;
  }

  /**
   * Generate the inline errors for the items.
   *
   * @param \Drupal\custom_forms\Entity\CustomForm $custom_form
   * @param array $errors
   * @param array $items
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   * @param \Drupal\Core\Ajax\AjaxResponse|NULL $response
   */
  public function generateInlineErrors(CustomFormEntity $custom_form, array $errors, array $items, FormStateInterface $form_state, AjaxResponse $response = NULL): void {
    /** @var \Drupal\custom_forms\CustomFormItem $item */
    foreach ($items as $item) {
      // Remove any existing error messages.
      if ($response !== NULL) {
        $response->addCommand(new CustomFormsErrorCommand('removeCustomFormsError', 'form.custom-forms[data-custom-form-id="' . $custom_form->id() . '"] [name="' . $item->getJquerySelector() . '"]'));
      }

      $children = $item->getChildren();
      // If the item has no children and no errors, we skip it.
      if (empty($children) && empty($errors[$item->getMachineName()])) {
        continue;
      }

      $error_messages = [];

      if (isset($errors[$item->getMachineName()])) {
        /**
         * @var string $error_type
         * @var \Drupal\Core\StringTranslation\TranslatableMarkup $error_message
         */
        foreach ($errors[$item->getMachineName()] as $error_type => $error_message) {
          $error_messages[] = $error_message;
        }
      }

      if (!empty($error_messages)) {
        if (count($error_messages) > 1) {
          $display_errors = [
            '#theme' => 'item_list',
            '#list_type' => 'ul',
            '#items' => $error_messages,
          ];
        }
        else {
          $display_errors = reset($error_messages);
        }
        if ($response !== NULL) {
          $response->addCommand(new CustomFormsErrorCommand(
            'addCustomFormsError',
            'form.custom-forms[data-custom-form-id="' . $custom_form->id() . '"] [name="' . $item->getJquerySelector() . '"]',
            $this->renderCheck($display_errors)
          ));
        }
        else {
          $form_state->setErrorByName($item->getMachineName(), $display_errors);
        }
      }

      // Also generate errors for children.
      if ($item->getType() === 'group' && count($children) > 0) {
        $this->generateInlineErrors($custom_form, $errors, $children, $form_state, $response);
      }
    }
  }

  /**
   * Generates a unique token.
   *
   * @param int $length
   *   The maximum length of the token string.
   *
   * @return string
   *   Returns a unique token.
   *
   * @throws \Exception
   *   If it was not possible to gather sufficient entropy.
   */
  private function generateToken($length) {
    $token = '';
    $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $codeAlphabet.= "abcdefghijklmnopqrstuvwxyz";
    $codeAlphabet.= "0123456789";
    $max = strlen($codeAlphabet); // edited

    for ($i=0; $i < $length; $i++) {
      $token .= $codeAlphabet[random_int(0, $max-1)];
    }

    return $token;
  }

  /**
   * Generate the form url based on the request.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request to generate the URL from.
   *
   * @return string
   *   Returns the form url as a string.
   */
  private function generateFormUrl(Request $request) : string {
    // Generate URL from quest.
    $current_url = Url::createFromRequest($request);
    // Set it to absolute so we get the full URL.
    $current_url->setAbsolute(TRUE);
    // Get the query parameters from the request so we can add them to the URL.
    $query_parameters = $request->query->all();

    $skipped_parameters = [
      FormBuilderInterface::AJAX_FORM_REQUEST,
      MainContentViewSubscriber::WRAPPER_FORMAT,
      AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER,
    ];
    // Run through all query parameters and remove any ajax-related queries.
    foreach ($query_parameters as $key => $value) {
      if (!in_array($key, $skipped_parameters, FALSE)) {
        $current_url->setRouteParameter($key, $value);
      }
    }

    // return the form URL.
    return $current_url->toString();
  }

  protected function renderCheck(&$element) {
    if (!$element && $element !== 0) {
      return NULL;
    }
    if (is_array($element)) {
      // Early return if this element was pre-rendered (no need to re-render).
      if (isset($element['#printed']) && $element['#printed'] == TRUE && isset($element['#markup']) && strlen($element['#markup']) > 0) {
        return $element['#markup'];
      }
      show($element);
      return \Drupal::service('renderer')->renderPlain($element);
    }
    else {
      // Safe-guard for inappropriate use of render() on flat variables: return
      // the variable as-is.
      return $element;
    }
  }

}