Passed
Push — master ( 970d9b...cc8532 )
by
unknown
14:07
created

ResourceFactory::retrieveFileOrFolderObject()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 39
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 21
nc 8
nop 1
dl 0
loc 39
rs 8.4444
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Core\Resource;
17
18
use Psr\Http\Message\ServerRequestInterface;
19
use TYPO3\CMS\Backend\Utility\BackendUtility;
20
use TYPO3\CMS\Core\Collection\CollectionInterface;
21
use TYPO3\CMS\Core\Core\Environment;
22
use TYPO3\CMS\Core\Database\ConnectionPool;
23
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
24
use TYPO3\CMS\Core\Http\ApplicationType;
25
use TYPO3\CMS\Core\Resource\Collection\FileCollectionRegistry;
26
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
27
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
28
use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
29
use TYPO3\CMS\Core\Service\FlexFormService;
30
use TYPO3\CMS\Core\SingletonInterface;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
use TYPO3\CMS\Core\Utility\MathUtility;
33
use TYPO3\CMS\Core\Utility\PathUtility;
34
35
/**
36
 * Factory class for FAL objects
37
 */
38
class ResourceFactory implements SingletonInterface
39
{
40
    /**
41
     * @var Collection\AbstractFileCollection[]
42
     */
43
    protected $collectionInstances = [];
44
45
    /**
46
     * @var File[]
47
     */
48
    protected $fileInstances = [];
49
50
    /**
51
     * @var FileReference[]
52
     */
53
    protected $fileReferenceInstances = [];
54
55
    /**
56
     * @var StorageRepository
57
     */
58
    protected $storageRepository;
59
60
    public function __construct(StorageRepository $storageRepository)
61
    {
62
        $this->storageRepository = $storageRepository;
63
    }
64
65
    /**
66
     * Returns the Default Storage
67
     *
68
     * The Default Storage is considered to be the replacement for the fileadmin/ construct.
69
     * It is automatically created with the setting fileadminDir from install tool.
70
     * getDefaultStorage->getDefaultFolder() will get you fileadmin/user_upload/ in a standard
71
     * TYPO3 installation.
72
     *
73
     * @return ResourceStorage|null
74
     * @internal It is recommended to use the StorageRepository in the future, and this is only kept as backwards-compat layer
75
     */
76
    public function getDefaultStorage()
77
    {
78
        return $this->storageRepository->getDefaultStorage();
79
    }
80
81
    /**
82
     * Creates an instance of the storage from given UID. The $recordData can
83
     * be supplied to increase performance.
84
     *
85
     * @param int|null $uid The uid of the storage to instantiate.
86
     * @param array $recordData The record row from database.
87
     * @param string $fileIdentifier Identifier for a file. Used for auto-detection of a storage, but only if $uid === 0 (Local default storage) is used
88
     *
89
     * @throws \InvalidArgumentException
90
     * @return ResourceStorage
91
     * @internal It is recommended to use the StorageRepository in the future, and this is only kept as backwards-compat layer
92
     */
93
    public function getStorageObject($uid, array $recordData = [], &$fileIdentifier = null)
94
    {
95
        return $this->storageRepository->getStorageObject($uid, $recordData, $fileIdentifier);
96
    }
97
98
    /**
99
     * Converts a flexform data string to a flat array with key value pairs.
100
     *
101
     * It is recommended to not use this functionality directly, and instead implement this code yourself, as this
102
     * code has nothing to do with a Public API for Resources.
103
     *
104
     * @param string $flexFormData
105
     * @return array Array with key => value pairs of the field data in the FlexForm
106
     * @internal
107
     */
108
    public function convertFlexFormDataToConfigurationArray($flexFormData)
109
    {
110
        $configuration = [];
111
        if ($flexFormData) {
112
            $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
113
            $configuration = $flexFormService->convertFlexFormContentToArray($flexFormData);
114
        }
115
        return $configuration;
116
    }
117
118
    /**
119
     * Creates an instance of the collection from given UID. The $recordData can be supplied to increase performance.
120
     *
121
     * @param int $uid The uid of the collection to instantiate.
122
     * @param array $recordData The record row from database.
123
     *
124
     * @throws \InvalidArgumentException
125
     * @return Collection\AbstractFileCollection
126
     */
127
    public function getCollectionObject($uid, array $recordData = [])
128
    {
129
        if (!is_numeric($uid)) {
0 ignored issues
show
introduced by
The condition is_numeric($uid) is always true.
Loading history...
130
            throw new \InvalidArgumentException('The UID of collection has to be numeric. UID given: "' . $uid . '"', 1314085999);
131
        }
132
        if (!$this->collectionInstances[$uid]) {
133
            // Get mount data if not already supplied as argument to this function
134
            if (empty($recordData) || $recordData['uid'] !== $uid) {
135
                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_collection');
136
                $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
137
                $recordData = $queryBuilder->select('*')
138
                    ->from('sys_file_collection')
139
                    ->where(
140
                        $queryBuilder->expr()->eq(
141
                            'uid',
142
                            $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
143
                        )
144
                    )
145
                    ->execute()
146
                    ->fetch();
147
                if (empty($recordData)) {
148
                    throw new \InvalidArgumentException('No collection found for given UID: "' . $uid . '"', 1314085992);
149
                }
150
            }
151
            $collectionObject = $this->createCollectionObject($recordData);
152
            $this->collectionInstances[$uid] = $collectionObject;
153
        }
154
        return $this->collectionInstances[$uid];
155
    }
156
157
    /**
158
     * Creates a collection object.
159
     *
160
     * @param array $collectionData The database row of the sys_file_collection record.
161
     * @return Collection\AbstractFileCollection|CollectionInterface
162
     */
163
    public function createCollectionObject(array $collectionData)
164
    {
165
        /** @var Collection\FileCollectionRegistry $registry */
166
        $registry = GeneralUtility::makeInstance(FileCollectionRegistry::class);
167
168
        /** @var \TYPO3\CMS\Core\Collection\AbstractRecordCollection $class */
169
        $class = $registry->getFileCollectionClass($collectionData['type']);
170
171
        return $class::create($collectionData);
172
    }
173
174
    /**
175
     * Creates a storage object from a storage database row.
176
     *
177
     * @param array $storageRecord
178
     * @param array|null $storageConfiguration Storage configuration (if given, this won't be extracted from the FlexForm value but the supplied array used instead)
179
     * @return ResourceStorage
180
     * @internal It is recommended to use the StorageRepository in the future, and this is only kept as backwards-compat layer
181
     */
182
    public function createStorageObject(array $storageRecord, array $storageConfiguration = null)
183
    {
184
        return $this->storageRepository->createStorageObject($storageRecord, $storageConfiguration);
185
    }
186
187
    /**
188
     * Creates a folder to directly access (a part of) a storage.
189
     *
190
     * @param ResourceStorage $storage The storage the folder belongs to
191
     * @param string $identifier The path to the folder. Might also be a simple unique string, depending on the storage driver.
192
     * @param string $name The name of the folder (e.g. the folder name)
193
     * @return Folder
194
     * @internal it is recommended to access the ResourceStorage object directly and access ->getFolder($identifier) this method is kept for backwards compatibility
195
     */
196
    public function createFolderObject(ResourceStorage $storage, $identifier, $name)
197
    {
198
        return GeneralUtility::makeInstance(Folder::class, $storage, $identifier, $name);
199
    }
200
201
    /**
202
     * Creates an instance of the file given UID. The $fileData can be supplied
203
     * to increase performance.
204
     *
205
     * @param int $uid The uid of the file to instantiate.
206
     * @param array $fileData The record row from database.
207
     *
208
     * @throws \InvalidArgumentException
209
     * @throws Exception\FileDoesNotExistException
210
     * @return File
211
     */
212
    public function getFileObject($uid, array $fileData = [])
213
    {
214
        if (!is_numeric($uid)) {
0 ignored issues
show
introduced by
The condition is_numeric($uid) is always true.
Loading history...
215
            throw new \InvalidArgumentException('The UID of file has to be numeric. UID given: "' . $uid . '"', 1300096564);
216
        }
217
        if (empty($this->fileInstances[$uid])) {
218
            // Fetches data in case $fileData is empty
219
            if (empty($fileData)) {
220
                $fileData = $this->getFileIndexRepository()->findOneByUid($uid);
221
                if ($fileData === false) {
222
                    throw new FileDoesNotExistException('No file found for given UID: ' . $uid, 1317178604);
223
                }
224
            }
225
            $this->fileInstances[$uid] = $this->createFileObject($fileData);
226
        }
227
        return $this->fileInstances[$uid];
228
    }
229
230
    /**
231
     * Gets a file object from an identifier [storage]:[fileId]
232
     *
233
     * @param string $identifier
234
     * @return File|ProcessedFile|null
235
     * @throws \InvalidArgumentException
236
     */
237
    public function getFileObjectFromCombinedIdentifier($identifier)
238
    {
239
        if (!is_string($identifier) || $identifier === '') {
0 ignored issues
show
introduced by
The condition is_string($identifier) is always true.
Loading history...
240
            throw new \InvalidArgumentException('Invalid file identifier given. It must be of type string and not empty. "' . gettype($identifier) . '" given.', 1401732564);
241
        }
242
        $parts = GeneralUtility::trimExplode(':', $identifier);
243
        if (count($parts) === 2) {
244
            $storageUid = (int)$parts[0];
245
            $fileIdentifier = $parts[1];
246
        } else {
247
            // We only got a path: Go into backwards compatibility mode and
248
            // use virtual Storage (uid=0)
249
            $storageUid = 0;
250
            $fileIdentifier = $parts[0];
251
        }
252
        return $this->storageRepository->getStorageObject($storageUid, [], $fileIdentifier)
253
            ->getFileByIdentifier($fileIdentifier);
254
    }
255
256
    /**
257
     * Gets a file object from storage by file identifier
258
     * If the file is outside of the process folder, it gets indexed and returned as file object afterwards
259
     * If the file is within processing folder, the file object will be directly returned
260
     *
261
     * @param ResourceStorage|int $storage
262
     * @param string $fileIdentifier
263
     * @return File|ProcessedFile|null
264
     * @internal It is recommended to use the StorageRepository in the future, and this is only kept as backwards-compat layer
265
     */
266
    public function getFileObjectByStorageAndIdentifier($storage, &$fileIdentifier)
267
    {
268
        if (!($storage instanceof ResourceStorage)) {
269
            $storage = $this->storageRepository->getStorageObject($storage, [], $fileIdentifier);
270
        }
271
        return $storage->getFileByIdentifier($fileIdentifier);
272
    }
273
274
    /**
275
     * Bulk function, can be used for anything to get a file or folder
276
     *
277
     * 1. It's a UID
278
     * 2. It's a combined identifier
279
     * 3. It's just a path/filename (coming from the oldstyle/backwards compatibility)
280
     *
281
     * Files, previously laid on fileadmin/ or something, will be "mapped" to the storage the file is
282
     * in now. Files like typo3temp/ or typo3conf/ will be moved to the first writable storage
283
     * in its processing folder
284
     *
285
     * $input could be
286
     * - "2:myfolder/myfile.jpg" (combined identifier)
287
     * - "23" (file UID)
288
     * - "uploads/myfile.png" (backwards-compatibility, storage "0")
289
     * - "file:23"
290
     *
291
     * @param string $input
292
     * @return File|Folder|null
293
     */
294
    public function retrieveFileOrFolderObject($input)
295
    {
296
        // Remove Environment::getPublicPath() because absolute paths under Windows systems contain ':'
297
        // This is done in all considered sub functions anyway
298
        $input = str_replace(Environment::getPublicPath() . '/', '', $input);
299
300
        if (GeneralUtility::isFirstPartOfStr($input, 'file:')) {
301
            $input = substr($input, 5);
302
            return $this->retrieveFileOrFolderObject($input);
303
        }
304
        if (MathUtility::canBeInterpretedAsInteger($input)) {
305
            return $this->getFileObject((int)$input);
306
        }
307
        if (strpos($input, ':') > 0) {
308
            [$prefix] = explode(':', $input);
309
            if (MathUtility::canBeInterpretedAsInteger($prefix)) {
310
                // path or folder in a valid storageUID
311
                return $this->getObjectFromCombinedIdentifier($input);
312
            }
313
            if ($prefix === 'EXT') {
314
                $input = GeneralUtility::getFileAbsFileName($input);
315
                if (empty($input)) {
316
                    return null;
317
                }
318
319
                $input = PathUtility::getRelativePath(Environment::getPublicPath() . '/', PathUtility::dirname($input)) . PathUtility::basename($input);
320
                return $this->getFileObjectFromCombinedIdentifier($input);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getFileObj...binedIdentifier($input) also could return the type TYPO3\CMS\Core\Resource\ProcessedFile which is incompatible with the documented return type TYPO3\CMS\Core\Resource\...re\Resource\Folder|null.
Loading history...
321
            }
322
            return null;
323
        }
324
        // this is a backwards-compatible way to access "0-storage" files or folders
325
        // eliminate double slashes, /./ and /../
326
        $input = PathUtility::getCanonicalPath(ltrim($input, '/'));
327
        if (@is_file(Environment::getPublicPath() . '/' . $input)) {
328
            // only the local file
329
            return $this->getFileObjectFromCombinedIdentifier($input);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getFileObj...binedIdentifier($input) also could return the type TYPO3\CMS\Core\Resource\ProcessedFile which is incompatible with the documented return type TYPO3\CMS\Core\Resource\...re\Resource\Folder|null.
Loading history...
330
        }
331
        // only the local path
332
        return $this->getFolderObjectFromCombinedIdentifier($input);
333
    }
334
335
    /**
336
     * Gets a folder object from an identifier [storage]:[fileId]
337
     *
338
     * @TODO check naming, inserted by SteffenR while working on filelist
339
     * @param string $identifier
340
     * @return Folder
341
     */
342
    public function getFolderObjectFromCombinedIdentifier($identifier)
343
    {
344
        $parts = GeneralUtility::trimExplode(':', $identifier);
345
        if (count($parts) === 2) {
346
            $storageUid = (int)$parts[0];
347
            $folderIdentifier = $parts[1];
348
        } else {
349
            // We only got a path: Go into backwards compatibility mode and
350
            // use virtual Storage (uid=0)
351
            $storageUid = 0;
352
353
            // please note that getStorageObject() might modify $folderIdentifier when
354
            // auto-detecting the best-matching storage to use
355
            $folderIdentifier = $parts[0];
356
            // make sure to not use an absolute path, and remove Environment::getPublicPath if it is prepended
357
            if (GeneralUtility::isFirstPartOfStr($folderIdentifier, Environment::getPublicPath() . '/')) {
358
                $folderIdentifier = PathUtility::stripPathSitePrefix($parts[0]);
359
            }
360
        }
361
        return $this->storageRepository->getStorageObject($storageUid, [], $folderIdentifier)->getFolder($folderIdentifier);
362
    }
363
364
    /**
365
     * Gets a storage object from a combined identifier
366
     *
367
     * @param string $identifier An identifier of the form [storage uid]:[object identifier]
368
     * @return ResourceStorage
369
     * @internal It is recommended to use the StorageRepository in the future, and this is only kept as backwards-compat layer
370
     */
371
    public function getStorageObjectFromCombinedIdentifier($identifier)
372
    {
373
        $parts = GeneralUtility::trimExplode(':', $identifier);
374
        $storageUid = count($parts) === 2 ? $parts[0] : null;
375
        return $this->storageRepository->findByUid($storageUid);
0 ignored issues
show
Bug introduced by
$storageUid of type null|string is incompatible with the type integer expected by parameter $uid of TYPO3\CMS\Core\Resource\...Repository::findByUid(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

375
        return $this->storageRepository->findByUid(/** @scrutinizer ignore-type */ $storageUid);
Loading history...
376
    }
377
378
    /**
379
     * Gets a file or folder object.
380
     *
381
     * @param string $identifier
382
     *
383
     * @throws Exception\ResourceDoesNotExistException
384
     * @return FileInterface|Folder
385
     */
386
    public function getObjectFromCombinedIdentifier($identifier)
387
    {
388
        [$storageId, $objectIdentifier] = GeneralUtility::trimExplode(':', $identifier);
389
        $storage = $this->storageRepository->findByUid($storageId);
0 ignored issues
show
Bug introduced by
$storageId of type string is incompatible with the type integer expected by parameter $uid of TYPO3\CMS\Core\Resource\...Repository::findByUid(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

389
        $storage = $this->storageRepository->findByUid(/** @scrutinizer ignore-type */ $storageId);
Loading history...
390
        if ($storage->hasFile($objectIdentifier)) {
391
            return $storage->getFile($objectIdentifier);
392
        }
393
        if ($storage->hasFolder($objectIdentifier)) {
394
            return $storage->getFolder($objectIdentifier);
395
        }
396
        throw new ResourceDoesNotExistException('Object with identifier "' . $identifier . '" does not exist in storage', 1329647780);
397
    }
398
399
    /**
400
     * Creates a file object from an array of file data. Requires a database
401
     * row to be fetched.
402
     *
403
     * @param array $fileData
404
     * @param ResourceStorage $storage
405
     * @return File
406
     */
407
    public function createFileObject(array $fileData, ResourceStorage $storage = null)
408
    {
409
        if (array_key_exists('storage', $fileData) && MathUtility::canBeInterpretedAsInteger($fileData['storage'])) {
410
            $storageObject = $this->storageRepository->findByUid((int)$fileData['storage']);
411
        } elseif ($storage !== null) {
412
            $storageObject = $storage;
413
            $fileData['storage'] = $storage->getUid();
414
        } else {
415
            throw new \RuntimeException('A file needs to reside in a Storage', 1381570997);
416
        }
417
        /** @var File $fileObject */
418
        $fileObject = GeneralUtility::makeInstance(File::class, $fileData, $storageObject);
419
        return $fileObject;
420
    }
421
422
    /**
423
     * Creates an instance of a FileReference object. The $fileReferenceData can
424
     * be supplied to increase performance.
425
     *
426
     * @param int $uid The uid of the file usage (sys_file_reference) to instantiate.
427
     * @param array $fileReferenceData The record row from database.
428
     * @param bool $raw Whether to get raw results without performing overlays
429
     * @return FileReference
430
     * @throws \InvalidArgumentException
431
     * @throws Exception\ResourceDoesNotExistException
432
     */
433
    public function getFileReferenceObject($uid, array $fileReferenceData = [], $raw = false)
434
    {
435
        if (!is_numeric($uid)) {
0 ignored issues
show
introduced by
The condition is_numeric($uid) is always true.
Loading history...
436
            throw new \InvalidArgumentException(
437
                'The reference UID for the file (sys_file_reference) has to be numeric. UID given: "' . $uid . '"',
438
                1300086584
439
            );
440
        }
441
        if (!$this->fileReferenceInstances[$uid]) {
442
            // Fetches data in case $fileData is empty
443
            if (empty($fileReferenceData)) {
444
                $fileReferenceData = $this->getFileReferenceData($uid, $raw);
445
                if (!is_array($fileReferenceData)) {
446
                    throw new ResourceDoesNotExistException(
447
                        'No file reference (sys_file_reference) was found for given UID: "' . $uid . '"',
448
                        1317178794
449
                    );
450
                }
451
            }
452
            $this->fileReferenceInstances[$uid] = $this->createFileReferenceObject($fileReferenceData);
453
        }
454
        return $this->fileReferenceInstances[$uid];
455
    }
456
457
    /**
458
     * Creates a file usage object from an array of fileReference data
459
     * from sys_file_reference table.
460
     * Requires a database row to be already fetched and present.
461
     *
462
     * @param array $fileReferenceData
463
     * @return FileReference
464
     */
465
    public function createFileReferenceObject(array $fileReferenceData)
466
    {
467
        /** @var FileReference $fileReferenceObject */
468
        $fileReferenceObject = GeneralUtility::makeInstance(FileReference::class, $fileReferenceData);
469
        return $fileReferenceObject;
470
    }
471
472
    /**
473
     * Gets data for the given uid of the file reference record.
474
     *
475
     * @param int $uid The uid of the file usage (sys_file_reference) to be fetched
476
     * @param bool $raw Whether to get raw results without performing overlays
477
     * @return array|null
478
     */
479
    protected function getFileReferenceData($uid, $raw = false)
480
    {
481
        if (!$raw
482
            && ($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
483
            && ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend()
484
        ) {
485
            $fileReferenceData = BackendUtility::getRecordWSOL('sys_file_reference', $uid);
486
        } elseif (!$raw && is_object($GLOBALS['TSFE'])) {
487
            $fileReferenceData = $GLOBALS['TSFE']->sys_page->checkRecord('sys_file_reference', $uid);
488
        } else {
489
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference');
490
            $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
491
            $fileReferenceData = $queryBuilder->select('*')
492
                ->from('sys_file_reference')
493
                ->where(
494
                    $queryBuilder->expr()->eq(
495
                        'uid',
496
                        $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
497
                    )
498
                )
499
                ->execute()
500
                ->fetch();
501
        }
502
        return $fileReferenceData;
503
    }
504
505
    /**
506
     * Returns an instance of the FileIndexRepository
507
     *
508
     * @return FileIndexRepository
509
     */
510
    protected function getFileIndexRepository()
511
    {
512
        return FileIndexRepository::getInstance();
513
    }
514
}
515