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 / Controller / SubmissionValidationController.php
Size: Mime:
<?php

namespace Drupal\custom_forms\Controller;

use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\custom_forms\Entity\CustomFormSubmission;
use NumberFormatter;
use Symfony\Component\DependencyInjection\ContainerInterface;

class SubmissionValidationController extends ControllerBase {

  protected $logger;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('logger.factory')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function __construct(LoggerChannelFactoryInterface $loggerChannelFactory) {
    $this->logger = $loggerChannelFactory->get('Custom Forms');
  }

  /**
   * Validate the submitted values based on the specified items.
   *
   * @param \Drupal\custom_forms\Entity\CustomFormSubmission $submission
   *   The submission containing the values to validate.
   * @param \Drupal\custom_forms\CustomFormItem[] $items
   *   The array of form items (fields and groups) to validate.
   * @param \NumberFormatter $numberFormatter
   *   The number formatter used for number comparison checks.
   * @param array $errors
   *   An array of errors to allow for continued addition of errors when the
   *   function is called recursively.
   *
   * @return bool
   *   Returns TRUE if the items validate successfully, otherwise FALSE.
   */
  public function validateItems(CustomFormSubmission $submission, array $items, NumberFormatter $numberFormatter, array &$errors = []) : bool {
    $result = TRUE;
    $values = $submission->getData();
    if (empty($errors)) {
      $errors = $submission->getErrors();
    }

    /** @var \Drupal\custom_forms\CustomFormItem $item */
    foreach ($items as $item) {
      // If the field is always hidden, we do not want to validate it.
      if ($item->getSetting('visibility') === 'hidden') {
        continue;
      }

      /** @var \Drupal\custom_forms\CustomFormItem[] $children */
      $children = $item->getChildren();
      $machine_name = $item->getMachineName();
      $plugin = $item->getPlugin();

      // Add error message if field is fully required.
      $is_required = FALSE;
      if ($plugin->isEmpty($values, $item) && (bool) $item->getSetting('required') === TRUE) {
        $is_required = TRUE;
        $errors[$machine_name]['required'] = $this->t('@field-name is required.', ['@field-name' => $item->getSetting('label')]);
      }

      $states = $item->getStates();
      foreach ($states as $state) {
        switch ($state['state']) {
          case 'required':
            // We only need to check for required if it isn't already marked
            // as such.
            if (!$is_required) {
              $is_required = $this->checkConditions($values, $state['condition'], $state['operator'], $numberFormatter);
              if ($is_required) {
                if (!$item->isEmpty($values)) {
                  unset($errors[$machine_name]['required']);
                }
                else {
                  $errors[$machine_name]['required'] = $this->t('@field-name is required.', ['@field-name' => $item->getSetting('label')]);
                }
              }
            }
            break;

          case 'optional':
            $is_optional = $this->checkConditions($values, $state['condition'], $state['operator'], $numberFormatter);
            if (!$is_optional) {
              if (!empty($values[$machine_name])) {
                unset($errors[$machine_name]['required']);
              }
              else {
                $errors[$machine_name]['required'] = $this->t('@field-name is required.', ['@field-name' => $item->getSetting('label')]);
              }
            }
            break;

          case 'visible':
            $is_visible = $this->checkConditions($values, $state['condition'], $state['operator'], $numberFormatter);
            // If the item is not visible, don't show required validation
            // errors.
            if (!$is_visible) {
              unset($errors[$machine_name]['required']);
            }
            break;

          case 'hidden':
            $is_hidden = $this->checkConditions($values, $state['condition'], $state['operator'], $numberFormatter);

            // If the item is hidden, don't show required validation errors.
            if ($is_hidden) {
              unset($errors[$machine_name]['required']);
            }
            break;
        }
      }

      // If the item has children, validate them as well.
      if (!empty($children)) {
        $result = $this->validateItems($submission, $children, $numberFormatter, $errors);
      }

      // If no errors at this point, call the item's own validate functions.
      if ($item->getType() === 'field') {
        try {
          $item->getPlugin()->validate($item, $values, $errors);
        }
        catch (PluginException $e) {
          $error_message = t('PluginException during field validation. Message = %message', [
            '%message' => $e->getMessage(),
          ]);
          $this->logger->error($error_message);
          return FALSE;
        }
      }
    }

    $submission->setErrors($errors);

    return $result;
  }

  /**
   * Check if all conditions are met.
   *
   * @param array $values
   *   An array containing all submitted values to validate conditions against.
   * @param array $conditions
   *   An array of all the conditions for the state.
   * @param string $operator
   *   The operator that the conditions use.
   * @param \NumberFormatter $numberFormatter
   *   The number formatter used for number comparison checks.
   *
   * @return bool
   *   Returns TRUE if all conditions are met.
   */
  private function checkConditions(array $values, array $conditions, $operator, NumberFormatter $numberFormatter) : bool {
    $valid_conditions = 0;
    foreach ($conditions as $condition) {
      // We cannot validate expanded or collapsed items, so we skip those
      // triggers.
      if (in_array($condition['trigger'], ['expanded', 'collapsed'])) {
        $valid_conditions++;
        continue;
      }

      switch ($condition['trigger']) {
        case 'empty':
          if (empty($values[$condition['selector']])) {
            $valid_conditions++;
          }
          break;

        case 'filled':
          if (!empty($values[$condition['selector']])) {
            $valid_conditions++;
          }
          break;

        case 'checked':
          if (
            isset($values[$condition['selector']]) &&
            (bool) $values[$condition['selector']] === TRUE
          ) {
            $valid_conditions++;
          }
          break;

        case 'unchecked':
          if (
            (
              isset($values[$condition['selector']]) &&
              (bool) $values[$condition['selector']] === FALSE
            ) ||
            empty($values[$condition['selector']])
          ) {
            $valid_conditions++;
          }
          break;

        case 'value':
          if (
            isset($values[$condition['selector']]) &&
            $values[$condition['selector']] === $condition['value']
          ) {
            $valid_conditions++;
          }
          break;

        case '!value':
          if (
            !isset($values[$condition['selector']]) ||
            (
              isset($values[$condition['selector']]) &&
              $values[$condition['selector']] !== $condition['value']
            )
          ) {
            $valid_conditions++;
          }
          break;

        case 'pattern':
          if (isset($values[$condition['selector']])) {
            $match = preg_match($condition['value'], $values[$condition['selector']]);

            if (!empty($match)) {
              $valid_conditions++;
            }
          }
          break;

        case '!pattern':
          if (isset($values[$condition['selector']])) {
            $match = preg_match($condition['value'], $values[$condition['selector']]);

            if (empty($match)) {
              $valid_conditions++;
            }
          }
          break;

        case 'less':
          if (isset($values[$condition['selector']])) {
            $field_value = $numberFormatter->parse($values[$condition['selector']]);
            $condition_value = $numberFormatter->parse($values[$condition['value']]);
            if (
              $field_value !== FALSE &&
              $condition_value !== FALSE &&
              $field_value < $condition_value
            ) {
              $valid_conditions++;
            }
          }
          break;

        case 'greater':
          if (isset($values[$condition['selector']])) {
            $field_value = $numberFormatter->parse($values[$condition['selector']]);
            $condition_value = $numberFormatter->parse($values[$condition['value']]);
            if (
              $field_value !== FALSE &&
              $condition_value !== FALSE &&
              $field_value > $condition_value
            ) {
              $valid_conditions++;
            }
          }
          break;
      }
    }

    // Depending on the operator we need to validate it a bit differently.
    switch ($operator) {
      case 'or':
        // If using the OR operator, we just need more than 1 valid condition.
        if ($valid_conditions > 0) {
          return TRUE;
        }
        break;

      case 'xor':
        // If using the XOR operator, we only accept 1 valid condition,
        // no more, no less.
        if ($valid_conditions === 1) {
          return TRUE;
        }
        break;

      case 'and':
      default:
        // If using the AND operator, the valid conditions must match the
        // total conditions.
        if ($valid_conditions === count($conditions)) {
          return TRUE;
        }
        break;
    }

    // If we get this far, we know the condition check failed and we can return
    // FALSE to indicate as such.
    return FALSE;
  }

}