‪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;
26 use TYPO3\CMS\Core\Resource\Driver\DriverInterface;
33 
37 class ‪StorageRepository implements LoggerAwareInterface
38 {
39  use LoggerAwareTrait;
40 
44  protected ?array ‪$storageRowCache = null;
45 
49  protected ?array ‪$localDriverStorageCache = null;
50 
51  protected readonly string ‪$table;
52 
56  protected array ‪$storageInstances = [];
57 
58  public function ‪__construct(
59  protected readonly EventDispatcherInterface $eventDispatcher,
60  protected readonly ‪DriverRegistry $driverRegistry,
61  ) {
62  $this->table = 'sys_file_storage';
63  }
64 
74  {
75  $allStorages = $this->‪findAll();
76  foreach ($allStorages as $storage) {
77  if ($storage->isDefault()) {
78  return $storage;
79  }
80  }
81  return null;
82  }
83 
84  public function ‪findByUid(int ‪$uid): ?‪ResourceStorage
85  {
86  $this->‪initializeLocalCache();
87  if (isset($this->storageRowCache[‪$uid]) || ‪$uid === 0) {
88  return $this->getStorageObject(‪$uid, $this->storageRowCache[‪$uid] ?? []);
89  }
90  return null;
91  }
92 
99  {
101  return count($parts) === 2 ? $this->‪findByUid((int)$parts[0]) : null;
102  }
103 
104  protected function ‪fetchRecordDataByUid(int ‪$uid): array
105  {
106  $this->‪initializeLocalCache();
107  if (!isset($this->storageRowCache[‪$uid])) {
108  throw new \InvalidArgumentException(sprintf('No storage found with uid "%d".', ‪$uid), 1599235454);
109  }
110 
111  return $this->storageRowCache[‪$uid];
112  }
113 
117  protected function ‪initializeLocalCache(): void
118  {
119  if ($this->storageRowCache === null) {
120  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
121  ->getQueryBuilderForTable($this->table);
122 
123  $result = $queryBuilder
124  ->select('*')
125  ->from($this->table)
126  ->orderBy('name')
127  ->executeQuery();
128 
129  $this->storageRowCache = [];
130  while ($row = $result->fetchAssociative()) {
131  if (!empty($row['uid'])) {
132  $this->storageRowCache[(int)$row['uid']] = $row;
133  }
134  }
135 
136  // if no storage is created before or the user has not access to a storage
137  // $this->storageRowCache would have the value array()
138  // so check if there is any record. If no record is found, create the fileadmin/ storage
139  // selecting just one row is enough
140 
141  if ($this->storageRowCache === []) {
142  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
143  ->getConnectionForTable($this->table);
144 
145  $storageObjectsCount = $connection->count('uid', $this->table, []);
146 
147  if ($storageObjectsCount === 0) {
148  if ($this->createLocalStorage(
149  rtrim(‪$GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] ?? 'fileadmin', '/'),
150  ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'],
151  'relative',
152  'This is the local fileadmin/ directory. This storage mount has been created automatically by TYPO3.',
153  true
154  ) > 0) {
155  // clear Cache to force reloading of storages
156  $this->‪flush();
157  // call self for initialize Cache
158  $this->‪initializeLocalCache();
159  }
160  }
161  }
162  }
163  }
164 
170  public function ‪flush(): void
171  {
172  $this->storageRowCache = null;
173  $this->storageInstances = [];
174  $this->localDriverStorageCache = null;
175  }
176 
183  public function ‪findByStorageType(string $storageType): array
184  {
185  $this->‪initializeLocalCache();
186 
187  $storageObjects = [];
188  foreach ($this->storageRowCache as $storageRow) {
189  if ($storageRow['driver'] !== $storageType) {
190  continue;
191  }
192  if ($this->driverRegistry->driverExists($storageRow['driver'])) {
193  $storageObjects[] = $this->getStorageObject($storageRow['uid'], $storageRow);
194  } else {
195  $this->logger->warning('Could not instantiate storage "{name}" because of missing driver.', ['name' => $storageRow['name']]);
196  }
197  }
198  return $storageObjects;
199  }
200 
207  public function ‪findAll(): array
208  {
209  $this->‪initializeLocalCache();
210 
211  $storageObjects = [];
212  foreach ($this->storageRowCache as $storageRow) {
213  if ($this->driverRegistry->driverExists($storageRow['driver'])) {
214  $storageObjects[] = $this->getStorageObject($storageRow['uid'], $storageRow);
215  } else {
216  $this->logger->warning('Could not instantiate storage "{name}" because of missing driver.', ['name' => $storageRow['name']]);
217  }
218  }
219  return $storageObjects;
220  }
221 
230  public function createLocalStorage(string ‪$name, string $basePath, string $pathType, string $description = '', bool $default = false): int
231  {
232  $caseSensitive = $this->‪testCaseSensitivity($pathType === 'relative' ? ‪Environment::getPublicPath() . '/' . $basePath : $basePath);
233  // create the FlexForm for the driver configuration
234  $flexFormData = [
235  'data' => [
236  'sDEF' => [
237  'lDEF' => [
238  'basePath' => ['vDEF' => rtrim($basePath, '/') . '/'],
239  'pathType' => ['vDEF' => $pathType],
240  'caseSensitive' => ['vDEF' => $caseSensitive],
241  ],
242  ],
243  ],
244  ];
245 
246  $flexFormXml = GeneralUtility::makeInstance(FlexFormTools::class)->flexArray2Xml($flexFormData);
247 
248  // create the record
249  $field_values = [
250  'pid' => 0,
251  'tstamp' => ‪$GLOBALS['EXEC_TIME'],
252  'crdate' => ‪$GLOBALS['EXEC_TIME'],
253  'name' => ‪$name,
254  'description' => $description,
255  'driver' => 'Local',
256  'configuration' => $flexFormXml,
257  'is_online' => 1,
258  'auto_extract_metadata' => 1,
259  'is_browsable' => 1,
260  'is_public' => 1,
261  'is_writable' => 1,
262  'is_default' => $default ? 1 : 0,
263  ];
264 
265  $dbConnection = GeneralUtility::makeInstance(ConnectionPool::class)
266  ->getConnectionForTable($this->table);
267  $dbConnection->insert($this->table, $field_values);
268 
269  // Flush local resourceStorage cache so the storage can be accessed during the same request right away
270  $this->‪flush();
271 
272  return (int)$dbConnection->lastInsertId();
273  }
274 
280  protected function ‪testCaseSensitivity(string $absolutePath): bool
281  {
282  $caseSensitive = true;
283  $path = rtrim($absolutePath, '/') . '/aAbB';
284  $testFileExists = @file_exists($path);
285 
286  // create test file
287  if (!$testFileExists) {
288  // @todo: This misses a test for directory existence, touch does not create
289  // dirs. StorageRepositoryTest stumbles here. It should at least be
290  // sanitized to not touch() a file in a non-existing directory.
291  touch($path);
292  }
293 
294  // do the actual sensitivity check
295  if (@file_exists(strtoupper($path)) && @file_exists(strtolower($path))) {
296  $caseSensitive = false;
297  }
298 
299  // clean filesystem
300  if (!$testFileExists) {
301  unlink($path);
302  }
303 
304  return $caseSensitive;
305  }
306 
315  public function getStorageObject(int ‪$uid, array $recordData = [], ?string &$fileIdentifier = null): ResourceStorage
316  {
317  if (‪$uid === 0 && $fileIdentifier !== null) {
318  ‪$uid = $this->findBestMatchingStorageByLocalPath($fileIdentifier);
319  }
320  if (!isset($this->storageInstances[‪$uid])) {
321  $storageConfiguration = null;
322  $event = $this->eventDispatcher->dispatch(new BeforeResourceStorageInitializationEvent(‪$uid, $recordData, $fileIdentifier));
323  $recordData = $event->getRecord();
324  ‪$uid = $event->getStorageUid();
325  $fileIdentifier = $event->getFileIdentifier();
326  // If the built-in storage with UID=0 is requested:
327  if (‪$uid === 0) {
328  $recordData = [
329  'uid' => 0,
330  'pid' => 0,
331  'name' => 'Fallback Storage',
332  'description' => 'Internal storage, mounting the main TYPO3_site directory.',
333  'driver' => 'Local',
334  'processingfolder' => 'typo3temp/assets/_processed_/',
335  // legacy code
336  'configuration' => '',
337  'is_online' => true,
338  'is_browsable' => true,
339  'is_public' => true,
340  'is_writable' => true,
341  'is_default' => false,
342  ];
343  $storageConfiguration = [
344  'basePath' => ‪Environment::getPublicPath(),
345  'pathType' => 'absolute',
346  ];
347  } elseif ($recordData === [] || (int)$recordData['uid'] !== ‪$uid) {
348  $recordData = $this->‪fetchRecordDataByUid($uid);
349  }
350  $storageObject = $this->createStorageObject($recordData, $storageConfiguration);
351  $storageObject = $this->eventDispatcher
352  ->dispatch(new AfterResourceStorageInitializationEvent($storageObject))
353  ->getStorage();
354  $this->storageInstances[‪$uid] = $storageObject;
355  }
356  return $this->storageInstances[‪$uid];
357  }
358 
368  protected function findBestMatchingStorageByLocalPath(string &$localPath): int
369  {
370  if ($this->localDriverStorageCache === null) {
371  $this->initializeLocalStorageCache();
372  }
373  // normalize path information (`//`, `../`)
374  $localPath = ‪PathUtility::getCanonicalPath($localPath);
375  if (!str_starts_with($localPath, '/')) {
376  $localPath = '/' . $localPath;
377  }
378  $bestMatchStorageUid = 0;
379  $bestMatchLength = 0;
380  foreach ($this->localDriverStorageCache as $storageUid => $basePath) {
381  // try to match (resolved) relative base-path
382  if ($basePath->getRelative() !== null
383  && null !== $commonPrefix = ‪PathUtility::getCommonPrefix([$basePath->getRelative(), $localPath])
384  ) {
385  $matchLength = strlen($commonPrefix);
386  $basePathLength = strlen($basePath->getRelative());
387  if ($matchLength >= $basePathLength && $matchLength > $bestMatchLength) {
388  $bestMatchStorageUid = $storageUid;
389  $bestMatchLength = $matchLength;
390  }
391  }
392  // try to match (resolved) absolute base-path
393  if (null !== $commonPrefix = ‪PathUtility::getCommonPrefix([$basePath->getAbsolute(), $localPath])) {
394  $matchLength = strlen($commonPrefix);
395  $basePathLength = strlen($basePath->getAbsolute());
396  if ($matchLength >= $basePathLength && $matchLength > $bestMatchLength) {
397  $bestMatchStorageUid = $storageUid;
398  $bestMatchLength = $matchLength;
399  }
400  }
401  }
402  if ($bestMatchLength > 0) {
403  // $commonPrefix always has trailing slash, which needs to be excluded
404  // (commonPrefix: /some/path/, localPath: /some/path/file.png --> /file.png; keep leading slash)
405  $localPath = substr($localPath, $bestMatchLength - 1);
406  }
407  return $bestMatchStorageUid;
408  }
409 
413  protected function initializeLocalStorageCache(): void
414  {
415  $this->localDriverStorageCache = [
416  // implicit legacy storage in project's public path
418  ];
419  $storageObjects = $this->‪findByStorageType('Local');
420  foreach ($storageObjects as $localStorage) {
421  $configuration = $localStorage->getConfiguration();
422  if (!isset($configuration['basePath']) || !isset($configuration['pathType'])) {
423  continue;
424  }
425  if ($configuration['pathType'] === 'relative') {
426  $pathType = ‪LocalPath::TYPE_RELATIVE;
427  } elseif ($configuration['pathType'] === 'absolute') {
428  $pathType = ‪LocalPath::TYPE_ABSOLUTE;
429  } else {
430  continue;
431  }
432  $this->localDriverStorageCache[$localStorage->getUid()] = GeneralUtility::makeInstance(
433  LocalPath::class,
434  $configuration['basePath'],
435  $pathType
436  );
437  }
438  }
439 
445  protected function createStorageObject(array $storageRecord, ?array $storageConfiguration = null): ResourceStorage
446  {
447  if (!$storageConfiguration && !empty($storageRecord['configuration'])) {
448  $storageConfiguration = $this->convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
449  }
450  $driverType = $storageRecord['driver'];
451  $driverObject = $this->getDriverObject($driverType, (array)$storageConfiguration);
452  $storageRecord['configuration'] = $storageConfiguration;
453  return GeneralUtility::makeInstance(ResourceStorage::class, $driverObject, $storageRecord, $this->eventDispatcher);
454  }
455 
461  protected function convertFlexFormDataToConfigurationArray(string $flexFormData): array
462  {
463  if ($flexFormData) {
464  return GeneralUtility::makeInstance(FlexFormService::class)->convertFlexFormContentToArray($flexFormData);
465  }
466  return [];
467  }
468 
475  protected function getDriverObject(string $driverIdentificationString, array $driverConfiguration): DriverInterface
476  {
477  $driverClass = $this->driverRegistry->getDriverClass($driverIdentificationString);
479  $driverObject = GeneralUtility::makeInstance($driverClass, $driverConfiguration);
480  return $driverObject;
481  }
482 
486  public function createFromRecord(array $storageRecord): ResourceStorage
487  {
488  return $this->createStorageObject($storageRecord);
489  }
490 }
‪TYPO3\CMS\Core\Utility\PathUtility\getCanonicalPath
‪static string getCanonicalPath(string $path)
Definition: PathUtility.php:365
‪TYPO3\CMS\Core\Resource\StorageRepository\$table
‪readonly string $table
Definition: StorageRepository.php:51
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Core\Resource\StorageRepository\__construct
‪__construct(protected readonly EventDispatcherInterface $eventDispatcher, protected readonly DriverRegistry $driverRegistry,)
Definition: StorageRepository.php:58
‪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:30
‪TYPO3\CMS\Core\Resource\StorageRepository\fetchRecordDataByUid
‪fetchRecordDataByUid(int $uid)
Definition: StorageRepository.php:104
‪TYPO3\CMS\Core\Resource\StorageRepository\findByStorageType
‪list< ResourceStorage > findByStorageType(string $storageType)
Definition: StorageRepository.php:183
‪TYPO3\CMS\Core\Utility\PathUtility\getCommonPrefix
‪static getCommonPrefix(array $paths)
Definition: PathUtility.php:166
‪TYPO3\CMS\Core\Resource\StorageRepository\findAll
‪list< ResourceStorage > findAll()
Definition: StorageRepository.php:207
‪TYPO3\CMS\Core\Service\FlexFormService
Definition: FlexFormService.php:25
‪TYPO3\CMS\Core\Resource\StorageRepository\testCaseSensitivity
‪int< 0, createLocalStorage(string $name, string $basePath, string $pathType, string $description='', bool $default=false):int { $caseSensitive=$this-> testCaseSensitivity($pathType==='relative' ? Environment::getPublicPath() . '/' . $basePath :$basePath)
‪TYPO3\CMS\Core\Resource\LocalPath\TYPE_ABSOLUTE
‪const TYPE_ABSOLUTE
Definition: LocalPath.php:29
‪TYPO3\CMS\Core\Resource\StorageRepository\findByCombinedIdentifier
‪findByCombinedIdentifier(string $identifier)
Definition: StorageRepository.php:98
‪TYPO3\CMS\Core\Resource\Event\AfterResourceStorageInitializationEvent
Definition: AfterResourceStorageInitializationEvent.php:28
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools
Definition: FlexFormTools.php:42
‪TYPO3\CMS\Core\Resource\StorageRepository\getDefaultStorage
‪getDefaultStorage()
Definition: StorageRepository.php:73
‪TYPO3\CMS\Core\Resource\StorageRepository
Definition: StorageRepository.php:38
‪$name
‪$name
Definition: phpIntegrityChecker.php:235
‪TYPO3\CMS\Core\Resource\StorageRepository\$storageInstances
‪array $storageInstances
Definition: StorageRepository.php:56
‪TYPO3\CMS\Core\Resource\StorageRepository\$localDriverStorageCache
‪array $localDriverStorageCache
Definition: StorageRepository.php:49
‪TYPO3\CMS\Core\Resource\StorageRepository\initializeLocalCache
‪initializeLocalCache()
Definition: StorageRepository.php:117
‪TYPO3\CMS\Core\Resource
Definition: generateMimeTypes.php:54
‪TYPO3\CMS\Core\Resource\Driver\DriverRegistry
Definition: DriverRegistry.php:24
‪TYPO3\CMS\Core\Resource\ResourceStorage
Definition: ResourceStorage.php:129
‪TYPO3\CMS\Webhooks\Message\$uid
‪identifier readonly int $uid
Definition: PageModificationMessage.php:35
‪TYPO3\CMS\Core\Resource\StorageRepository\flush
‪flush()
Definition: StorageRepository.php:170
‪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\Core\Environment
Definition: Environment.php:41
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Resource\StorageRepository\$storageRowCache
‪array $storageRowCache
Definition: StorageRepository.php:44
‪TYPO3\CMS\Core\Resource\StorageRepository\findByUid
‪findByUid(int $uid)
Definition: StorageRepository.php:84
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:822
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37