diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 17b626ff..c7fef7e6 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -292,7 +292,7 @@ parameters: - message: "#^Cannot access offset mixed on Ibexa\\\\Contracts\\\\Rest\\\\Input\\\\Parser\\.$#" - count: 2 + count: 1 path: src/contracts/Input/ParsingDispatcher.php - @@ -740,16 +740,6 @@ parameters: count: 1 path: src/lib/Input/Parser/ContentObjectStates.php - - - message: "#^Instantiated class Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\User\\\\Limitation\\\\SiteaccessLimitation not found\\.$#" - count: 1 - path: src/lib/Input/ParserTools.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Input\\\\ParserTools\\:\\:getLimitationByIdentifier\\(\\) should return Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\User\\\\Limitation but returns Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\User\\\\Limitation\\\\SiteaccessLimitation\\.$#" - count: 1 - path: src/lib/Input/ParserTools.php - - message: "#^Method Ibexa\\\\Rest\\\\Input\\\\ParserTools\\:\\:isEmbeddedObject\\(\\) has parameter \\$objectElement with no value type specified in iterable type array\\.$#" count: 1 diff --git a/src/bundle/Resources/config/input_parsers.yml b/src/bundle/Resources/config/input_parsers.yml index 379a707b..cbd1bc93 100644 --- a/src/bundle/Resources/config/input_parsers.yml +++ b/src/bundle/Resources/config/input_parsers.yml @@ -814,3 +814,38 @@ services: arguments: - 'sectionId' - 'Ibexa\Contracts\Core\Repository\Values\User\Limitation\SectionLimitation' + + Ibexa\Rest\Server\Input\Parser\Criterion\ImageMimeType: + parent: Ibexa\Rest\Server\Common\Parser + arguments: + $validator: '@validator' + tags: + - { name: ibexa.rest.input.parser, mediaType: application/vnd.ibexa.api.internal.criterion.ImageMimeType } + + Ibexa\Rest\Server\Input\Parser\Criterion\ImageFileSize: + parent: Ibexa\Rest\Server\Common\Parser + arguments: + $validator: '@validator' + tags: + - { name: ibexa.rest.input.parser, mediaType: application/vnd.ibexa.api.internal.criterion.ImageFileSize } + + Ibexa\Rest\Server\Input\Parser\Criterion\ImageOrientation: + parent: Ibexa\Rest\Server\Common\Parser + arguments: + $validator: '@validator' + tags: + - { name: ibexa.rest.input.parser, mediaType: application/vnd.ibexa.api.internal.criterion.ImageOrientation } + + Ibexa\Rest\Server\Input\Parser\Criterion\ImageDimensions: + parent: Ibexa\Rest\Server\Common\Parser + arguments: + $validator: '@validator' + tags: + - { name: ibexa.rest.input.parser, mediaType: application/vnd.ibexa.api.internal.criterion.ImageDimensions } + + Ibexa\Rest\Server\Input\Parser\Criterion\Image: + parent: Ibexa\Rest\Server\Common\Parser + arguments: + $validator: '@validator' + tags: + - { name: ibexa.rest.input.parser, mediaType: application/vnd.ibexa.api.internal.criterion.Image } diff --git a/src/lib/Input/ParserTools.php b/src/lib/Input/ParserTools.php index edca18d1..3c92bbb4 100644 --- a/src/lib/Input/ParserTools.php +++ b/src/lib/Input/ParserTools.php @@ -17,7 +17,7 @@ use Ibexa\Contracts\Core\Repository\Values\User\Limitation\ParentOwnerLimitation; use Ibexa\Contracts\Core\Repository\Values\User\Limitation\ParentUserGroupLimitation; use Ibexa\Contracts\Core\Repository\Values\User\Limitation\SectionLimitation; -use Ibexa\Contracts\Core\Repository\Values\User\Limitation\SiteaccessLimitation; +use Ibexa\Contracts\Core\Repository\Values\User\Limitation\SiteAccessLimitation; use Ibexa\Contracts\Core\Repository\Values\User\Limitation\SubtreeLimitation; use Ibexa\Contracts\Core\Repository\Values\User\Limitation\UserGroupLimitation; use Ibexa\Contracts\Rest\Exceptions; diff --git a/src/lib/Server/Exceptions/ValidationFailedException.php b/src/lib/Server/Exceptions/ValidationFailedException.php new file mode 100644 index 00000000..85561661 --- /dev/null +++ b/src/lib/Server/Exceptions/ValidationFailedException.php @@ -0,0 +1,32 @@ +errors = $errors; + + parent::__construct("Input data validation failed for $contextName", 1, $previous); + } + + public function getErrors(): ConstraintViolationListInterface + { + return $this->errors; + } +} diff --git a/src/lib/Server/Input/Parser/Criterion/Image.php b/src/lib/Server/Input/Parser/Criterion/Image.php new file mode 100644 index 00000000..8572a15c --- /dev/null +++ b/src/lib/Server/Input/Parser/Criterion/Image.php @@ -0,0 +1,87 @@ +validator = $validator; + } + + /** + * @param array $data + * + * @return \Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Image + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidCriterionArgumentException + */ + public function parse(array $data, ParsingDispatcher $parsingDispatcher): ImageCriterion + { + $this->validateInputArray($data); + + $criterionData = $data[self::IMAGE_CRITERION]; + + return new ImageCriterion( + $criterionData[self::FIELD_DEF_IDENTIFIER_KEY], + $this->extractImageCriteria($criterionData) + ); + } + + /** + * @param array $data + */ + private function validateInputArray(array $data): void + { + $validatorBuilder = new ImageCriterionValidatorBuilder($this->validator); + $validatorBuilder->validateInputArray($data); + $violations = $validatorBuilder->build()->getViolations(); + + if ($violations->count() > 0) { + throw new ValidationFailedException( + self::IMAGE_CRITERION, + $violations + ); + } + } + + /** + * @param array $data + * + * @return array{ + * mimeTypes?: string|array, + * size?: array{min?: int|null, max?: int|null}, + * width?: array{min?: int|null, max?: int|null}, + * height?: array{min?: int|null, max?: int|null}, + * orientation?: string|array, + * } + */ + private function extractImageCriteria(array $data): array + { + return array_filter( + $data, + static fn (string $criteria): bool => self::FIELD_DEF_IDENTIFIER_KEY !== $criteria, + ARRAY_FILTER_USE_KEY + ); + } +} diff --git a/src/lib/Server/Input/Parser/Criterion/ImageDimensions.php b/src/lib/Server/Input/Parser/Criterion/ImageDimensions.php new file mode 100644 index 00000000..5de165e6 --- /dev/null +++ b/src/lib/Server/Input/Parser/Criterion/ImageDimensions.php @@ -0,0 +1,82 @@ +validator = $validator; + } + + /** + * @param array $data + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + */ + public function parse(array $data, ParsingDispatcher $parsingDispatcher): ImageDimensionsCriterion + { + $this->validateInputArray($data); + + $criterionData = $data[self::IMAGE_DIMENSIONS_CRITERION]; + + return new ImageDimensionsCriterion( + $criterionData[self::FIELD_DEF_IDENTIFIER_KEY], + $this->extractImageCriteria($criterionData) + ); + } + + /** + * @param array $data + */ + private function validateInputArray(array $data): void + { + $validatorBuilder = new ImageDimensionsCriterionValidatorBuilder($this->validator); + $validatorBuilder->validateInputArray($data); + $violations = $validatorBuilder->build()->getViolations(); + + if ($violations->count() > 0) { + throw new ValidationFailedException( + self::IMAGE_DIMENSIONS_CRITERION, + $violations + ); + } + } + + /** + * @param array $data + * + * @return array{ + * width?: array{min?: int|null, max?: int|null}, + * height?: array{min?: int|null, max?: int|null}, + * } + */ + private function extractImageCriteria(array $data): array + { + return array_filter( + $data, + static fn (string $criteria): bool => self::FIELD_DEF_IDENTIFIER_KEY !== $criteria, + ARRAY_FILTER_USE_KEY + ); + } +} diff --git a/src/lib/Server/Input/Parser/Criterion/ImageFileSize.php b/src/lib/Server/Input/Parser/Criterion/ImageFileSize.php new file mode 100644 index 00000000..093a262c --- /dev/null +++ b/src/lib/Server/Input/Parser/Criterion/ImageFileSize.php @@ -0,0 +1,72 @@ +validator = $validator; + } + + /** + * @param array $data + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + */ + public function parse(array $data, ParsingDispatcher $parsingDispatcher): ImageFileSizeCriterion + { + $this->validateInputArray($data); + + $sizeData = $data[self::IMAGE_FILE_SIZE_CRITERION][self::SIZE_KEY]; + $minFileSize = isset($sizeData['min']) + ? (int) $sizeData['min'] + : 0; + + $maxFileSize = isset($sizeData['max']) + ? (int) $sizeData['max'] + : null; + + return new ImageFileSizeCriterion( + $data[self::IMAGE_FILE_SIZE_CRITERION][self::FIELD_DEF_IDENTIFIER_KEY], + $minFileSize, + $maxFileSize, + ); + } + + /** + * @param array $data + */ + private function validateInputArray(array $data): void + { + $validatorBuilder = new ImageFileSizeCriterionValidatorBuilder($this->validator); + $validatorBuilder->validateInputArray($data); + $violations = $validatorBuilder->build()->getViolations(); + + if ($violations->count() > 0) { + throw new ValidationFailedException( + self::IMAGE_FILE_SIZE_CRITERION, + $violations + ); + } + } +} diff --git a/src/lib/Server/Input/Parser/Criterion/ImageMimeType.php b/src/lib/Server/Input/Parser/Criterion/ImageMimeType.php new file mode 100644 index 00000000..c5ebbb89 --- /dev/null +++ b/src/lib/Server/Input/Parser/Criterion/ImageMimeType.php @@ -0,0 +1,62 @@ +validator = $validator; + } + + /** + * @param array $data + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + */ + public function parse(array $data, ParsingDispatcher $parsingDispatcher): ImageMimeTypeCriterion + { + $this->validateInputArray($data); + + return new ImageMimeTypeCriterion( + $data[self::IMAGE_MIME_TYPE_CRITERION][self::FIELD_DEF_IDENTIFIER_KEY], + $data[self::IMAGE_MIME_TYPE_CRITERION][self::TYPE_KEY] ?? [], + ); + } + + /** + * @param array $data + */ + private function validateInputArray(array $data): void + { + $validatorBuilder = new ImageMimeTypeCriterionValidatorBuilder($this->validator); + $validatorBuilder->validateInputArray($data); + $violations = $validatorBuilder->build()->getViolations(); + + if ($violations->count() > 0) { + throw new ValidationFailedException( + self::IMAGE_MIME_TYPE_CRITERION, + $violations + ); + } + } +} diff --git a/src/lib/Server/Input/Parser/Criterion/ImageOrientation.php b/src/lib/Server/Input/Parser/Criterion/ImageOrientation.php new file mode 100644 index 00000000..86f21d59 --- /dev/null +++ b/src/lib/Server/Input/Parser/Criterion/ImageOrientation.php @@ -0,0 +1,62 @@ +validator = $validator; + } + + /** + * @param array $data + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + */ + public function parse(array $data, ParsingDispatcher $parsingDispatcher): ImageOrientationCriterion + { + $this->validateInputArray($data); + + return new ImageOrientationCriterion( + $data[self::IMAGE_ORIENTATION_CRITERION][self::FIELD_DEF_IDENTIFIER_KEY], + $data[self::IMAGE_ORIENTATION_CRITERION][self::ORIENTATION_KEY] + ); + } + + /** + * @param array $data + */ + private function validateInputArray(array $data): void + { + $validatorBuilder = new ImageOrientationCriterionValidatorBuilder($this->validator); + $validatorBuilder->validateInputArray($data); + $violations = $validatorBuilder->build()->getViolations(); + + if ($violations->count() > 0) { + throw new ValidationFailedException( + self::IMAGE_ORIENTATION_CRITERION, + $violations + ); + } + } +} diff --git a/src/lib/Server/Validation/Builder/Input/Parser/BaseInputParserCollectionValidatorBuilder.php b/src/lib/Server/Validation/Builder/Input/Parser/BaseInputParserCollectionValidatorBuilder.php new file mode 100644 index 00000000..04487577 --- /dev/null +++ b/src/lib/Server/Validation/Builder/Input/Parser/BaseInputParserCollectionValidatorBuilder.php @@ -0,0 +1,30 @@ + $this->getCollectionConstraints()] + ); + } + + /** + * @return array + */ + abstract protected function getCollectionConstraints(): array; +} diff --git a/src/lib/Server/Validation/Builder/Input/Parser/BaseInputParserValidatorBuilder.php b/src/lib/Server/Validation/Builder/Input/Parser/BaseInputParserValidatorBuilder.php new file mode 100644 index 00000000..59caaeda --- /dev/null +++ b/src/lib/Server/Validation/Builder/Input/Parser/BaseInputParserValidatorBuilder.php @@ -0,0 +1,47 @@ +validator = $validator->startContext(); + } + + abstract protected function buildConstraint(): Constraint; + + /** + * @phpstan-param array $data + */ + final public function validateInputArray(array $data): self + { + $this->validator + ->validate( + $data, + $this->buildConstraint() + ); + + return $this; + } + + final public function build(): ContextualValidatorInterface + { + return $this->validator; + } +} diff --git a/src/lib/Server/Validation/Builder/Input/Parser/Criterion/BaseImageCriterionValidatorBuilder.php b/src/lib/Server/Validation/Builder/Input/Parser/Criterion/BaseImageCriterionValidatorBuilder.php new file mode 100644 index 00000000..37213351 --- /dev/null +++ b/src/lib/Server/Validation/Builder/Input/Parser/Criterion/BaseImageCriterionValidatorBuilder.php @@ -0,0 +1,61 @@ + [ + new Assert\Type('string'), + new Assert\All( + [ + new Assert\Type('string'), + new Assert\NotBlank(), + ] + ), + ], + ]); + } + + protected function getRangeConstraint(): Constraint + { + return new Assert\Collection( + [ + 'fields' => [ + 'min' => new Assert\Optional( + [ + new Assert\Type('numeric'), + ] + ), + 'max' => new Assert\Optional( + [ + new Assert\Type('numeric'), + ] + ), + ], + ] + ); + } +} diff --git a/src/lib/Server/Validation/Builder/Input/Parser/Criterion/ImageCriterionValidatorBuilder.php b/src/lib/Server/Validation/Builder/Input/Parser/Criterion/ImageCriterionValidatorBuilder.php new file mode 100644 index 00000000..398bda9a --- /dev/null +++ b/src/lib/Server/Validation/Builder/Input/Parser/Criterion/ImageCriterionValidatorBuilder.php @@ -0,0 +1,54 @@ + new Assert\Collection( + [ + Image::FIELD_DEF_IDENTIFIER_KEY => $this->getFieldDefIdentifierConstraint(), + Image::MIME_TYPES_KEY => new Assert\Optional( + [ + $this->getStringOrArrayOfStringConstraint(), + ] + ), + ImageFileSize::SIZE_KEY => new Assert\Optional( + [ + $this->getRangeConstraint(), + ] + ), + ImageDimensions::WIDTH_KEY => new Assert\Optional( + [ + $this->getRangeConstraint(), + ] + ), + ImageDimensions::HEIGHT_KEY => new Assert\Optional( + [ + $this->getRangeConstraint(), + ] + ), + ImageOrientation::ORIENTATION_KEY => new Assert\Optional( + [ + $this->getStringOrArrayOfStringConstraint(), + ] + ), + ] + ), + ]; + } +} diff --git a/src/lib/Server/Validation/Builder/Input/Parser/Criterion/ImageDimensionsCriterionValidatorBuilder.php b/src/lib/Server/Validation/Builder/Input/Parser/Criterion/ImageDimensionsCriterionValidatorBuilder.php new file mode 100644 index 00000000..72d3ae1d --- /dev/null +++ b/src/lib/Server/Validation/Builder/Input/Parser/Criterion/ImageDimensionsCriterionValidatorBuilder.php @@ -0,0 +1,36 @@ + new Assert\Collection( + [ + ImageDimensions::FIELD_DEF_IDENTIFIER_KEY => $this->getFieldDefIdentifierConstraint(), + ImageDimensions::WIDTH_KEY => new Assert\Optional( + [ + $this->getRangeConstraint(), + ] + ), + ImageDimensions::HEIGHT_KEY => new Assert\Optional( + [ + $this->getRangeConstraint(), + ] + ), + ] + ), + ]; + } +} diff --git a/src/lib/Server/Validation/Builder/Input/Parser/Criterion/ImageFileSizeCriterionValidatorBuilder.php b/src/lib/Server/Validation/Builder/Input/Parser/Criterion/ImageFileSizeCriterionValidatorBuilder.php new file mode 100644 index 00000000..1f20f0ae --- /dev/null +++ b/src/lib/Server/Validation/Builder/Input/Parser/Criterion/ImageFileSizeCriterionValidatorBuilder.php @@ -0,0 +1,31 @@ + new Assert\Collection( + [ + ImageFileSize::FIELD_DEF_IDENTIFIER_KEY => $this->getFieldDefIdentifierConstraint(), + ImageFileSize::SIZE_KEY => new Assert\Optional( + [ + $this->getRangeConstraint(), + ] + ), + ] + ), + ]; + } +} diff --git a/src/lib/Server/Validation/Builder/Input/Parser/Criterion/ImageMimeTypeCriterionValidatorBuilder.php b/src/lib/Server/Validation/Builder/Input/Parser/Criterion/ImageMimeTypeCriterionValidatorBuilder.php new file mode 100644 index 00000000..45574b09 --- /dev/null +++ b/src/lib/Server/Validation/Builder/Input/Parser/Criterion/ImageMimeTypeCriterionValidatorBuilder.php @@ -0,0 +1,31 @@ + new Assert\Collection( + [ + ImageMimeType::FIELD_DEF_IDENTIFIER_KEY => $this->getFieldDefIdentifierConstraint(), + ImageMimeType::TYPE_KEY => new Assert\Optional( + [ + $this->getStringOrArrayOfStringConstraint(), + ] + ), + ] + ), + ]; + } +} diff --git a/src/lib/Server/Validation/Builder/Input/Parser/Criterion/ImageOrientationCriterionValidatorBuilder.php b/src/lib/Server/Validation/Builder/Input/Parser/Criterion/ImageOrientationCriterionValidatorBuilder.php new file mode 100644 index 00000000..7a1b38de --- /dev/null +++ b/src/lib/Server/Validation/Builder/Input/Parser/Criterion/ImageOrientationCriterionValidatorBuilder.php @@ -0,0 +1,31 @@ + new Assert\Collection( + [ + ImageOrientation::FIELD_DEF_IDENTIFIER_KEY => $this->getFieldDefIdentifierConstraint(), + ImageOrientation::ORIENTATION_KEY => new Assert\Optional( + [ + $this->getStringOrArrayOfStringConstraint(), + ] + ), + ] + ), + ]; + } +}