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