*/ protected ObjectStorage $fileUploadConfigurations; /** * @var ObjectStorage */ protected ObjectStorage $fileUploadDeletionConfigurations; public function __construct() { $this->fileUploadConfigurations = new ObjectStorage(); $this->fileUploadDeletionConfigurations = new ObjectStorage(); } public function addFileUploadConfiguration(FileUploadConfiguration $configuration): void { $this->fileUploadConfigurations->attach($configuration); } public function getFileUploadConfigurations(): ObjectStorage { return $this->fileUploadConfigurations; } public function hasfileUploadConfigurations(): bool { return $this->fileUploadConfigurations->count() > 0; } /** * Returns the FileUploadConfiguration for the given propertyName */ public function getFileUploadConfigurationForProperty(string $propertyName): ?FileUploadConfiguration { foreach ($this->fileUploadConfigurations as $configuration) { if ($configuration->getPropertyName() === $propertyName) { return $configuration; } } return null; } /** * Registers a file deletion for the given property and file reference uid */ public function registerFileDeletion(string $property, int $fileReferenceUid): void { $fileUploadDeletionConfiguration = $this->getFileUploadDeletionConfigurationForProperty($property); if (!$fileUploadDeletionConfiguration) { $fileUploadDeletionConfiguration = GeneralUtility::makeInstance(FileUploadDeletionConfiguration::class, $property); $this->fileUploadDeletionConfigurations->attach($fileUploadDeletionConfiguration); } $fileUploadDeletionConfiguration->addFileReferenceUid($fileReferenceUid); } /** * Returns all file deletion configurations */ public function getFileUploadDeletionConfigurations(): ObjectStorage { return $this->fileUploadDeletionConfigurations; } /** * Returns the FileUploadDeletionConfiguration for the given propertyName */ public function getFileUploadDeletionConfigurationForProperty(string $propertyName): ?FileUploadDeletionConfiguration { foreach ($this->fileUploadDeletionConfigurations as $configuration) { if ($configuration->getPropertyName() === $propertyName) { return $configuration; } } return null; } /** * Returns the amount of configured file deletions for the given property */ private function getFileUploadDeletionCountForProperty(string $propertyName): int { $fileUploadDeletionConfiguration = $this->getFileUploadDeletionConfigurationForProperty($propertyName); if ($fileUploadDeletionConfiguration) { return count($fileUploadDeletionConfiguration->getFileReferenceUids()); } return 0; } /** * Validates file operations for the given argument by checking file upload and file deletion configurations and * returning the validation result. */ public function validateFileOperations(Argument $argument): Result { $validationResults = new Result(); $value = $argument->getValue(); foreach ($this->fileUploadConfigurations as $configuration) { $uploadedFilesForProperty = $argument->getUploadedFilesForProperty( $configuration->getPropertyName() ); $fileDeletionCount = $this->getFileUploadDeletionCountForProperty($configuration->getPropertyName()); $currentPropertyValue = null; if ($value) { $currentPropertyValue = ObjectAccess::getPropertyPath($value, $configuration->getPropertyName()); } $validationResult = $this->getValidationResultsForProperty( $configuration, $configuration->getPropertyName(), $currentPropertyValue, $uploadedFilesForProperty, $fileDeletionCount ); $validationResults->merge($validationResult); } return $validationResults; } /** * Validates file uploads and file deletions for the given propertyPath and currentPropertyValue and returns * the validation result. */ private function getValidationResultsForProperty( FileUploadConfiguration $configuration, string $propertyPath, mixed $currentPropertyValue, array $uploadedFiles, int $fileDeletionCount ): Result { $validationResults = new Result(); if ($currentPropertyValue instanceof FileReference) { $currentAmount = 1; } elseif ($currentPropertyValue instanceof ObjectStorage) { $currentAmount = $currentPropertyValue->count(); } else { $currentAmount = 0; } // Validate, that minimum files requirement is valid after file deletion(s) if ($fileDeletionCount > 0 && ($currentPropertyValue instanceof FileReference || $currentPropertyValue instanceof ObjectStorage) ) { $newAmount = $currentAmount - $fileDeletionCount + count($uploadedFiles); if ($newAmount < $configuration->getMinFiles()) { $minFilesError = new Error( $this->translateErrorMessage( 'filehandlingserviceconfiguration.minfiles.delete.notvalid', 'extbase', ), 1714557062 ); $validationResults->forProperty($propertyPath) ->addError($minFilesError); } } // If the given $currentPropertyValue (which is the target property for file upload) is either a FileReference // or a non empty ObjectStorage and no uploaded files are available, the rest of the validation can be skipped. if ($uploadedFiles === [] && ($currentPropertyValue instanceof FileReference || ($currentPropertyValue instanceof ObjectStorage && $currentPropertyValue->count() > 0)) ) { return $validationResults; } if (count($uploadedFiles) < $configuration->getMinFiles()) { $minFilesError = new Error( $this->translateErrorMessage( 'filehandlingserviceconfiguration.minfiles.notvalid', 'extbase', [$configuration->getMinFiles()] ), 1708596527 ); $validationResults->forProperty($propertyPath) ->addError($minFilesError); } if ((count($uploadedFiles) + $currentAmount - $fileDeletionCount) > $configuration->getMaxFiles()) { $minFilesError = new Error( $this->translateErrorMessage( 'filehandlingserviceconfiguration.maxfiles.notvalid', 'extbase', [$configuration->getMaxFiles()] ), 1708596528 ); $validationResults->forProperty($propertyPath) ->addError($minFilesError); } $validators = $this->enforceDefaultValidators( ...$configuration->getValidators() ); foreach ($validators as $validator) { foreach ($uploadedFiles as $uploadedFile) { $validatorResult = $validator->validate($uploadedFile); if ($validatorResult->hasErrors()) { $validationResults->forProperty($propertyPath)->merge($validatorResult); } } } return $validationResults; } /** * @return list */ private function enforceDefaultValidators(ValidatorInterface ...$validators): array { $enforceValidators = [ FileNameValidator::class, FileExtensionMimeTypeConsistencyValidator::class, ]; $existingValidators = array_map(get_class(...), $validators); $missingValidators = array_diff($enforceValidators, $existingValidators); foreach ($missingValidators as $missingValidator) { $validators[] = GeneralUtility::makeInstance($missingValidator); } return $validators; } /** * Wrapper to translate error messages */ private function translateErrorMessage(string $translateKey, string $extensionName, array $arguments = []): string { return LocalizationUtility::translate( $translateKey, $extensionName, $arguments ) ?? ''; } }