‪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  {
100  $parts = GeneralUtility::trimExplode(':', ‪$identifier);
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  'is_browsable' => 1,
259  'is_public' => 1,
260  'is_writable' => 1,
261  'is_default' => $default ? 1 : 0,
262  ];
263 
264  $dbConnection = GeneralUtility::makeInstance(ConnectionPool::class)
265  ->getConnectionForTable($this->table);
266  $dbConnection->insert($this->table, $field_values);
267 
268  // Flush local resourceStorage cache so the storage can be accessed during the same request right away
269  $this->‪flush();
270 
271  return (int)$dbConnection->lastInsertId($this->table);
272  }
273 
279  protected function ‪testCaseSensitivity(string $absolutePath): bool
280  {
281  $caseSensitive = true;
282  $path = rtrim($absolutePath, '/') . '/aAbB';
283  $testFileExists = @file_exists($path);
284 
285  // create test file
286  if (!$testFileExists) {
287  // @todo: This misses a test for directory existence, touch does not create
288  // dirs. StorageRepositoryTest stumbles here. It should at least be
289  // sanitized to not touch() a file in a non-existing directory.
290  touch($path);
291  }
292 
293  // do the actual sensitivity check
294  if (@file_exists(strtoupper($path)) && @file_exists(strtolower($path))) {
295  $caseSensitive = false;
296  }
297 
298  // clean filesystem
299  if (!$testFileExists) {
300  unlink($path);
301  }
302 
303  return $caseSensitive;
304  }
305 
314  public function getStorageObject(int ‪$uid, array $recordData = [], ?string &$fileIdentifier = null): ResourceStorage
315  {
316  if (‪$uid === 0 && $fileIdentifier !== null) {
317  ‪$uid = $this->findBestMatchingStorageByLocalPath($fileIdentifier);
318  }
319  if (!isset($this->storageInstances[‪$uid])) {
320  $storageConfiguration = null;
321  $event = $this->eventDispatcher->dispatch(new BeforeResourceStorageInitializationEvent(‪$uid, $recordData, $fileIdentifier));
322  $recordData = $event->getRecord();
323  ‪$uid = $event->getStorageUid();
324  $fileIdentifier = $event->getFileIdentifier();
325  // If the built-in storage with UID=0 is requested:
326  if (‪$uid === 0) {
327  $recordData = [
328  'uid' => 0,
329  'pid' => 0,
330  'name' => 'Fallback Storage',
331  'description' => 'Internal storage, mounting the main TYPO3_site directory.',
332  'driver' => 'Local',
333  'processingfolder' => 'typo3temp/assets/_processed_/',
334  // legacy code
335  'configuration' => '',
336  'is_online' => true,
337  'is_browsable' => true,
338  'is_public' => true,
339  'is_writable' => true,
340  'is_default' => false,
341  ];
342  $storageConfiguration = [
343  'basePath' => ‪Environment::getPublicPath(),
344  'pathType' => 'absolute',
345  ];
346  } elseif ($recordData === [] || (int)$recordData['uid'] !== ‪$uid) {
347  $recordData = $this->‪fetchRecordDataByUid($uid);
348  }
349  $storageObject = $this->createStorageObject($recordData, $storageConfiguration);
350  $storageObject = $this->eventDispatcher
351  ->dispatch(new AfterResourceStorageInitializationEvent($storageObject))
352  ->getStorage();
353  $this->storageInstances[‪$uid] = $storageObject;
354  }
355  return $this->storageInstances[‪$uid];
356  }
357 
367  protected function findBestMatchingStorageByLocalPath(string &$localPath): int
368  {
369  if ($this->localDriverStorageCache === null) {
370  $this->initializeLocalStorageCache();
371  }
372  // normalize path information (`//`, `../`)
373  $localPath = ‪PathUtility::getCanonicalPath($localPath);
374  if (!str_starts_with($localPath, '/')) {
375  $localPath = '/' . $localPath;
376  }
377  $bestMatchStorageUid = 0;
378  $bestMatchLength = 0;
379  foreach ($this->localDriverStorageCache as $storageUid => $basePath) {
380  // try to match (resolved) relative base-path
381  if ($basePath->getRelative() !== null
382  && null !== $commonPrefix = ‪PathUtility::getCommonPrefix([$basePath->getRelative(), $localPath])
383  ) {
384  $matchLength = strlen($commonPrefix);
385  $basePathLength = strlen($basePath->getRelative());
386  if ($matchLength >= $basePathLength && $matchLength > $bestMatchLength) {
387  $bestMatchStorageUid = $storageUid;
388  $bestMatchLength = $matchLength;
389  }
390  }
391  // try to match (resolved) absolute base-path
392  if (null !== $commonPrefix = ‪PathUtility::getCommonPrefix([$basePath->getAbsolute(), $localPath])) {
393  $matchLength = strlen($commonPrefix);
394  $basePathLength = strlen($basePath->getAbsolute());
395  if ($matchLength >= $basePathLength && $matchLength > $bestMatchLength) {
396  $bestMatchStorageUid = $storageUid;
397  $bestMatchLength = $matchLength;
398  }
399  }
400  }
401  if ($bestMatchLength > 0) {
402  // $commonPrefix always has trailing slash, which needs to be excluded
403  // (commonPrefix: /some/path/, localPath: /some/path/file.png --> /file.png; keep leading slash)
404  $localPath = substr($localPath, $bestMatchLength - 1);
405  }
406  return $bestMatchStorageUid;
407  }
408 
412  protected function initializeLocalStorageCache(): void
413  {
414  $this->localDriverStorageCache = [
415  // implicit legacy storage in project's public path
417  ];
418  $storageObjects = $this->‪findByStorageType('Local');
419  foreach ($storageObjects as $localStorage) {
420  $configuration = $localStorage->getConfiguration();
421  if (!isset($configuration['basePath']) || !isset($configuration['pathType'])) {
422  continue;
423  }
424  if ($configuration['pathType'] === 'relative') {
425  $pathType = ‪LocalPath::TYPE_RELATIVE;
426  } elseif ($configuration['pathType'] === 'absolute') {
427  $pathType = ‪LocalPath::TYPE_ABSOLUTE;
428  } else {
429  continue;
430  }
431  $this->localDriverStorageCache[$localStorage->getUid()] = GeneralUtility::makeInstance(
432  LocalPath::class,
433  $configuration['basePath'],
434  $pathType
435  );
436  }
437  }
438 
444  protected function createStorageObject(array $storageRecord, ?array $storageConfiguration = null): ResourceStorage
445  {
446  if (!$storageConfiguration && !empty($storageRecord['configuration'])) {
447  $storageConfiguration = $this->convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
448  }
449  $driverType = $storageRecord['driver'];
450  $driverObject = $this->getDriverObject($driverType, (array)$storageConfiguration);
451  $storageRecord['configuration'] = $storageConfiguration;
452  return GeneralUtility::makeInstance(ResourceStorage::class, $driverObject, $storageRecord, $this->eventDispatcher);
453  }
454 
460  protected function convertFlexFormDataToConfigurationArray(string $flexFormData): array
461  {
462  if ($flexFormData) {
463  return GeneralUtility::makeInstance(FlexFormService::class)->convertFlexFormContentToArray($flexFormData);
464  }
465  return [];
466  }
467 
474  protected function getDriverObject(string $driverIdentificationString, array $driverConfiguration): DriverInterface
475  {
476  $driverClass = $this->driverRegistry->getDriverClass($driverIdentificationString);
478  $driverObject = GeneralUtility::makeInstance($driverClass, $driverConfiguration);
479  return $driverObject;
480  }
481 
485  public function createFromRecord(array $storageRecord): ResourceStorage
486  {
487  return $this->createStorageObject($storageRecord);
488  }
489 }
‪TYPO3\CMS\Core\Utility\PathUtility\getCanonicalPath
‪static string getCanonicalPath(string $path)
Definition: PathUtility.php:364
‪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:165
‪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:40
‪TYPO3\CMS\Core\Resource\StorageRepository\getDefaultStorage
‪getDefaultStorage()
Definition: StorageRepository.php:73
‪TYPO3\CMS\Core\Resource\StorageRepository
Definition: StorageRepository.php:38
‪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:52
‪TYPO3\CMS\Core\Resource\Driver\DriverRegistry
Definition: DriverRegistry.php:24
‪TYPO3\CMS\Core\Resource\ResourceStorage
Definition: ResourceStorage.php:128
‪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:48
‪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\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37