Feature: #103511 - Introduce Extbase file upload and deletion handling

See forge#103511

Description

TYPO3 now provides an API for file upload- and deletion-handling in Extbase extensions, which allows extension developers to implement file uploads more easily into Extbase Domain Models.

The scope of this API is to cover some of the most common use cases and to keep the internal file upload and deletion process in Extbase as simple as possible.

The API supports mapping and handling of file uploads and deletions for the following scenarios:

  • Property of type FileReference in a domain model
  • Property of type \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> in a domain model

File uploads can be validated by the following rules:

  • minimum and maximum file count
  • minimum and maximum file size
  • allowed MIME types
  • image dimensions (for image uploads)

Additionally, it is ensured, that the filename given by the client is valid, meaning that no invalid characters (null-bytes) are added and that the file does not contain an invalid file extension. The API has support for custom validators, which can be created on demand.

To avoid complexity and maintain data integrity, a file upload is only processed if the validation of all properties of a domain model is successful. In this first implementation, file uploads are not persisted/cached temporarily, so this means in any case of a validation failure ("normal" validators and file upload validation) a file upload must be performed again by users.

Possible future enhancements of this functionality could enhance the existing #[FileUpload] attribute/annotation with configuration like a temporary storage location, or specifying additional custom validators (which can be done via the PHP-API as described below)

Nesting of domain models

File upload handling for nested domain models (e.g. modelA.modelB.fileReference) is not supported.

File upload configuration with the FileUpload attribute

File upload for a property of a domain model can be configured using the newly introduced \TYPO3\CMS\Extbase\Annotation\FileUpload attribute.

Example:

#[FileUpload([
    'validation' => [
        'required' => true,
        'maxFiles' => 1,
        'fileSize' => ['minimum' => '0K', 'maximum' => '2M'],
        'mimeType' => ['allowedMimeTypes' => ['image/jpeg', 'image/png']],
    ],
    'uploadFolder' => '1:/user_upload/files/',
])]
protected ?FileReference $file = null;
Copied!

All configuration settings of the \TYPO3\CMS\Extbase\Mvc\Controller\FileUploadConfiguration object can be defined using the FileUpload attribute. It is however not possible to add custom validators using the FileUpload attribute, which you can achieve with a manual configuration as shown below.

The currently available configuration array keys are:

  • validation ( array with keys required, maxFiles, minFiles, fileSize, allowedMimeTypes, mimeType, imageDimensions, see File upload validation)
  • uploadFolder ( string, destination folder)
  • duplicationBehavior ( object, behaviour when file exists)
  • addRandomSuffix ( bool, suffixing files)
  • createUploadFolderIfNotExist ( bool, whether to create missing directories)

It is also possible to use the FileUpload annotation to configure file upload properties, but it is recommended to use the FileUpload attribute due to better readability.

Manual file upload configuration

A file upload configuration can also be created manually and should be done in the initialize*Action.

Example:

public function initializeCreateAction(): void
{
    $mimeTypeValidator = GeneralUtility::makeInstance(MimeTypeValidator::class);
    $mimeTypeValidator->setOptions(['allowedMimeTypes' => ['image/jpeg']]);

    $fileHandlingServiceConfiguration = $this->arguments->getArgument('myArgument')->getFileHandlingServiceConfiguration();
    $fileHandlingServiceConfiguration->addFileUploadConfiguration(
        (new FileUploadConfiguration('myPropertyName'))
            ->setRequired()
            ->addValidator($mimeTypeValidator)
            ->setMaxFiles(1)
            ->setUploadFolder('1:/user_upload/files/')
    );

    $this->arguments->getArgument('myArgument')->getPropertyMappingConfiguration()->skipProperties('myPropertyName');
}
Copied!

Configuration options for file uploads

The configuration for a file upload is defined in a FileUploadConfiguration object.

This object contains the following configuration options.

Property name:

Defines the name of the property of a domain model to which the file upload configuration applies. The value is automatically retrieved when using the FileUpload attribute. If the FileUploadConfiguration object is created manually, it must be set using the $propertyName constructor argument.

Validation:

File upload validation is defined in an array of validators in the FileUploadConfiguration object. The validator \TYPO3\CMS\Extbase\Validation\Validator\FileNameValidator , which ensures that no executable PHP files can be uploaded, is added by default if the file upload configuration object is created using the FileUpload attribute.

In addition, Extbase includes the following validators to validate an UploadedFile object:

  • \TYPO3\CMS\Extbase\Validation\Validator\FileSizeValidator
  • \TYPO3\CMS\Extbase\Validation\Validator\MimeTypeValidator
  • \TYPO3\CMS\Extbase\Validation\Validator\ImageDimensionsValidator

Those validators can either be configured with the FileUpload attribute or added manually to the configuration object with the addValidator method.

Required:

Defines whether a file must be uploaded. If it is set to true, the minFiles configuration is set to 1.

Minimum files:

Defines the minimum amount of files to be uploaded.

Maximum files:

Defines the maximum amount of files to be uploaded.

Upload folder:

Defines the upload path for the file upload. This configuration expects a storage identifier (e.g. 1:/user_upload/folder/). If the given target folder in the storage does not exist, it is created automatically.

Upload folder creation, when missing:

The default creation of a missing storage folder can be disabled via the configuration attribute createUploadFolderIfNotExist ( bool, default true).

Add random suffix:

When enabled, the filename of an uploaded and persisted file will contain a random 16 char suffix. As an example, an uploaded file named job-application.pdf will be persisted as job-application-<random-hash>.pdf in the upload folder.

The default value for this configuration is true and it is recommended to keep this configuration active.

This configuration only has an effect when uploaded files are persisted.

Duplication behavior:

Defines the FAL behavior, when a file with the same name exists in the target folder. Possible values are DuplicationBehavior::RENAME (default), DuplicationBehavior::REPLACE and DuplicationBehavior::CANCEL.

Modifying existing configuration

File upload configuration defined by the FileUpload attribute can be changed in the initialize*Action.

Example:

public function initializeCreateAction(): void
{
    $validator = GeneralUtility::makeInstance(MyCustomValidator::class);

    $argument = $this->arguments->getArgument('myArgument');
    $configuration = $argument->getFileHandlingServiceConfiguration()->getFileUploadConfigurationForProperty('file');
    $configuration?->setMinFiles(2);
    $configuration?->addValidator($validator);
    $configuration?->setUploadFolder('1:/user_upload/custom_folder');
}
Copied!

The example shows how to modify the file upload configuration for the argument item and the property file. The minimum amount of files to be uploaded is set to 2 and a custom validator is added.

To remove all defined validators except the DenyPhpUploadValidator, use the resetValidators() method.

Using TypoScript configuration for file uploads configuration

When a file upload configuration for a property has been added using the FileUpload attribute, it may be required make the upload folder or other configuration options configurable with TypoScript.

Extension authors should use the initialize*Action to apply settings from TypoScript to a file upload configuration.

Example:

public function initializeCreateAction(): void
{
    $argument = $this->arguments->getArgument('myArgument');
    $configuration = $argument->getFileHandlingServiceConfiguration()->getConfigurationForProperty('file');
    $configuration?->setUploadFolder($this->settings['uploadFolder'] ?? '1:/fallback_folder');
}
Copied!

File upload validation

Each uploaded file can be validated against a configurable set of validators. The validation section of the FileUpload attribute allows to configure commonly used validators using a configuration shorthand.

The following validation rules can be configured in the validation section of the FileUpload attribute:

  • required
  • minFiles
  • maxFiles
  • fileSize (for \TYPO3\CMS\Extbase\Validation\Validator\FilesizeValidator)
  • imageDimensions (for \TYPO3\CMS\Extbase\Validation\Validator\ImageDimensionsValidator )
  • mimeType (for \TYPO3\CMS\Extbase\Validation\Validator\MimeTypeValidator )
  • allowedMimeTypes (shorthand notation for configuration option allowedMimeTypes of the MimeTypeValidator)

Example:

#[FileUpload([
    'validation' => [
        'required' => true,
        'maxFiles' => 1,
        'fileSize' => ['minimum' => '0K', 'maximum' => '2M'],
        'mimeType' => ['allowedMimeTypes' => ['image/jpeg']],
        'imageDimensions' => ['maxWidth' => 4096, 'maxHeight' => 4096]
    ],
    'uploadFolder' => '1:/user_upload/extbase_single_file/',
])]
Copied!

Extbase will internally use the Extbase file upload validators for fileSize, mimeType and imageDimensions validation.

Custom validators can be created according to project requirements and must extend the Extbase AbstractValidator . The value to be validated is always a PSR-7 UploadedFile object. Custom validators can however not be used in the FileUpload attribute and must be configured manually.

Shorthand notation for allowedMimeTypes

Using the mimeType configuration array, all options of the MimeTypeValidator can be set as sub-keys (since TYPO3 13.4.1):

#[FileUpload([
    'validation' => [
        'required' => true,
        'mimeType' => [
            'allowedMimeTypes' => ['image/jpeg'],
            'ignoreFileExtensionCheck' => false,
            'notAllowedMessage' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:validation.mimetype.notAllowedMessage',
            'invalidExtensionMessage' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:validation.mimetype.invalidExtensionMessage',
        ],
    ],
    'uploadFolder' => '1:/user_upload/files/',
])]
Copied!

The shorthand notation via 'allowedMimeTypes' continues to exist, in case only the mime type validation is needed. However, it is recommended to utilize the full 'mimeType' configuration array.

Deletion of uploaded files and file references

The new Fluid ViewHelper Form.uploadDeleteCheckbox ViewHelper <f:form.uploadDeleteCheckbox> can be used to show a "delete file" checkbox in a form.

Example for object with FileReference property:

<f:form.uploadDeleteCheckbox property="file" fileReference="{object.file}" />
Copied!

Example for an object with an TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> property, containing multiple files and allowing to delete the first one (iteration is possible within Fluid, to do that for every object of the collection):

<f:form.uploadDeleteCheckbox property="file.0" fileReference="{object.file}" />
Copied!

Extbase will then handle file deletion(s) before persisting a validated object. It will:

  • validate that minimum and maximum file upload configuration for the affected property is fulfilled (only if the property has a FileUpload )
  • delete the affected sys_file_reference record
  • delete the affected file

Internally, Extbase uses FileUploadDeletionConfiguration objects to track file deletions for properties of arguments. Files are deleted directly without checking whether the current file is referenced by other objects.

Apart from using this ViewHelper, it is of course still possible to manipulate FileReference properties with custom logic before persistence.

New PSR-14 events

The following new PSR-14 event has been added to allow customization of file upload related tasks:

ModifyUploadedFileTargetFilenameEvent

The ModifyUploadedFileTargetFilenameEvent allows event listeners to alter a filename of an uploaded file before it is persisted.

Event listeners can use the method getTargetFilename() to retrieve the filename used for persistence of a configured uploaded file. The filename can then be adjusted via setTargetFilename(). The relevant configuration can be retrieved via getConfiguration().

Impact

Extension developers can use the new feature to implement file uploads and file deletions in Extbase extensions easily with commonly known Extbase property attributes/annotations.