custom/plugins/DreiscSeoPro/src/Core/Foundation/Dal/Validator.php line 64

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace DreiscSeoPro\Core\Foundation\Dal;
  3. use Doctrine\DBAL\Connection;
  4. use Doctrine\DBAL\DBALException;
  5. use Doctrine\DBAL\FetchMode;
  6. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\InsertCommand;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\UpdateCommand;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\WriteCommand;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
  10. use Shopware\Core\Framework\Uuid\Exception\InvalidUuidException;
  11. use Shopware\Core\Framework\Uuid\Exception\InvalidUuidLengthException;
  12. use Shopware\Core\Framework\Uuid\Uuid;
  13. use Shopware\Core\Framework\Validation\WriteConstraintViolationException;
  14. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  15. use Symfony\Component\Validator\ConstraintViolation;
  16. use Symfony\Component\Validator\ConstraintViolationInterface;
  17. use Symfony\Component\Validator\ConstraintViolationList;
  18. use function strtolower;
  19. abstract class Validator implements EventSubscriberInterface
  20. {
  21.     public const VIOLATION_FIELD_SHOULD_NOT_BE_BLANK 'field_should_not_be_blank';
  22.     public const VIOLATION_INVALID_VALUE_FOR_THE_FIELD 'invalid_value_for_the_field';
  23.     public const VIOLATION_FIELD_HAS_SPECIAL_VALUE_AND_OTHER_FIELDS_ARE_REQUIRED 'field_has_special_value_and_other_fields_are_required';
  24.     /**
  25.      * @var Connection
  26.      */
  27.     protected $connection;
  28.     /**
  29.      * @var string|null
  30.      */
  31.     protected $currentId;
  32.     /**
  33.      * @var array|null
  34.      */
  35.     protected $payload;
  36.     /**
  37.      * @var ConstraintViolationList
  38.      */
  39.     protected $violations;
  40.     abstract protected function getDefinitionClass(): string;
  41.     abstract protected function getEntityName(): string;
  42.     abstract protected function fetchViolations(WriteCommand $command): void;
  43.     public function __construct(Connection $connection)
  44.     {
  45.         $this->connection $connection;
  46.     }
  47.     public static function getSubscribedEvents(): array
  48.     {
  49.         return [
  50.             PreWriteValidationEvent::class => 'preValidate'
  51.         ];
  52.     }
  53.     public function preValidate(PreWriteValidationEvent $event): void
  54.     {
  55.         $commands $event->getCommands();
  56.         $this->violations = new ConstraintViolationList();
  57.         foreach ($commands as $command) {
  58.             /** Abort if its not a insert or update */
  59.             if (!$command instanceof InsertCommand && !$command instanceof UpdateCommand) {
  60.                 continue;
  61.             }
  62.             /** Abort if not a transaction of the current entity */
  63.             if ($command->getDefinition()->getClass() !== $this->getDefinitionClass()) {
  64.                 continue;
  65.             }
  66.             /** Params of the insert/update */
  67.             $this->payload $command->getPayload();
  68.             /** Fetch the id of the entity */
  69.             $primaryKeys $command->getPrimaryKey();
  70.             $this->currentId strtolower(Uuid::fromBytesToHex($primaryKeys['id']));
  71.             /**
  72.              * Load the existing data, if this an update and merge this
  73.              * data with the new one. After this we are able to check the
  74.              * complete data set
  75.              */
  76.             if($command instanceof UpdateCommand && !empty($primaryKeys['id'])) {
  77.                 $statement $this->connection->executeQuery('
  78.                     SELECT *
  79.                     FROM ' $this->getEntityName() . '
  80.                     WHERE `id` = :id
  81.                 ', [
  82.                         'id' => $primaryKeys['id']
  83.                     ]
  84.                 );
  85.                 $existingData $statement->fetch(FetchMode::ASSOCIATIVE);
  86.                 /** Merge the payload in the existing data array */
  87.                 foreach($this->payload as $key => $value) {
  88.                     $existingData[$key] = $value;
  89.                 }
  90.                 $this->payload $existingData;
  91.             }
  92.             /** Fetch the violations */
  93.             $this->fetchViolations($command);
  94.         }
  95.         if ($this->violations->count() > 0) {
  96.             $event->getExceptions()->add(new WriteConstraintViolationException($this->violations));
  97.         }
  98.     }
  99.     /**
  100.      * Checks if a field is empty
  101.      * @param string $storageName
  102.      */
  103.     protected function violationIfEmpty(string $storageName): void
  104.     {
  105.         if (empty($this->payload[$storageName])) {
  106.             $this->violations->add(
  107.                 $this->buildViolation(
  108.                     'The field "{{ field }}" should not be blank',
  109.                     [
  110.                         '{{ field }}' => $storageName
  111.                     ],
  112.                     null,
  113.                     '/' $this->currentId,
  114.                     '',
  115.                     self::VIOLATION_FIELD_SHOULD_NOT_BE_BLANK
  116.                 )
  117.             );
  118.         }
  119.     }
  120.     /**
  121.      * Add a violation if the value of the given field is not on the whitelist
  122.      *
  123.      * @param string $storageName
  124.      * @param array $whitelist
  125.      */
  126.     protected function violationIfValueNotOnWhitelist(string $storageName, array $whitelist): void
  127.     {
  128.         if (!in_array($this->payload[$storageName], $whitelisttrue)) {
  129.             $this->violations->add(
  130.                 $this->buildViolation(
  131.                     'Invalid value for the field "{{ field }}". Possible values are: ' implode(', '$whitelist),
  132.                     [
  133.                         '{{ field }}' => $storageName
  134.                     ],
  135.                     null,
  136.                     '/' $this->currentId,
  137.                     $this->payload[$storageName],
  138.                     self::VIOLATION_INVALID_VALUE_FOR_THE_FIELD
  139.                 )
  140.             );
  141.         }
  142.     }
  143.     /**
  144.      * Add a violation if the $field has the $value and one of the fields in the list ($notEmptyFields) is empty
  145.      *
  146.      * @param string $field
  147.      * @param mixed $value
  148.      * @param array $notEmptyFields
  149.      */
  150.     protected function violationIfFieldHasSpecialValueAndOtherFieldsAreEmpty(string $field$value, array $notEmptyFields): void
  151.     {
  152.         /** Abort, if the field has not the given value */
  153.         if(empty($this->payload[$field]) || $this->payload[$field] !== $value) {
  154.             return;
  155.         }
  156.         $invalidFieldNames = [];
  157.         foreach($notEmptyFields as $notEmptyField) {
  158.             if (empty($this->payload[$notEmptyField])) {
  159.                 $invalidFieldNames[] = $notEmptyField;
  160.             }
  161.         }
  162.         /** Abort, if no invalid field was found */
  163.         if(empty($invalidFieldNames)) {
  164.             return;
  165.         }
  166.         $this->violations->add(
  167.             $this->buildViolation(
  168.                 'The value of the field "{{ field }}" is "{{ value }}". In this case the following fields should not be blank: ' implode(', '$invalidFieldNames),
  169.                 [
  170.                     '{{ field }}' => $field,
  171.                     '{{ value }}' => $value
  172.                 ],
  173.                 null,
  174.                 '/' $this->currentId,
  175.                 '',
  176.                 self::VIOLATION_FIELD_HAS_SPECIAL_VALUE_AND_OTHER_FIELDS_ARE_REQUIRED
  177.             )
  178.         );
  179.     }
  180.     /**
  181.      * Unique-check
  182.      *
  183.      * Add a violation if the is already an entity where the database fields with the
  184.      * given storage names have the same values
  185.      *
  186.      * @param WriteCommand $command
  187.      * @param array $storageNames
  188.      * @throws DBALException
  189.      * @throws InvalidUuidException
  190.      * @throws InvalidUuidLengthException
  191.      */
  192.     protected function violationIfThereIsAlreadyAnEntityWithTheSameValues(WriteCommand $command, array $storageNames): void
  193.     {
  194.         $payload $command->getPayload();
  195.         $conditions = [];
  196.         $parameters = [];
  197.         foreach($storageNames as $storageName) {
  198.             if (empty($payload[$storageName])) {
  199.                 /** Field was not set in the payload, so we check for empty or null */
  200.                 $conditions[] = sprintf(
  201.                     '(`%1$s` = \'\' OR `%1$s` IS NULL)',
  202.                     $storageName
  203.                 );
  204.             } else {
  205.                 /** Field was set, so we check for given value */
  206.                 $parameters[$storageName] = $payload[$storageName];
  207.                 $conditions[] = sprintf(
  208.                     '`%1$s` = :%1$s',
  209.                     $storageName
  210.                 );
  211.             }
  212.         }
  213.         /** Exclude the own entity, if it's an update */
  214.         $primaryKeys $command->getPrimaryKey();
  215.         if($command instanceof UpdateCommand && is_array($primaryKeys)) {
  216.             foreach($primaryKeys as $primaryKey => $primaryKeyValue) {
  217.                 $parameters[$primaryKey] = $primaryKeyValue;
  218.                 $conditions[] = sprintf(
  219.                     '`%1$s` != :%1$s',
  220.                     $primaryKey
  221.                 );
  222.             }
  223.         }
  224.         /** Check if we find a duplicate */
  225.         $statement $this->connection->executeQuery('
  226.             SELECT *
  227.             FROM ' $this->getEntityName() . '
  228.             WHERE ' implode(' AND '$conditions) . '
  229.         '$parameters);
  230.         $existingData $statement->fetch(FetchMode::ASSOCIATIVE);
  231.         if($existingData !== false) {
  232.             $this->violations->add(
  233.                 $this->buildViolation(
  234.                     'There is already an entity with the unique field values for "{{ fields }}" ยป see {{ entityName }}.id: {{ id }}',
  235.                     [
  236.                         '{{ fields }}' => implode(', '$storageNames),
  237.                         '{{ entityName }}' => $this->getEntityName(),
  238.                         '{{ id }}' => !empty($payload['id']) ? Uuid::fromBytesToHex($payload['id']) : '[missing id field]'
  239.                     ],
  240.                     null,
  241.                     '/' $this->currentId,
  242.                     '',
  243.                     self::VIOLATION_FIELD_SHOULD_NOT_BE_BLANK
  244.                 )
  245.             );
  246.         }
  247.     }
  248.     /**
  249.      * @param string $messageTemplate
  250.      * @param array $parameters
  251.      * @param null $root
  252.      * @param string|null $propertyPath
  253.      * @param null $invalidValue
  254.      * @param null $code
  255.      * @return ConstraintViolationInterface
  256.      */
  257.     protected function buildViolation(
  258.         string $messageTemplate,
  259.         array $parameters,
  260.         $root null,
  261.         ?string $propertyPath null,
  262.         $invalidValue null,
  263.         $code null
  264.     ): ConstraintViolationInterface {
  265.         return new ConstraintViolation(
  266.             $this->strReplace(array_keys($parameters), array_values($parameters), $messageTemplate),
  267.             $messageTemplate,
  268.             $parameters,
  269.             $root,
  270.             $propertyPath,
  271.             $invalidValue,
  272.             $plural null,
  273.             $code,
  274.             $constraint null,
  275.             $cause null
  276.         );
  277.     }
  278.     /**
  279.      * PHP 8.0 Support
  280.      */
  281.     public function strReplace($search$replace$subject)
  282.     {
  283.         if(empty($search) || empty($replace) || empty($subject)) {
  284.             return $subject;
  285.         }
  286.         return str_replace($search$replace$subject);
  287.     }
  288. }