Completed
Push — master ( 7a0b3c...09a41b )
by
unknown
19:44
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 TYPO3\CMS\Backend\Utility\BackendUtility;
19
use TYPO3\CMS\Core\Collection\CollectionInterface;
20
use TYPO3\CMS\Core\Core\Environment;
21
use TYPO3\CMS\Core\Database\ConnectionPool;
22
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
23
use TYPO3\CMS\Core\Resource\Collection\FileCollectionRegistry;
24
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
25
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
26
use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
27
use TYPO3\CMS\Core\Service\FlexFormService;
28
use TYPO3\CMS\Core\SingletonInterface;
29
use TYPO3\CMS\Core\Utility\GeneralUtility;
30
use TYPO3\CMS\Core\Utility\MathUtility;
31
use TYPO3\CMS\Core\Utility\PathUtility;
32
33
/**
34
 * Factory class for FAL objects
35
 */
36
class ResourceFactory implements SingletonInterface
37
{
38
    /**
39
     * @var Collection\AbstractFileCollection[]
40
     */
41
    protected $collectionInstances = [];
42
43
    /**
44
     * @var File[]
45
     */
46
    protected $fileInstances = [];
47
48
    /**
49
     * @var FileReference[]
50
     */
51
    protected $fileReferenceInstances = [];
52
53
    /**
54
     * @var StorageRepository
55
     */
56
    protected $storageRepository;
57
58
    public function __construct(StorageRepository $storageRepository)
59
    {
60
        $this->storageRepository = $storageRepository;
61
    }
62
63
    /**
64
     * Returns the Default Storage
65
     *
66
     * The Default Storage is considered to be the replacement for the fileadmin/ construct.
67
     * It is automatically created with the setting fileadminDir from install tool.
68
     * getDefaultStorage->getDefaultFolder() will get you fileadmin/user_upload/ in a standard
69
     * TYPO3 installation.
70
     *
71
     * @return ResourceStorage|null
72
     * @internal It is recommended to use the StorageRepository in the future, and this is only kept as backwards-compat layer
73
     */
74
    public function getDefaultStorage()
75
    {
76
        return $this->storageRepository->getDefaultStorage();
77
    }
78
79
    /**
80
     * Creates an instance of the storage from given UID. The $recordData can
81
     * be supplied to increase performance.
82
     *
83
     * @param int $uid The uid of the storage to instantiate.
84
     * @param array $recordData The record row from database.
85
     * @param string $fileIdentifier Identifier for a file. Used for auto-detection of a storage, but only if $uid === 0 (Local default storage) is used
86
     *
87
     * @throws \InvalidArgumentException
88
     * @return ResourceStorage
89
     * @internal It is recommended to use the StorageRepository in the future, and this is only kept as backwards-compat layer
90
     */
91
    public function getStorageObject($uid, array $recordData = [], &$fileIdentifier = null)
92
    {
93
        return $this->storageRepository->getStorageObject($uid, $recordData, $fileIdentifier);
94
    }
95
96
    /**
97
     * Converts a flexform data string to a flat array with key value pairs.
98
     *
99
     * It is recommended to not use this functionality directly, and instead implement this code yourself, as this
100
     * code has nothing to do with a Public API for Resources.
101
     *
102
     * @param string $flexFormData
103
     * @return array Array with key => value pairs of the field data in the FlexForm
104
     * @internal
105
     */
106
    public function convertFlexFormDataToConfigurationArray($flexFormData)
107
    {
108
        $configuration = [];
109
        if ($flexFormData) {
110
            $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
111
            $configuration = $flexFormService->convertFlexFormContentToArray($flexFormData);
112
        }
113
        return $configuration;
114
    }
115
116
    /**
117
     * Creates an instance of the collection from given UID. The $recordData can be supplied to increase performance.
118
     *
119
     * @param int $uid The uid of the collection to instantiate.
120
     * @param array $recordData The record row from database.
121
     *
122
     * @throws \InvalidArgumentException
123
     * @return Collection\AbstractFileCollection
124
     */
125
    public function getCollectionObject($uid, array $recordData = [])
126
    {
127
        if (!is_numeric($uid)) {
0 ignored issues
show
introduced by
The condition is_numeric($uid) is always true.
Loading history...
128
            throw new \InvalidArgumentException('The UID of collection has to be numeric. UID given: "' . $uid . '"', 1314085999);
129
        }
130
        if (!$this->collectionInstances[$uid]) {
131
            // Get mount data if not already supplied as argument to this function
132
            if (empty($recordData) || $recordData['uid'] !== $uid) {
133
                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_collection');
134
                $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
135
                $recordData = $queryBuilder->select('*')
136
                    ->from('sys_file_collection')
137
                    ->where(
138
                        $queryBuilder->expr()->eq(
139
                            'uid',
140
                            $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
141
                        )
142
                    )
143
                    ->execute()
144
                    ->fetch();
145
                if (empty($recordData)) {
146
                    throw new \InvalidArgumentException('No collection found for given UID: "' . $uid . '"', 1314085992);
147
                }
148
            }
149
            $collectionObject = $this->createCollectionObject($recordData);
150
            $this->collectionInstances[$uid] = $collectionObject;
151
        }
152
        return $this->collectionInstances[$uid];
153
    }
154
155
    /**
156
     * Creates a collection object.
157
     *
158
     * @param array $collectionData The database row of the sys_file_collection record.
159
     * @return Collection\AbstractFileCollection|CollectionInterface
160
     */
161
    public function createCollectionObject(array $collectionData)
162
    {
163
        /** @var Collection\FileCollectionRegistry $registry */
164
        $registry = GeneralUtility::makeInstance(FileCollectionRegistry::class);
165
166
        /** @var \TYPO3\CMS\Core\Collection\AbstractRecordCollection $class */
167
        $class = $registry->getFileCollectionClass($collectionData['type']);
168
169
        return $class::create($collectionData);
170
    }
171
172
    /**
173
     * Creates a storage object from a storage database row.
174
     *
175
     * @param array $storageRecord
176
     * @param array|null $storageConfiguration Storage configuration (if given, this won't be extracted from the FlexForm value but the supplied array used instead)
177
     * @return ResourceStorage
178
     * @internal It is recommended to use the StorageRepository in the future, and this is only kept as backwards-compat layer
179
     */
180
    public function createStorageObject(array $storageRecord, array $storageConfiguration = null)
181
    {
182
        return $this->storageRepository->createStorageObject($storageRecord, $storageConfiguration);
183
    }
184
185
    /**
186
     * Creates a folder to directly access (a part of) a storage.
187
     *
188
     * @param ResourceStorage $storage The storage the folder belongs to
189
     * @param string $identifier The path to the folder. Might also be a simple unique string, depending on the storage driver.
190
     * @param string $name The name of the folder (e.g. the folder name)
191
     * @return Folder
192
     * @internal it is recommended to access the ResourceStorage object directly and access ->getFolder($identifier) this method is kept for backwards compatibility
193
     */
194
    public function createFolderObject(ResourceStorage $storage, $identifier, $name)
195
    {
196
        return GeneralUtility::makeInstance(Folder::class, $storage, $identifier, $name);
197
    }
198
199
    /**
200
     * Creates an instance of the file given UID. The $fileData can be supplied
201
     * to increase performance.
202
     *
203
     * @param int $uid The uid of the file to instantiate.
204
     * @param array $fileData The record row from database.
205
     *
206
     * @throws \InvalidArgumentException
207
     * @throws Exception\FileDoesNotExistException
208
     * @return File
209
     */
210
    public function getFileObject($uid, array $fileData = [])
211
    {
212
        if (!is_numeric($uid)) {
0 ignored issues
show
introduced by
The condition is_numeric($uid) is always true.
Loading history...
213
            throw new \InvalidArgumentException('The UID of file has to be numeric. UID given: "' . $uid . '"', 1300096564);
214
        }
215
        if (empty($this->fileInstances[$uid])) {
216
            // Fetches data in case $fileData is empty
217
            if (empty($fileData)) {
218
                $fileData = $this->getFileIndexRepository()->findOneByUid($uid);
219
                if ($fileData === false) {
220
                    throw new FileDoesNotExistException('No file found for given UID: ' . $uid, 1317178604);
221
                }
222
            }
223
            $this->fileInstances[$uid] = $this->createFileObject($fileData);
224
        }
225
        return $this->fileInstances[$uid];
226
    }
227
228
    /**
229
     * Gets a file object from an identifier [storage]:[fileId]
230
     *
231
     * @param string $identifier
232
     * @return File|ProcessedFile|null
233
     * @throws \InvalidArgumentException
234
     */
235
    public function getFileObjectFromCombinedIdentifier($identifier)
236
    {
237
        if (!is_string($identifier) || $identifier === '') {
0 ignored issues
show
introduced by
The condition is_string($identifier) is always true.
Loading history...
238
            throw new \InvalidArgumentException('Invalid file identifier given. It must be of type string and not empty. "' . gettype($identifier) . '" given.', 1401732564);
239
        }
240
        $parts = GeneralUtility::trimExplode(':', $identifier);
241
        if (count($parts) === 2) {
242
            $storageUid = $parts[0];
243
            $fileIdentifier = $parts[1];
244
        } else {
245
            // We only got a path: Go into backwards compatibility mode and
246
            // use virtual Storage (uid=0)
247
            $storageUid = 0;
248
            $fileIdentifier = $parts[0];
249
        }
250
        return $this->storageRepository->getStorageObject($storageUid, [], $fileIdentifier)
0 ignored issues
show
Bug introduced by
It seems like $storageUid can also be of type string; however, parameter $uid of TYPO3\CMS\Core\Resource\...ory::getStorageObject() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

250
        return $this->storageRepository->getStorageObject(/** @scrutinizer ignore-type */ $storageUid, [], $fileIdentifier)
Loading history...
251
            ->getFileByIdentifier($fileIdentifier);
252
    }
253
254
    /**
255
     * Gets a file object from storage by file identifier
256
     * If the file is outside of the process folder, it gets indexed and returned as file object afterwards
257
     * If the file is within processing folder, the file object will be directly returned
258
     *
259
     * @param ResourceStorage|int $storage
260
     * @param string $fileIdentifier
261
     * @return File|ProcessedFile|null
262
     * @internal It is recommended to use the StorageRepository in the future, and this is only kept as backwards-compat layer
263
     */
264
    public function getFileObjectByStorageAndIdentifier($storage, &$fileIdentifier)
265
    {
266
        if (!($storage instanceof ResourceStorage)) {
267
            $storage = $this->storageRepository->getStorageObject($storage, [], $fileIdentifier);
268
        }
269
        return $storage->getFileByIdentifier($fileIdentifier);
270
    }
271
272
    /**
273
     * Bulk function, can be used for anything to get a file or folder
274
     *
275
     * 1. It's a UID
276
     * 2. It's a combined identifier
277
     * 3. It's just a path/filename (coming from the oldstyle/backwards compatibility)
278
     *
279
     * Files, previously laid on fileadmin/ or something, will be "mapped" to the storage the file is
280
     * in now. Files like typo3temp/ or typo3conf/ will be moved to the first writable storage
281
     * in its processing folder
282
     *
283
     * $input could be
284
     * - "2:myfolder/myfile.jpg" (combined identifier)
285
     * - "23" (file UID)
286
     * - "uploads/myfile.png" (backwards-compatibility, storage "0")
287
     * - "file:23"
288
     *
289
     * @param string $input
290
     * @return File|Folder|null
291
     */
292
    public function retrieveFileOrFolderObject($input)
293
    {
294
        // Remove Environment::getPublicPath() because absolute paths under Windows systems contain ':'
295
        // This is done in all considered sub functions anyway
296
        $input = str_replace(Environment::getPublicPath() . '/', '', $input);
297
298
        if (GeneralUtility::isFirstPartOfStr($input, 'file:')) {
299
            $input = substr($input, 5);
300
            return $this->retrieveFileOrFolderObject($input);
301
        }
302
        if (MathUtility::canBeInterpretedAsInteger($input)) {
303
            return $this->getFileObject($input);
0 ignored issues
show
Bug introduced by
$input of type string is incompatible with the type integer expected by parameter $uid of TYPO3\CMS\Core\Resource\...actory::getFileObject(). ( Ignorable by Annotation )

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

303
            return $this->getFileObject(/** @scrutinizer ignore-type */ $input);
Loading history...
304
        }
305
        if (strpos($input, ':') > 0) {
306
            [$prefix] = explode(':', $input);
307
            if (MathUtility::canBeInterpretedAsInteger($prefix)) {
308
                // path or folder in a valid storageUID
309
                return $this->getObjectFromCombinedIdentifier($input);
310
            }
311
            if ($prefix === 'EXT') {
312
                $input = GeneralUtility::getFileAbsFileName($input);
313
                if (empty($input)) {
314
                    return null;
315
                }
316
317
                $input = PathUtility::getRelativePath(Environment::getPublicPath() . '/', PathUtility::dirname($input)) . PathUtility::basename($input);
318
                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...
319
            }
320
            return null;
321
        }
322
        // this is a backwards-compatible way to access "0-storage" files or folders
323
        // eliminate double slashes, /./ and /../
324
        $input = PathUtility::getCanonicalPath(ltrim($input, '/'));
325
        if (@is_file(Environment::getPublicPath() . '/' . $input)) {
326
            // only the local file
327
            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...
328
        }
329
        // only the local path
330
        return $this->getFolderObjectFromCombinedIdentifier($input);
331
    }
332
333
    /**
334
     * Gets a folder object from an identifier [storage]:[fileId]
335
     *
336
     * @TODO check naming, inserted by SteffenR while working on filelist
337
     * @param string $identifier
338
     * @return Folder
339
     */
340
    public function getFolderObjectFromCombinedIdentifier($identifier)
341
    {
342
        $parts = GeneralUtility::trimExplode(':', $identifier);
343
        if (count($parts) === 2) {
344
            $storageUid = $parts[0];
345
            $folderIdentifier = $parts[1];
346
        } else {
347
            // We only got a path: Go into backwards compatibility mode and
348
            // use virtual Storage (uid=0)
349
            $storageUid = 0;
350
351
            // please note that getStorageObject() might modify $folderIdentifier when
352
            // auto-detecting the best-matching storage to use
353
            $folderIdentifier = $parts[0];
354
            // make sure to not use an absolute path, and remove Environment::getPublicPath if it is prepended
355
            if (GeneralUtility::isFirstPartOfStr($folderIdentifier, Environment::getPublicPath() . '/')) {
356
                $folderIdentifier = PathUtility::stripPathSitePrefix($parts[0]);
357
            }
358
        }
359
        return $this->storageRepository->getStorageObject($storageUid, [], $folderIdentifier)->getFolder($folderIdentifier);
0 ignored issues
show
Bug introduced by
It seems like $storageUid can also be of type string; however, parameter $uid of TYPO3\CMS\Core\Resource\...ory::getStorageObject() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

359
        return $this->storageRepository->getStorageObject(/** @scrutinizer ignore-type */ $storageUid, [], $folderIdentifier)->getFolder($folderIdentifier);
Loading history...
360
    }
361
362
    /**
363
     * Gets a storage object from a combined identifier
364
     *
365
     * @param string $identifier An identifier of the form [storage uid]:[object identifier]
366
     * @return ResourceStorage
367
     * @internal It is recommended to use the StorageRepository in the future, and this is only kept as backwards-compat layer
368
     */
369
    public function getStorageObjectFromCombinedIdentifier($identifier)
370
    {
371
        $parts = GeneralUtility::trimExplode(':', $identifier);
372
        $storageUid = count($parts) === 2 ? $parts[0] : null;
373
        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

373
        return $this->storageRepository->findByUid(/** @scrutinizer ignore-type */ $storageUid);
Loading history...
374
    }
375
376
    /**
377
     * Gets a file or folder object.
378
     *
379
     * @param string $identifier
380
     *
381
     * @throws Exception\ResourceDoesNotExistException
382
     * @return FileInterface|Folder
383
     */
384
    public function getObjectFromCombinedIdentifier($identifier)
385
    {
386
        [$storageId, $objectIdentifier] = GeneralUtility::trimExplode(':', $identifier);
387
        $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

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