2 declare(strict_types
=1);
3 namespace TYPO3\CMS\Form\Mvc\Property\TypeConverter
;
6 * This file is part of the TYPO3 CMS project.
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
15 * The TYPO3 project - inspiring people to share!
18 use TYPO3\CMS\Core\Log\LogManager
;
19 use TYPO3\CMS\Core\
Resource\File
as File
;
20 use TYPO3\CMS\Core\
Resource\FileReference
as CoreFileReference
;
21 use TYPO3\CMS\Core\Utility\GeneralUtility
;
22 use TYPO3\CMS\Extbase\Domain\Model\AbstractFileFolder
;
23 use TYPO3\CMS\Extbase\Domain\Model\FileReference
as ExtbaseFileReference
;
24 use TYPO3\CMS\Extbase\Error\Error
;
25 use TYPO3\CMS\Extbase\Property\Exception\TypeConverterException
;
26 use TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface
;
27 use TYPO3\CMS\Extbase\Property\TypeConverter\AbstractTypeConverter
;
28 use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator
;
29 use TYPO3\CMS\Form\Service\TranslationService
;
32 * Class UploadedFileReferenceConverter
37 class UploadedFileReferenceConverter
extends AbstractTypeConverter
41 * Folder where the file upload should go to (including storage).
43 const CONFIGURATION_UPLOAD_FOLDER
= 1;
46 * How to handle a upload when the name of the uploaded file conflicts.
48 const CONFIGURATION_UPLOAD_CONFLICT_MODE
= 2;
51 * Validator for file types
53 const CONFIGURATION_FILE_VALIDATORS
= 4;
58 protected $defaultUploadFolder = '1:/user_upload/';
61 * One of 'cancel', 'replace', 'rename'
65 protected $defaultConflictMode = 'rename';
70 protected $sourceTypes = ['array'];
75 protected $targetType = ExtbaseFileReference
::class;
78 * Take precedence over the available FileReferenceConverter
82 protected $priority = 12;
85 * @var \TYPO3\CMS\Core\Resource\FileInterface[]
87 protected $convertedResources = [];
90 * @var \TYPO3\CMS\Core\Resource\ResourceFactory
92 protected $resourceFactory;
95 * @var \TYPO3\CMS\Extbase\Security\Cryptography\HashService
97 protected $hashService;
100 * @var \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface
102 protected $persistenceManager;
105 * @param \TYPO3\CMS\Core\Resource\ResourceFactory $resourceFactory
108 public function injectResourceFactory(\TYPO3\CMS\Core\
Resource\ResourceFactory
$resourceFactory)
110 $this->resourceFactory
= $resourceFactory;
114 * @param \TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService
117 public function injectHashService(\TYPO3\CMS\Extbase\Security\Cryptography\HashService
$hashService)
119 $this->hashService
= $hashService;
123 * @param \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface $persistenceManager
126 public function injectPersistenceManager(\TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface
$persistenceManager)
128 $this->persistenceManager
= $persistenceManager;
132 * Actually convert from $source to $targetType, taking into account the fully
133 * built $convertedChildProperties and $configuration.
135 * @param string|int $source
136 * @param string $targetType
137 * @param array $convertedChildProperties
138 * @param PropertyMappingConfigurationInterface $configuration
139 * @return AbstractFileFolder
142 public function convertFrom($source, $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface
$configuration = null)
144 if (!isset($source['error']) ||
$source['error'] === \UPLOAD_ERR_NO_FILE
) {
145 if (isset($source['submittedFile']['resourcePointer'])) {
147 $resourcePointer = $this->hashService
->validateAndStripHmac($source['submittedFile']['resourcePointer']);
148 if (strpos($resourcePointer, 'file:') === 0) {
149 $fileUid = substr($resourcePointer, 5);
150 return $this->createFileReferenceFromFalFileObject($this->resourceFactory
->getFileObject($fileUid));
152 return $this->createFileReferenceFromFalFileReferenceObject($this->resourceFactory
->getFileReferenceObject($resourcePointer), $resourcePointer);
153 } catch (\InvalidArgumentException
$e) {
154 // Nothing to do. No file is uploaded and resource pointer is invalid. Discard!
160 if ($source['error'] !== \UPLOAD_ERR_OK
) {
161 return $this->objectManager
->get(Error
::class, $this->getUploadErrorMessage($source['error']), 1471715915);
164 if (isset($this->convertedResources
[$source['tmp_name']])) {
165 return $this->convertedResources
[$source['tmp_name']];
169 $resource = $this->importUploadedResource($source, $configuration);
170 } catch (\Exception
$e) {
171 return $this->objectManager
->get(Error
::class, $e->getMessage(), $e->getCode());
174 $this->convertedResources
[$source['tmp_name']] = $resource;
179 * Import a resource and respect configuration given for properties
181 * @param array $uploadInfo
182 * @param PropertyMappingConfigurationInterface $configuration
183 * @return ExtbaseFileReference
184 * @throws TypeConverterException
186 protected function importUploadedResource(
188 PropertyMappingConfigurationInterface
$configuration
189 ): ExtbaseFileReference
{
190 if (!GeneralUtility
::verifyFilenameAgainstDenyPattern($uploadInfo['name'])) {
191 throw new TypeConverterException('Uploading files with PHP file extensions is not allowed!', 1471710357);
194 $uploadFolderId = $configuration->getConfigurationValue(self
::class, self
::CONFIGURATION_UPLOAD_FOLDER
) ?
: $this->defaultUploadFolder
;
195 $conflictMode = $configuration->getConfigurationValue(self
::class, self
::CONFIGURATION_UPLOAD_CONFLICT_MODE
) ?
: $this->defaultConflictMode
;
197 $uploadFolder = $this->resourceFactory
->retrieveFileOrFolderObject($uploadFolderId);
198 $uploadedFile = $uploadFolder->addUploadedFile($uploadInfo, $conflictMode);
200 $validators = $configuration->getConfigurationValue(self
::class, self
::CONFIGURATION_FILE_VALIDATORS
);
201 if (is_array($validators)) {
202 foreach ($validators as $validator) {
203 if ($validator instanceof AbstractValidator
) {
204 $validationResult = $validator->validate($uploadedFile);
205 if ($validationResult->hasErrors()) {
206 $uploadedFile->getStorage()->deleteFile($uploadedFile);
207 throw new TypeConverterException($validationResult->getErrors()[0]->getMessage(), 1471708999);
213 $resourcePointer = isset($uploadInfo['submittedFile']['resourcePointer']) && strpos($uploadInfo['submittedFile']['resourcePointer'], 'file:') === false
214 ?
$this->hashService
->validateAndStripHmac($uploadInfo['submittedFile']['resourcePointer'])
217 $fileReferenceModel = $this->createFileReferenceFromFalFileObject($uploadedFile, $resourcePointer);
219 return $fileReferenceModel;
224 * @param int $resourcePointer
225 * @return ExtbaseFileReference
227 protected function createFileReferenceFromFalFileObject(
229 int $resourcePointer = null
230 ): ExtbaseFileReference
{
231 $fileReference = $this->resourceFactory
->createFileReferenceObject(
233 'uid_local' => $file->getUid(),
234 'uid_foreign' => uniqid('NEW_'),
235 'uid' => uniqid('NEW_'),
239 return $this->createFileReferenceFromFalFileReferenceObject($fileReference, $resourcePointer);
243 * @param CoreFileReference $falFileReference
244 * @param int $resourcePointer
245 * @return ExtbaseFileReference
247 protected function createFileReferenceFromFalFileReferenceObject(
248 CoreFileReference
$falFileReference,
249 int $resourcePointer = null
250 ): ExtbaseFileReference
{
251 if ($resourcePointer === null) {
252 $fileReference = $this->objectManager
->get(ExtbaseFileReference
::class);
254 $fileReference = $this->persistenceManager
->getObjectByIdentifier($resourcePointer, ExtbaseFileReference
::class, false);
257 $fileReference->setOriginalResource($falFileReference);
258 return $fileReference;
262 * Returns a human-readable message for the given PHP file upload error
265 * @param int $errorCode
268 protected function getUploadErrorMessage(int $errorCode): string
270 $logger = GeneralUtility
::makeInstance(LogManager
::class)->getLogger(static::class);
271 switch ($errorCode) {
272 case \UPLOAD_ERR_INI_SIZE
:
273 $logger->error('The uploaded file exceeds the upload_max_filesize directive in php.ini.', []);
274 return TranslationService
::getInstance()->translate('upload.error.150530345', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
275 case \UPLOAD_ERR_FORM_SIZE
:
276 $logger->error('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.', []);
277 return TranslationService
::getInstance()->translate('upload.error.150530345', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
278 case \UPLOAD_ERR_PARTIAL
:
279 $logger->error('The uploaded file was only partially uploaded.', []);
280 return TranslationService
::getInstance()->translate('upload.error.150530346', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
281 case \UPLOAD_ERR_NO_FILE
:
282 $logger->error('No file was uploaded.', []);
283 return TranslationService
::getInstance()->translate('upload.error.150530347', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
284 case \UPLOAD_ERR_NO_TMP_DIR
:
285 $logger->error('Missing a temporary folder.', []);
286 return TranslationService
::getInstance()->translate('upload.error.150530348', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
287 case \UPLOAD_ERR_CANT_WRITE
:
288 $logger->error('Failed to write file to disk.', []);
289 return TranslationService
::getInstance()->translate('upload.error.150530348', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
290 case \UPLOAD_ERR_EXTENSION
:
291 $logger->error('File upload stopped by extension.', []);
292 return TranslationService
::getInstance()->translate('upload.error.150530348', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
294 $logger->error('Unknown upload error.', []);
295 return TranslationService
::getInstance()->translate('upload.error.150530348', null, 'EXT:form/Resources/Private/Language/locallang.xlf');