‪TYPO3CMS  ‪main
StorageRepository.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 
20 use Psr\EventDispatcher\EventDispatcherInterface;
21 use Psr\Log\LoggerAwareInterface;
22 use Psr\Log\LoggerAwareTrait;
33 
37 class ‪StorageRepository implements LoggerAwareInterface
38 {
39  use LoggerAwareTrait;
40 
44  protected ‪$storageRowCache;
45 
49  protected $localDriverStorageCache;
50 
54  protected $table = 'sys_file_storage';
55 
59  protected $driverRegistry;
60 
64  protected $eventDispatcher;
65 
69  protected $storageInstances;
70 
71  public function __construct(EventDispatcherInterface $eventDispatcher, ‪DriverRegistry $driverRegistry)
72  {
73  $this->‪eventDispatcher = $eventDispatcher;
74  $this->‪driverRegistry = $driverRegistry;
75  }
76 
85  public function ‪getDefaultStorage(): ?‪ResourceStorage
86  {
87  $allStorages = $this->‪findAll();
88  foreach ($allStorages as $storage) {
89  if ($storage->isDefault()) {
90  return $storage;
91  }
92  }
93  return null;
94  }
95 
96  public function ‪findByUid(int ‪$uid): ?ResourceStorage
97  {
98  $this->‪initializeLocalCache();
99  if (isset($this->storageRowCache[‪$uid]) || ‪$uid === 0) {
100  return $this->‪getStorageObject($uid, $this->storageRowCache[‪$uid] ?? []);
101  }
102  return null;
103  }
104 
111  {
113  return count($parts) === 2 ? $this->‪findByUid((int)$parts[0]) : null;
114  }
115 
116  protected function ‪fetchRecordDataByUid(int ‪$uid): array
117  {
118  $this->‪initializeLocalCache();
119  if (!isset($this->storageRowCache[‪$uid])) {
120  throw new \InvalidArgumentException(sprintf('No storage found with uid "%d".', ‪$uid), 1599235454);
121  }
122 
123  return $this->storageRowCache[‪$uid];
124  }
125 
129  protected function ‪initializeLocalCache()
130  {
131  if ($this->storageRowCache === null) {
132  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
133  ->getQueryBuilderForTable($this->table);
134 
135  $result = $queryBuilder
136  ->select('*')
137  ->from($this->table)
138  ->orderBy('name')
139  ->executeQuery();
140 
141  $this->storageRowCache = [];
142  while ($row = $result->fetchAssociative()) {
143  if (!empty($row['uid'])) {
144  $this->storageRowCache[$row['uid']] = $row;
145  }
146  }
147 
148  // if no storage is created before or the user has not access to a storage
149  // $this->storageRowCache would have the value array()
150  // so check if there is any record. If no record is found, create the fileadmin/ storage
151  // selecting just one row is enough
152 
153  if ($this->storageRowCache === []) {
154  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
155  ->getConnectionForTable($this->table);
156 
157  $storageObjectsCount = $connection->count('uid', $this->table, []);
158 
159  if ($storageObjectsCount === 0) {
160  if ($this->‪createLocalStorage(
161  rtrim(‪$GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] ?? 'fileadmin', '/'),
162  ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'],
163  'relative',
164  'This is the local fileadmin/ directory. This storage mount has been created automatically by TYPO3.',
165  true
166  ) > 0) {
167  // clear Cache to force reloading of storages
168  $this->‪flush();
169  // call self for initialize Cache
170  $this->‪initializeLocalCache();
171  }
172  }
173  }
174  }
175  }
176 
182  public function ‪flush(): void
183  {
184  $this->storageRowCache = null;
185  $this->storageInstances = null;
186  $this->localDriverStorageCache = null;
187  }
188 
195  public function ‪findByStorageType($storageType)
196  {
197  $this->‪initializeLocalCache();
198 
199  $storageObjects = [];
200  foreach ($this->storageRowCache as $storageRow) {
201  if ($storageRow['driver'] !== $storageType) {
202  continue;
203  }
204  if ($this->‪driverRegistry->driverExists($storageRow['driver'])) {
205  $storageObjects[] = $this->‪getStorageObject($storageRow['uid'], $storageRow);
206  } else {
207  $this->logger->warning('Could not instantiate storage "{name}" because of missing driver.', ['name' => $storageRow['name']]);
208  }
209  }
210  return $storageObjects;
211  }
212 
219  public function ‪findAll()
220  {
221  $this->‪initializeLocalCache();
222 
223  $storageObjects = [];
224  foreach ($this->storageRowCache as $storageRow) {
225  if ($this->‪driverRegistry->driverExists($storageRow['driver'])) {
226  $storageObjects[] = $this->‪getStorageObject($storageRow['uid'], $storageRow);
227  } else {
228  $this->logger->warning('Could not instantiate storage "{name}" because of missing driver.', ['name' => $storageRow['name']]);
229  }
230  }
231  return $storageObjects;
232  }
233 
244  public function ‪createLocalStorage($name, $basePath, $pathType, $description = '', $default = false)
245  {
246  $caseSensitive = $this->‪testCaseSensitivity($pathType === 'relative' ? ‪Environment::getPublicPath() . '/' . $basePath : $basePath);
247  // create the FlexForm for the driver configuration
248  $flexFormData = [
249  'data' => [
250  'sDEF' => [
251  'lDEF' => [
252  'basePath' => ['vDEF' => rtrim($basePath, '/') . '/'],
253  'pathType' => ['vDEF' => $pathType],
254  'caseSensitive' => ['vDEF' => $caseSensitive],
255  ],
256  ],
257  ],
258  ];
259 
260  $flexFormXml = GeneralUtility::makeInstance(FlexFormTools::class)->flexArray2Xml($flexFormData, true);
261 
262  // create the record
263  $field_values = [
264  'pid' => 0,
265  'tstamp' => ‪$GLOBALS['EXEC_TIME'],
266  'crdate' => ‪$GLOBALS['EXEC_TIME'],
267  'name' => $name,
268  'description' => $description,
269  'driver' => 'Local',
270  'configuration' => $flexFormXml,
271  'is_online' => 1,
272  'is_browsable' => 1,
273  'is_public' => 1,
274  'is_writable' => 1,
275  'is_default' => $default ? 1 : 0,
276  ];
277 
278  $dbConnection = GeneralUtility::makeInstance(ConnectionPool::class)
279  ->getConnectionForTable($this->table);
280  $dbConnection->insert($this->table, $field_values);
281 
282  // Flush local resourceStorage cache so the storage can be accessed during the same request right away
283  $this->‪flush();
284 
285  return (int)$dbConnection->lastInsertId($this->table);
286  }
287 
294  protected function ‪testCaseSensitivity($absolutePath)
295  {
296  $caseSensitive = true;
297  $path = rtrim($absolutePath, '/') . '/aAbB';
298  $testFileExists = @file_exists($path);
299 
300  // create test file
301  if (!$testFileExists) {
302  // @todo: This misses a test for directory existence, touch does not create
303  // dirs. StorageRepositoryTest stumbles here. It should at least be
304  // sanitized to not touch() a file in a non-existing directory.
305  touch($path);
306  }
307 
308  // do the actual sensitivity check
309  if (@file_exists(strtoupper($path)) && @file_exists(strtolower($path))) {
310  $caseSensitive = false;
311  }
312 
313  // clean filesystem
314  if (!$testFileExists) {
315  unlink($path);
316  }
317 
318  return $caseSensitive;
319  }
320 
330  public function ‪getStorageObject(‪$uid, array $recordData = [], &$fileIdentifier = null): ResourceStorage
331  {
332  if (!is_numeric(‪$uid)) {
333  throw new \InvalidArgumentException('The UID of storage has to be numeric. UID given: "' . ‪$uid . '"', 1314085991);
334  }
335  ‪$uid = (int)‪$uid;
336  if (‪$uid === 0 && $fileIdentifier !== null) {
337  ‪$uid = $this->‪findBestMatchingStorageByLocalPath($fileIdentifier);
338  }
339  if (empty($this->storageInstances[‪$uid])) {
340  $storageConfiguration = null;
341  $event = $this->‪eventDispatcher->dispatch(new BeforeResourceStorageInitializationEvent(‪$uid, $recordData, $fileIdentifier));
342  $recordData = $event->getRecord();
343  ‪$uid = $event->getStorageUid();
344  $fileIdentifier = $event->getFileIdentifier();
345  // If the built-in storage with UID=0 is requested:
346  if (‪$uid === 0) {
347  $recordData = [
348  'uid' => 0,
349  'pid' => 0,
350  'name' => 'Fallback Storage',
351  'description' => 'Internal storage, mounting the main TYPO3_site directory.',
352  'driver' => 'Local',
353  'processingfolder' => 'typo3temp/assets/_processed_/',
354  // legacy code
355  'configuration' => '',
356  'is_online' => true,
357  'is_browsable' => true,
358  'is_public' => true,
359  'is_writable' => true,
360  'is_default' => false,
361  ];
362  $storageConfiguration = [
363  'basePath' => ‪Environment::getPublicPath(),
364  'pathType' => 'absolute',
365  ];
366  } elseif ($recordData === [] || (int)$recordData['uid'] !== ‪$uid) {
367  $recordData = $this->‪fetchRecordDataByUid($uid);
368  }
369  $storageObject = $this->‪createStorageObject($recordData, $storageConfiguration);
370  $storageObject = $this->‪eventDispatcher
371  ->dispatch(new AfterResourceStorageInitializationEvent($storageObject))
372  ->getStorage();
373  $this->storageInstances[‪$uid] = $storageObject;
374  }
375  return $this->storageInstances[‪$uid];
376  }
377 
386  protected function ‪findBestMatchingStorageByLocalPath(&$localPath): int
387  {
388  if ($this->localDriverStorageCache === null) {
390  }
391  // normalize path information (`//`, `../`)
392  $localPath = ‪PathUtility::getCanonicalPath($localPath);
393  if (!str_starts_with($localPath, '/')) {
394  $localPath = '/' . $localPath;
395  }
396  $bestMatchStorageUid = 0;
397  $bestMatchLength = 0;
398  foreach ($this->localDriverStorageCache as $storageUid => $basePath) {
399  // try to match (resolved) relative base-path
400  if ($basePath->getRelative() !== null
401  && null !== $commonPrefix = ‪PathUtility::getCommonPrefix([$basePath->getRelative(), $localPath])
402  ) {
403  $matchLength = strlen($commonPrefix);
404  $basePathLength = strlen($basePath->getRelative());
405  if ($matchLength >= $basePathLength && $matchLength > $bestMatchLength) {
406  $bestMatchStorageUid = $storageUid;
407  $bestMatchLength = $matchLength;
408  }
409  }
410  // try to match (resolved) absolute base-path
411  if (null !== $commonPrefix = ‪PathUtility::getCommonPrefix([$basePath->getAbsolute(), $localPath])) {
412  $matchLength = strlen($commonPrefix);
413  $basePathLength = strlen($basePath->getAbsolute());
414  if ($matchLength >= $basePathLength && $matchLength > $bestMatchLength) {
415  $bestMatchStorageUid = $storageUid;
416  $bestMatchLength = $matchLength;
417  }
418  }
419  }
420  if ($bestMatchLength > 0) {
421  // $commonPrefix always has trailing slash, which needs to be excluded
422  // (commonPrefix: /some/path/, localPath: /some/path/file.png --> /file.png; keep leading slash)
423  $localPath = substr($localPath, $bestMatchLength - 1);
424  }
425  return $bestMatchStorageUid;
426  }
427 
431  protected function ‪initializeLocalStorageCache(): void
432  {
433  $this->localDriverStorageCache = [
434  // implicit legacy storage in project's public path
436  ];
437  $storageObjects = $this->‪findByStorageType('Local');
438  foreach ($storageObjects as $localStorage) {
439  $configuration = $localStorage->getConfiguration();
440  if (!isset($configuration['basePath']) || !isset($configuration['pathType'])) {
441  continue;
442  }
443  if ($configuration['pathType'] === 'relative') {
444  $pathType = ‪LocalPath::TYPE_RELATIVE;
445  } elseif ($configuration['pathType'] === 'absolute') {
446  $pathType = ‪LocalPath::TYPE_ABSOLUTE;
447  } else {
448  continue;
449  }
450  $this->localDriverStorageCache[$localStorage->getUid()] = GeneralUtility::makeInstance(
451  LocalPath::class,
452  $configuration['basePath'],
453  $pathType
454  );
455  }
456  }
457 
463  protected function ‪createStorageObject(array $storageRecord, array $storageConfiguration = null): ‪ResourceStorage
464  {
465  if (!$storageConfiguration && !empty($storageRecord['configuration'])) {
466  $storageConfiguration = $this->‪convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
467  }
468  $driverType = $storageRecord['driver'];
469  $driverObject = $this->‪getDriverObject($driverType, (array)$storageConfiguration);
470  $storageRecord['configuration'] = $storageConfiguration;
471  return GeneralUtility::makeInstance(ResourceStorage::class, $driverObject, $storageRecord, $this->‪eventDispatcher);
472  }
473 
479  protected function ‪convertFlexFormDataToConfigurationArray(string $flexFormData): array
480  {
481  if ($flexFormData) {
482  return GeneralUtility::makeInstance(FlexFormService::class)->convertFlexFormContentToArray($flexFormData);
483  }
484  return [];
485  }
486 
493  protected function ‪getDriverObject(string $driverIdentificationString, array $driverConfiguration): ‪DriverInterface
494  {
495  $driverClass = $this->‪driverRegistry->getDriverClass($driverIdentificationString);
497  $driverObject = GeneralUtility::makeInstance($driverClass, $driverConfiguration);
498  return $driverObject;
499  }
500 
504  public function ‪createFromRecord(array $storageRecord): ‪ResourceStorage
505  {
506  return $this->‪createStorageObject($storageRecord);
507  }
508 }
‪TYPO3\CMS\Core\Resource\StorageRepository\createStorageObject
‪createStorageObject(array $storageRecord, array $storageConfiguration=null)
Definition: StorageRepository.php:457
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:916
‪TYPO3\CMS\Core\Utility\PathUtility\getCanonicalPath
‪static string getCanonicalPath(string $path)
Definition: PathUtility.php:364
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Core\Resource\StorageRepository\convertFlexFormDataToConfigurationArray
‪array convertFlexFormDataToConfigurationArray(string $flexFormData)
Definition: StorageRepository.php:473
‪TYPO3\CMS\Core\Resource\StorageRepository\getStorageObject
‪getStorageObject($uid, array $recordData=[], &$fileIdentifier=null)
Definition: StorageRepository.php:324
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static getPublicPath()
Definition: Environment.php:187
‪TYPO3\CMS\Core\Resource\LocalPath\TYPE_RELATIVE
‪const TYPE_RELATIVE
Definition: LocalPath.php:31
‪TYPO3\CMS\Core\Resource\StorageRepository\fetchRecordDataByUid
‪fetchRecordDataByUid(int $uid)
Definition: StorageRepository.php:110
‪TYPO3\CMS\Core\Resource\Driver\DriverInterface
Definition: DriverInterface.php:23
‪TYPO3\CMS\Core\Utility\PathUtility\getCommonPrefix
‪static getCommonPrefix(array $paths)
Definition: PathUtility.php:165
‪TYPO3\CMS\Core\Resource\StorageRepository\testCaseSensitivity
‪bool testCaseSensitivity($absolutePath)
Definition: StorageRepository.php:288
‪TYPO3\CMS\Core\Resource\StorageRepository\getDriverObject
‪getDriverObject(string $driverIdentificationString, array $driverConfiguration)
Definition: StorageRepository.php:487
‪TYPO3\CMS\Core\Service\FlexFormService
Definition: FlexFormService.php:25
‪TYPO3\CMS\Core\Resource\StorageRepository\findByStorageType
‪ResourceStorage[] findByStorageType($storageType)
Definition: StorageRepository.php:189
‪TYPO3\CMS\Core\Resource\LocalPath\TYPE_ABSOLUTE
‪const TYPE_ABSOLUTE
Definition: LocalPath.php:30
‪TYPO3\CMS\Core\Resource\StorageRepository\findByCombinedIdentifier
‪findByCombinedIdentifier(string $identifier)
Definition: StorageRepository.php:104
‪TYPO3\CMS\Core\Resource\StorageRepository\$storageRowCache
‪array null $storageRowCache
Definition: StorageRepository.php:43
‪TYPO3\CMS\Core\Resource\Event\AfterResourceStorageInitializationEvent
Definition: AfterResourceStorageInitializationEvent.php:28
‪TYPO3\CMS\Core\Resource\StorageRepository\driverRegistry
‪$this driverRegistry
Definition: StorageRepository.php:68
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools
Definition: FlexFormTools.php:57
‪TYPO3\CMS\Core\Resource\StorageRepository\getDefaultStorage
‪getDefaultStorage()
Definition: StorageRepository.php:79
‪TYPO3\CMS\Core\Resource\StorageRepository
Definition: StorageRepository.php:38
‪TYPO3\CMS\Core\Resource\StorageRepository\eventDispatcher
‪array< int, $localDriverStorageCache;protected string $table='sys_file_storage';protected DriverRegistry $driverRegistry;protected EventDispatcherInterface $eventDispatcher;protected ResourceStorage[]|null $storageInstances;public function __construct(EventDispatcherInterface $eventDispatcher, DriverRegistry $driverRegistry) { $this-> eventDispatcher
Definition: StorageRepository.php:67
‪TYPO3\CMS\Core\Resource\StorageRepository\findBestMatchingStorageByLocalPath
‪findBestMatchingStorageByLocalPath(&$localPath)
Definition: StorageRepository.php:380
‪TYPO3\CMS\Core\Resource\StorageRepository\createLocalStorage
‪int createLocalStorage($name, $basePath, $pathType, $description='', $default=false)
Definition: StorageRepository.php:238
‪TYPO3\CMS\Core\Resource\StorageRepository\initializeLocalCache
‪initializeLocalCache()
Definition: StorageRepository.php:123
‪TYPO3\CMS\Core\Resource\StorageRepository\initializeLocalStorageCache
‪initializeLocalStorageCache()
Definition: StorageRepository.php:425
‪TYPO3\CMS\Core\Resource
Definition: generateMimeTypes.php:52
‪TYPO3\CMS\Core\Resource\Driver\DriverRegistry
Definition: DriverRegistry.php:24
‪TYPO3\CMS\Core\Resource\ResourceStorage
Definition: ResourceStorage.php:127
‪TYPO3\CMS\Webhooks\Message\$uid
‪identifier readonly int $uid
Definition: PageModificationMessage.php:35
‪TYPO3\CMS\Core\Resource\StorageRepository\createFromRecord
‪createFromRecord(array $storageRecord)
Definition: StorageRepository.php:498
‪TYPO3\CMS\Core\Resource\StorageRepository\flush
‪flush()
Definition: StorageRepository.php:176
‪TYPO3\CMS\Core\Resource\Event\BeforeResourceStorageInitializationEvent
Definition: BeforeResourceStorageInitializationEvent.php:27
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Resource\LocalPath
Definition: LocalPath.php:29
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:51
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Core\Resource\StorageRepository\findByUid
‪findByUid(int $uid)
Definition: StorageRepository.php:90
‪TYPO3\CMS\Core\Resource\StorageRepository\findAll
‪ResourceStorage[] findAll()
Definition: StorageRepository.php:213
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37