‪TYPO3CMS  ‪main
UploadedFileReferenceConverter.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
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.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
25 use ‪TYPO3\CMS\Core\Resource\FileReference as CoreFileReference;
41 
49 {
54 
59 
63  public const ‪CONFIGURATION_UPLOAD_SEED = 3;
64 
69 
73  protected ‪$defaultUploadFolder = '1:/user_upload/';
74 
80  protected ‪$defaultConflictMode = 'rename';
81 
85  protected ‪$convertedResources = [];
86 
91 
95  protected ‪$hashService;
96 
100  protected ‪$persistenceManager;
101 
106  {
107  $this->resourceFactory = ‪$resourceFactory;
108  }
109 
114  {
115  $this->hashService = ‪$hashService;
116  }
117 
122  {
123  $this->persistenceManager = ‪$persistenceManager;
124  }
125 
135  public function ‪convertFrom($source, $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null)
136  {
137  if ($source instanceof UploadedFile) {
138  $source = $this->‪convertUploadedFileToUploadInfoArray($source);
139  }
140  // slot/listener using `FileDumpController` instead of direct public URL in (later) rendering process
141  $resourcePublicationSlot = GeneralUtility::makeInstance(ResourcePublicationSlot::class);
142  if (!isset($source['error']) || $source['error'] === \UPLOAD_ERR_NO_FILE) {
143  if (isset($source['submittedFile']['resourcePointer'])) {
144  try {
145  // File references use numeric resource pointers, direct
146  // file relations are using "file:" prefix (e.g. "file:5")
147  $resourcePointer = $this->hashService->validateAndStripHmac($source['submittedFile']['resourcePointer']);
148  if (str_starts_with($resourcePointer, 'file:')) {
149  $fileUid = (int)substr($resourcePointer, 5);
150  $resource = $this->‪createFileReferenceFromFalFileObject($this->resourceFactory->getFileObject($fileUid));
151  } else {
153  $this->resourceFactory->getFileReferenceObject($resourcePointer),
154  (int)$resourcePointer
155  );
156  }
157  $resourcePublicationSlot->add($resource->getOriginalResource()->getOriginalFile());
158  return $resource;
159  } catch (\InvalidArgumentException $e) {
160  // Nothing to do. No file is uploaded and resource pointer is invalid. Discard!
161  }
162  }
163  return null;
164  }
165 
166  if ($source['error'] !== \UPLOAD_ERR_OK) {
167  return GeneralUtility::makeInstance(Error::class, $this->‪getUploadErrorMessage($source['error']), 1471715915);
168  }
169 
170  if (isset($this->convertedResources[$source['tmp_name']])) {
171  return $this->convertedResources[$source['tmp_name']];
172  }
173 
174  if ($configuration === null) {
175  throw new \InvalidArgumentException('Argument $configuration must not be null', 1589183114);
176  }
177 
178  try {
179  $resource = $this->‪importUploadedResource($source, $configuration);
180  $resourcePublicationSlot->add($resource->getOriginalResource()->getOriginalFile());
181  } catch (TypeConverterException $e) {
182  return $e->getError();
183  } catch (\Exception $e) {
184  return GeneralUtility::makeInstance(Error::class, $e->getMessage(), $e->getCode());
185  }
186 
187  $this->convertedResources[$source['tmp_name']] = $resource;
188  return $resource;
189  }
190 
194  protected function ‪importUploadedResource(
195  array $uploadInfo,
198  if (!GeneralUtility::makeInstance(FileNameValidator::class)->isValid($uploadInfo['name'])) {
199  throw new ‪TypeConverterException('Uploading files with PHP file extensions is not allowed!', 1471710357);
200  }
201  // `CONFIGURATION_UPLOAD_SEED` is expected to be defined
202  // if it's not given any random seed is generated, instead of throwing an exception
203  $seed = $configuration->‪getConfigurationValue(self::class, self::CONFIGURATION_UPLOAD_SEED)
204  ?: GeneralUtility::makeInstance(Random::class)->generateRandomHexString(40);
205  $uploadFolderId = $configuration->‪getConfigurationValue(self::class, self::CONFIGURATION_UPLOAD_FOLDER) ?: ‪$this->defaultUploadFolder;
206  $conflictMode = $configuration->‪getConfigurationValue(self::class, self::CONFIGURATION_UPLOAD_CONFLICT_MODE) ?: ‪$this->defaultConflictMode;
207  $pseudoFile = GeneralUtility::makeInstance(PseudoFile::class, $uploadInfo);
208 
209  $validators = $configuration->‪getConfigurationValue(self::class, self::CONFIGURATION_FILE_VALIDATORS);
210  if (is_array($validators)) {
211  foreach ($validators as ‪$validator) {
212  if (‪$validator instanceof AbstractValidator) {
213  $validationResult = ‪$validator->validate($pseudoFile);
214  if ($validationResult->hasErrors()) {
215  throw ‪TypeConverterException::fromError($validationResult->getErrors()[0]);
216  }
217  }
218  }
219  }
220 
221  $uploadFolder = $this->‪provideUploadFolder($uploadFolderId);
222  // current folder name, derived from public random seed (`formSession`)
223  $currentName = 'form_' . ‪GeneralUtility::hmac($seed, self::class);
224  $uploadFolder = $this->‪provideTargetFolder($uploadFolder, $currentName);
225  // sub-folder in $uploadFolder with 160 bit of derived entropy (.../form_<40-chars-hash>/actual.file)
226  $uploadedFile = $uploadFolder->addUploadedFile($uploadInfo, $conflictMode);
227 
228  $resourcePointer = isset($uploadInfo['submittedFile']['resourcePointer']) && !str_contains($uploadInfo['submittedFile']['resourcePointer'], 'file:')
229  ? (int)$this->hashService->validateAndStripHmac($uploadInfo['submittedFile']['resourcePointer'])
230  : null;
231 
232  $fileReferenceModel = $this->‪createFileReferenceFromFalFileObject($uploadedFile, $resourcePointer);
233 
234  return $fileReferenceModel;
235  }
236 
237  protected function ‪createFileReferenceFromFalFileObject(
238  File $file,
239  int $resourcePointer = null
241  $fileReference = $this->resourceFactory->createFileReferenceObject(
242  [
243  'uid_local' => $file->getUid(),
244  'uid_foreign' => ‪StringUtility::getUniqueId('NEW_'),
245  'uid' => ‪StringUtility::getUniqueId('NEW_'),
246  'crop' => null,
247  ]
248  );
249  return $this->‪createFileReferenceFromFalFileReferenceObject($fileReference, $resourcePointer);
250  }
251 
258  CoreFileReference $falFileReference,
259  int $resourcePointer = null
261  if ($resourcePointer === null) {
262  $fileReference = GeneralUtility::makeInstance(PseudoFileReference::class);
263  } else {
264  $fileReference = $this->persistenceManager->getObjectByIdentifier($resourcePointer, PseudoFileReference::class, false);
265  }
266 
267  $fileReference->setOriginalResource($falFileReference);
268  return $fileReference;
269  }
270 
275  protected function ‪getUploadErrorMessage(int $errorCode): string
276  {
277  $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class);
278  switch ($errorCode) {
279  case \UPLOAD_ERR_INI_SIZE:
280  $logger->error('The uploaded file exceeds the upload_max_filesize directive in php.ini.', []);
281  return GeneralUtility::makeInstance(TranslationService::class)->translate('upload.error.150530345', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
282  case \UPLOAD_ERR_FORM_SIZE:
283  $logger->error('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.', []);
284  return GeneralUtility::makeInstance(TranslationService::class)->translate('upload.error.150530345', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
285  case \UPLOAD_ERR_PARTIAL:
286  $logger->error('The uploaded file was only partially uploaded.', []);
287  return GeneralUtility::makeInstance(TranslationService::class)->translate('upload.error.150530346', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
288  case \UPLOAD_ERR_NO_FILE:
289  $logger->error('No file was uploaded.', []);
290  return GeneralUtility::makeInstance(TranslationService::class)->translate('upload.error.150530347', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
291  case \UPLOAD_ERR_NO_TMP_DIR:
292  $logger->error('Missing a temporary folder.', []);
293  return GeneralUtility::makeInstance(TranslationService::class)->translate('upload.error.150530348', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
294  case \UPLOAD_ERR_CANT_WRITE:
295  $logger->error('Failed to write file to disk.', []);
296  return GeneralUtility::makeInstance(TranslationService::class)->translate('upload.error.150530348', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
297  case \UPLOAD_ERR_EXTENSION:
298  $logger->error('File upload stopped by extension.', []);
299  return GeneralUtility::makeInstance(TranslationService::class)->translate('upload.error.150530348', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
300  default:
301  $logger->error('Unknown upload error.', []);
302  return GeneralUtility::makeInstance(TranslationService::class)->translate('upload.error.150530348', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
303  }
304  }
305 
309  protected function ‪provideUploadFolder(string $uploadFolderIdentifier): Folder
310  {
311  try {
312  return $this->resourceFactory->getFolderObjectFromCombinedIdentifier($uploadFolderIdentifier);
313  } catch (FolderDoesNotExistException $exception) {
314  [$storageId, $storagePath] = explode(':', $uploadFolderIdentifier, 2);
315  $storage = $this->resourceFactory->getStorageObject($storageId);
316  $folderNames = GeneralUtility::trimExplode('/', $storagePath, true);
317  $uploadFolder = $this->‪provideTargetFolder($storage->getRootLevelFolder(), ...$folderNames);
318  $this->‪provideFolderInitialization($uploadFolder);
319  return $uploadFolder;
320  }
321  }
322 
326  protected function ‪provideTargetFolder(Folder $parentFolder, string $folderName): Folder
327  {
328  return $parentFolder->hasFolder($folderName)
329  ? $parentFolder->getSubfolder($folderName)
330  : $parentFolder->createFolder($folderName);
331  }
332 
337  protected function ‪provideFolderInitialization(‪Folder $parentFolder): void
338  {
339  if (!$parentFolder->‪hasFile('index.html')) {
340  $parentFolder->‪createFile('index.html');
341  }
342  }
343 
344  protected function ‪convertUploadedFileToUploadInfoArray(‪UploadedFile $uploadedFile): array
345  {
346  return [
347  'name' => $uploadedFile->‪getClientFilename(),
348  'tmp_name' => $uploadedFile->‪getTemporaryFileName(),
349  'size' => $uploadedFile->‪getSize(),
350  'error' => $uploadedFile->‪getError(),
351  'type' => $uploadedFile->‪getClientMediaType(),
352  ];
353  }
354 }
‪TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface\getConfigurationValue
‪mixed getConfigurationValue($typeConverterClassName, $key)
‪TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator
Definition: AbstractValidator.php:29
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter
Definition: UploadedFileReferenceConverter.php:49
‪TYPO3\CMS\Form\Mvc\Property\Exception\TypeConverterException\getError
‪getError()
Definition: TypeConverterException.php:37
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\PseudoFileReference
Definition: PseudoFileReference.php:36
‪TYPO3\CMS\Form\Mvc\Property\Exception\TypeConverterException\fromError
‪static fromError(Error $error)
Definition: TypeConverterException.php:29
‪TYPO3\CMS\Core\Http\UploadedFile\getError
‪int getError()
Definition: UploadedFile.php:211
‪TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface
Definition: PersistenceManagerInterface.php:22
‪TYPO3\CMS\Form\Service\TranslationService
Definition: TranslationService.php:45
‪TYPO3\CMS\Core\Resource\Security\FileNameValidator
Definition: FileNameValidator.php:25
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\injectHashService
‪injectHashService(HashService $hashService)
Definition: UploadedFileReferenceConverter.php:107
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\injectResourceFactory
‪injectResourceFactory(ResourceFactory $resourceFactory)
Definition: UploadedFileReferenceConverter.php:99
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\$resourceFactory
‪TYPO3 CMS Core Resource ResourceFactory $resourceFactory
Definition: UploadedFileReferenceConverter.php:86
‪TYPO3\CMS\Core\Resource\Folder\createFile
‪File createFile($fileName)
Definition: Folder.php:325
‪TYPO3\CMS\Core\Resource\FileReference
Definition: FileReference.php:35
‪TYPO3\CMS\Form\Slot\ResourcePublicationSlot
Definition: ResourcePublicationSlot.php:37
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter
Definition: FormDefinitionArrayConverter.php:18
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\$convertedResources
‪PseudoFileReference[] $convertedResources
Definition: UploadedFileReferenceConverter.php:82
‪TYPO3\CMS\Extbase\Security\Cryptography\HashService
Definition: HashService.php:31
‪TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface
Definition: PropertyMappingConfigurationInterface.php:22
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\importUploadedResource
‪importUploadedResource(array $uploadInfo, PropertyMappingConfigurationInterface $configuration)
Definition: UploadedFileReferenceConverter.php:188
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\injectPersistenceManager
‪injectPersistenceManager(PersistenceManagerInterface $persistenceManager)
Definition: UploadedFileReferenceConverter.php:115
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\createFileReferenceFromFalFileReferenceObject
‪createFileReferenceFromFalFileReferenceObject(CoreFileReference $falFileReference, int $resourcePointer=null)
Definition: UploadedFileReferenceConverter.php:251
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\CONFIGURATION_UPLOAD_FOLDER
‪const CONFIGURATION_UPLOAD_FOLDER
Definition: UploadedFileReferenceConverter.php:53
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\CONFIGURATION_UPLOAD_CONFLICT_MODE
‪const CONFIGURATION_UPLOAD_CONFLICT_MODE
Definition: UploadedFileReferenceConverter.php:58
‪TYPO3\CMS\Core\Resource\Folder\hasFile
‪hasFile(string $name)
Definition: Folder.php:370
‪TYPO3\CMS\Form\Exception
Definition: Exception.php:26
‪TYPO3\CMS\Core\Resource\Folder\getSubfolder
‪getSubfolder(string $name)
Definition: Folder.php:251
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\$defaultConflictMode
‪string $defaultConflictMode
Definition: UploadedFileReferenceConverter.php:78
‪TYPO3\CMS\Extbase\Domain\Model\AbstractFileFolder
Definition: AbstractFileFolder.php:29
‪TYPO3\CMS\Extbase\Error\Error
Definition: Error.php:25
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\$persistenceManager
‪TYPO3 CMS Extbase Persistence PersistenceManagerInterface $persistenceManager
Definition: UploadedFileReferenceConverter.php:94
‪TYPO3\CMS\Form\Mvc\Property\Exception\TypeConverterException
Definition: TypeConverterException.php:24
‪TYPO3\CMS\Core\Resource\Folder\createFolder
‪Folder createFolder($folderName)
Definition: Folder.php:336
‪$validator
‪if(isset($args['d'])) $validator
Definition: validateRstFiles.php:262
‪TYPO3\CMS\Core\Resource\Folder
Definition: Folder.php:37
‪TYPO3\CMS\Core\Resource\ResourceFactory
Definition: ResourceFactory.php:41
‪TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException
Definition: FolderDoesNotExistException.php:22
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\convertFrom
‪AbstractFileFolder Error null convertFrom($source, $targetType, array $convertedChildProperties=[], PropertyMappingConfigurationInterface $configuration=null)
Definition: UploadedFileReferenceConverter.php:129
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\CONFIGURATION_UPLOAD_SEED
‪const CONFIGURATION_UPLOAD_SEED
Definition: UploadedFileReferenceConverter.php:63
‪TYPO3\CMS\Core\Resource\File
Definition: File.php:26
‪TYPO3\CMS\Core\Http\UploadedFile\getClientFilename
‪string null getClientFilename()
Definition: UploadedFile.php:227
‪TYPO3\CMS\Core\Utility\GeneralUtility\hmac
‪static string hmac($input, $additionalSecret='')
Definition: GeneralUtility.php:475
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\provideUploadFolder
‪provideUploadFolder(string $uploadFolderIdentifier)
Definition: UploadedFileReferenceConverter.php:303
‪TYPO3\CMS\Extbase\Property\TypeConverter\AbstractTypeConverter
Definition: AbstractTypeConverter.php:29
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\provideFolderInitialization
‪provideFolderInitialization(Folder $parentFolder)
Definition: UploadedFileReferenceConverter.php:331
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\$defaultUploadFolder
‪string $defaultUploadFolder
Definition: UploadedFileReferenceConverter.php:72
‪TYPO3\CMS\Core\Log\LogManager
Definition: LogManager.php:33
‪TYPO3\CMS\Core\Resource\Folder\hasFolder
‪hasFolder(string $name)
Definition: Folder.php:389
‪TYPO3\CMS\Core\Http\UploadedFile
Definition: UploadedFile.php:34
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\CONFIGURATION_FILE_VALIDATORS
‪const CONFIGURATION_FILE_VALIDATORS
Definition: UploadedFileReferenceConverter.php:68
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\$hashService
‪TYPO3 CMS Extbase Security Cryptography HashService $hashService
Definition: UploadedFileReferenceConverter.php:90
‪TYPO3\CMS\Core\Crypto\Random
Definition: Random.php:27
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Core\Http\UploadedFile\getClientMediaType
‪string null getClientMediaType()
Definition: UploadedFile.php:257
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:24
‪TYPO3\CMS\Core\Http\UploadedFile\getTemporaryFileName
‪getTemporaryFileName()
Definition: UploadedFile.php:238
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\getUploadErrorMessage
‪getUploadErrorMessage(int $errorCode)
Definition: UploadedFileReferenceConverter.php:269
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\provideTargetFolder
‪provideTargetFolder(Folder $parentFolder, string $folderName)
Definition: UploadedFileReferenceConverter.php:320
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\convertUploadedFileToUploadInfoArray
‪convertUploadedFileToUploadInfoArray(UploadedFile $uploadedFile)
Definition: UploadedFileReferenceConverter.php:338
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static getUniqueId(string $prefix='')
Definition: StringUtility.php:57
‪TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter\createFileReferenceFromFalFileObject
‪createFileReferenceFromFalFileObject(File $file, int $resourcePointer=null)
Definition: UploadedFileReferenceConverter.php:231
‪TYPO3\CMS\Core\Http\UploadedFile\getSize
‪int null getSize()
Definition: UploadedFile.php:193