‪TYPO3CMS  11.5
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 
87  public function ‪getDefaultStorage(): ?‪ResourceStorage
88  {
89  $allStorages = $this->‪findAll();
90  foreach ($allStorages as $storage) {
91  if ($storage->isDefault()) {
92  return $storage;
93  }
94  }
95  return null;
96  }
97 
98  public function ‪findByUid(int $uid): ?ResourceStorage
99  {
100  $this->‪initializeLocalCache();
101  if (isset($this->storageRowCache[$uid]) || $uid === 0) {
102  return $this->‪getStorageObject($uid, $this->storageRowCache[$uid] ?? []);
103  }
104  return null;
105  }
106 
113  public function ‪findByCombinedIdentifier(string $identifier): ?ResourceStorage
114  {
115  $parts = ‪GeneralUtility::trimExplode(':', $identifier);
116  return count($parts) === 2 ? $this->‪findByUid((int)$parts[0]) : null;
117  }
118 
123  protected function ‪fetchRecordDataByUid(int $uid): array
124  {
125  $this->‪initializeLocalCache();
126  if (!isset($this->storageRowCache[$uid])) {
127  throw new \InvalidArgumentException(sprintf('No storage found with uid "%d".', $uid), 1599235454);
128  }
129 
130  return $this->storageRowCache[$uid];
131  }
132 
136  protected function ‪initializeLocalCache()
137  {
138  if ($this->storageRowCache === null) {
139  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
140  ->getQueryBuilderForTable($this->table);
141 
142  $result = $queryBuilder
143  ->select('*')
144  ->from($this->table)
145  ->orderBy('name')
146  ->executeQuery();
147 
148  $this->storageRowCache = [];
149  while ($row = $result->fetchAssociative()) {
150  if (!empty($row['uid'])) {
151  $this->storageRowCache[$row['uid']] = $row;
152  }
153  }
154 
155  // if no storage is created before or the user has not access to a storage
156  // $this->storageRowCache would have the value array()
157  // so check if there is any record. If no record is found, create the fileadmin/ storage
158  // selecting just one row is enough
159 
160  if ($this->storageRowCache === []) {
161  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
162  ->getConnectionForTable($this->table);
163 
164  $storageObjectsCount = $connection->count('uid', $this->table, []);
165 
166  if ($storageObjectsCount === 0) {
167  if ($this->‪createLocalStorage(
168  rtrim(‪$GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] ?? 'fileadmin', '/'),
169  ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'],
170  'relative',
171  'This is the local fileadmin/ directory. This storage mount has been created automatically by TYPO3.',
172  true
173  ) > 0) {
174  // clear Cache to force reloading of storages
175  $this->‪flush();
176  // call self for initialize Cache
177  $this->‪initializeLocalCache();
178  }
179  }
180  }
181  }
182  }
183 
189  public function ‪flush(): void
190  {
191  $this->storageRowCache = null;
192  $this->storageInstances = null;
193  $this->localDriverStorageCache = null;
194  }
195 
202  public function ‪findByStorageType($storageType)
203  {
204  $this->‪initializeLocalCache();
205 
206  $storageObjects = [];
207  foreach ($this->storageRowCache as $storageRow) {
208  if ($storageRow['driver'] !== $storageType) {
209  continue;
210  }
211  if ($this->‪driverRegistry->driverExists($storageRow['driver'])) {
212  $storageObjects[] = $this->‪getStorageObject($storageRow['uid'], $storageRow);
213  } else {
214  $this->logger->warning('Could not instantiate storage "{name}" because of missing driver.', ['name' => $storageRow['name']]);
215  }
216  }
217  return $storageObjects;
218  }
219 
226  public function ‪findAll()
227  {
228  $this->‪initializeLocalCache();
229 
230  $storageObjects = [];
231  foreach ($this->storageRowCache as $storageRow) {
232  if ($this->‪driverRegistry->driverExists($storageRow['driver'])) {
233  $storageObjects[] = $this->‪getStorageObject($storageRow['uid'], $storageRow);
234  } else {
235  $this->logger->warning('Could not instantiate storage "{name}" because of missing driver.', ['name' => $storageRow['name']]);
236  }
237  }
238  return $storageObjects;
239  }
240 
251  public function ‪createLocalStorage($name, $basePath, $pathType, $description = '', $default = false)
252  {
253  $caseSensitive = $this->‪testCaseSensitivity($pathType === 'relative' ? ‪Environment::getPublicPath() . '/' . $basePath : $basePath);
254  // create the FlexForm for the driver configuration
255  $flexFormData = [
256  'data' => [
257  'sDEF' => [
258  'lDEF' => [
259  'basePath' => ['vDEF' => rtrim($basePath, '/') . '/'],
260  'pathType' => ['vDEF' => $pathType],
261  'caseSensitive' => ['vDEF' => $caseSensitive],
262  ],
263  ],
264  ],
265  ];
266 
267  $flexFormXml = GeneralUtility::makeInstance(FlexFormTools::class)->flexArray2Xml($flexFormData, true);
268 
269  // create the record
270  $field_values = [
271  'pid' => 0,
272  'tstamp' => ‪$GLOBALS['EXEC_TIME'],
273  'crdate' => ‪$GLOBALS['EXEC_TIME'],
274  'name' => $name,
275  'description' => $description,
276  'driver' => 'Local',
277  'configuration' => $flexFormXml,
278  'is_online' => 1,
279  'is_browsable' => 1,
280  'is_public' => 1,
281  'is_writable' => 1,
282  'is_default' => $default ? 1 : 0,
283  ];
284 
285  $dbConnection = GeneralUtility::makeInstance(ConnectionPool::class)
286  ->getConnectionForTable($this->table);
287  $dbConnection->insert($this->table, $field_values);
288 
289  // Flush local resourceStorage cache so the storage can be accessed during the same request right away
290  $this->‪flush();
291 
292  return (int)$dbConnection->lastInsertId($this->table);
293  }
294 
301  protected function ‪testCaseSensitivity($absolutePath)
302  {
303  $caseSensitive = true;
304  $path = rtrim($absolutePath, '/') . '/aAbB';
305  $testFileExists = @file_exists($path);
306 
307  // create test file
308  if (!$testFileExists) {
309  // @todo: This misses a test for directory existence, touch does not create
310  // dirs. StorageRepositoryTest stumbles here. It should at least be
311  // sanitized to not touch() a file in a non-existing directory.
312  touch($path);
313  }
314 
315  // do the actual sensitivity check
316  if (@file_exists(strtoupper($path)) && @file_exists(strtolower($path))) {
317  $caseSensitive = false;
318  }
319 
320  // clean filesystem
321  if (!$testFileExists) {
322  unlink($path);
323  }
324 
325  return $caseSensitive;
326  }
327 
338  public function ‪getStorageObject($uid, array $recordData = [], &$fileIdentifier = null): ResourceStorage
339  {
340  if (!is_numeric($uid)) {
341  throw new \InvalidArgumentException('The UID of storage has to be numeric. UID given: "' . $uid . '"', 1314085991);
342  }
343  $uid = (int)$uid;
344  if ($uid === 0 && $fileIdentifier !== null) {
345  $uid = $this->‪findBestMatchingStorageByLocalPath($fileIdentifier);
346  }
347  if (empty($this->storageInstances[$uid])) {
348  $storageConfiguration = null;
350  $event = $this->‪eventDispatcher->dispatch(new BeforeResourceStorageInitializationEvent($uid, $recordData, $fileIdentifier));
351  $recordData = $event->getRecord();
352  $uid = $event->getStorageUid();
353  $fileIdentifier = $event->getFileIdentifier();
354  // If the built-in storage with UID=0 is requested:
355  if ($uid === 0) {
356  $recordData = [
357  'uid' => 0,
358  'pid' => 0,
359  'name' => 'Fallback Storage',
360  'description' => 'Internal storage, mounting the main TYPO3_site directory.',
361  'driver' => 'Local',
362  'processingfolder' => 'typo3temp/assets/_processed_/',
363  // legacy code
364  'configuration' => '',
365  'is_online' => true,
366  'is_browsable' => true,
367  'is_public' => true,
368  'is_writable' => true,
369  'is_default' => false,
370  ];
371  $storageConfiguration = [
372  'basePath' => ‪Environment::getPublicPath(),
373  'pathType' => 'absolute',
374  ];
375  } elseif ($recordData === [] || (int)$recordData['uid'] !== $uid) {
376  $recordData = $this->‪fetchRecordDataByUid($uid);
377  }
378  $storageObject = $this->‪createStorageObject($recordData, $storageConfiguration);
379  $storageObject = $this->‪eventDispatcher
380  ->dispatch(new AfterResourceStorageInitializationEvent($storageObject))
381  ->getStorage();
382  $this->storageInstances[$uid] = $storageObject;
383  }
384  return $this->storageInstances[$uid];
385  }
386 
396  protected function ‪findBestMatchingStorageByLocalPath(&$localPath): int
397  {
398  if ($this->localDriverStorageCache === null) {
400  }
401  // normalize path information (`//`, `../`)
402  $localPath = ‪PathUtility::getCanonicalPath($localPath);
403  if (!str_starts_with($localPath, '/')) {
404  $localPath = '/' . $localPath;
405  }
406  $bestMatchStorageUid = 0;
407  $bestMatchLength = 0;
408  foreach ($this->localDriverStorageCache as $storageUid => $basePath) {
409  // try to match (resolved) relative base-path
410  if ($basePath->getRelative() !== null
411  && null !== $commonPrefix = ‪PathUtility::getCommonPrefix([$basePath->getRelative(), $localPath])
412  ) {
413  $matchLength = strlen($commonPrefix);
414  $basePathLength = strlen($basePath->getRelative());
415  if ($matchLength >= $basePathLength && $matchLength > $bestMatchLength) {
416  $bestMatchStorageUid = $storageUid;
417  $bestMatchLength = $matchLength;
418  }
419  }
420  // try to match (resolved) absolute base-path
421  if (null !== $commonPrefix = ‪PathUtility::getCommonPrefix([$basePath->getAbsolute(), $localPath])) {
422  $matchLength = strlen($commonPrefix);
423  $basePathLength = strlen($basePath->getAbsolute());
424  if ($matchLength >= $basePathLength && $matchLength > $bestMatchLength) {
425  $bestMatchStorageUid = $storageUid;
426  $bestMatchLength = $matchLength;
427  }
428  }
429  }
430  if ($bestMatchLength > 0) {
431  // $commonPrefix always has trailing slash, which needs to be excluded
432  // (commonPrefix: /some/path/, localPath: /some/path/file.png --> /file.png; keep leading slash)
433  $localPath = substr($localPath, $bestMatchLength - 1);
434  }
435  return $bestMatchStorageUid;
436  }
437 
441  protected function ‪initializeLocalStorageCache(): void
442  {
443  $this->localDriverStorageCache = [
444  // implicit legacy storage in project's public path
446  ];
447  $storageObjects = $this->‪findByStorageType('Local');
448  foreach ($storageObjects as $localStorage) {
449  $configuration = $localStorage->getConfiguration();
450  if (!isset($configuration['basePath']) || !isset($configuration['pathType'])) {
451  continue;
452  }
453  if ($configuration['pathType'] === 'relative') {
454  $pathType = ‪LocalPath::TYPE_RELATIVE;
455  } elseif ($configuration['pathType'] === 'absolute') {
456  $pathType = ‪LocalPath::TYPE_ABSOLUTE;
457  } else {
458  continue;
459  }
460  $this->localDriverStorageCache[$localStorage->getUid()] = GeneralUtility::makeInstance(
461  LocalPath::class,
462  $configuration['basePath'],
463  $pathType
464  );
465  }
466  }
467 
476  public function ‪createStorageObject(array $storageRecord, ?array $storageConfiguration = null): ResourceStorage
477  {
478  if (!$storageConfiguration && !empty($storageRecord['configuration'])) {
479  $storageConfiguration = $this->‪convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
480  }
481  $driverType = $storageRecord['driver'];
482  $driverObject = $this->‪getDriverObject($driverType, (array)$storageConfiguration);
483  $storageRecord['configuration'] = $storageConfiguration;
484  return GeneralUtility::makeInstance(ResourceStorage::class, $driverObject, $storageRecord, $this->‪eventDispatcher);
485  }
486 
493  protected function ‪convertFlexFormDataToConfigurationArray(string $flexFormData): array
494  {
495  if ($flexFormData) {
496  return GeneralUtility::makeInstance(FlexFormService::class)->convertFlexFormContentToArray($flexFormData);
497  }
498  return [];
499  }
500 
508  protected function ‪getDriverObject(string $driverIdentificationString, array $driverConfiguration): DriverInterface
509  {
510  $driverClass = $this->‪driverRegistry->getDriverClass($driverIdentificationString);
512  $driverObject = GeneralUtility::makeInstance($driverClass, $driverConfiguration);
513  return $driverObject;
514  }
515 
521  public function ‪createFromRecord(array $storageRecord): ‪ResourceStorage
522  {
523  return $this->‪createStorageObject($storageRecord);
524  }
525 }
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:999
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:25
‪TYPO3\CMS\Core\Resource\StorageRepository\convertFlexFormDataToConfigurationArray
‪array convertFlexFormDataToConfigurationArray(string $flexFormData)
Definition: StorageRepository.php:487
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static string getPublicPath()
Definition: Environment.php:206
‪TYPO3\CMS\Core\Resource\LocalPath\TYPE_RELATIVE
‪const TYPE_RELATIVE
Definition: LocalPath.php:31
‪TYPO3\CMS\Core\Resource\Driver\DriverInterface
Definition: DriverInterface.php:23
‪TYPO3\CMS\Core\Resource\StorageRepository\testCaseSensitivity
‪bool testCaseSensitivity($absolutePath)
Definition: StorageRepository.php:295
‪TYPO3\CMS\Core\Utility\PathUtility\getCanonicalPath
‪static string getCanonicalPath($path)
Definition: PathUtility.php:380
‪TYPO3\CMS\Core\Resource\StorageRepository\fetchRecordDataByUid
‪array fetchRecordDataByUid(int $uid)
Definition: StorageRepository.php:117
‪TYPO3\CMS\Core\Service\FlexFormService
Definition: FlexFormService.php:25
‪TYPO3\CMS\Core\Resource\StorageRepository\findBestMatchingStorageByLocalPath
‪int findBestMatchingStorageByLocalPath(&$localPath)
Definition: StorageRepository.php:390
‪TYPO3\CMS\Core\Resource\StorageRepository\findByStorageType
‪ResourceStorage[] findByStorageType($storageType)
Definition: StorageRepository.php:196
‪TYPO3\CMS\Core\Resource\LocalPath\TYPE_ABSOLUTE
‪const TYPE_ABSOLUTE
Definition: LocalPath.php:30
‪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\Utility\PathUtility\getCommonPrefix
‪static string null getCommonPrefix(array $paths)
Definition: PathUtility.php:171
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools
Definition: FlexFormTools.php:40
‪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\createLocalStorage
‪int createLocalStorage($name, $basePath, $pathType, $description='', $default=false)
Definition: StorageRepository.php:245
‪TYPO3\CMS\Core\Resource\StorageRepository\getStorageObject
‪ResourceStorage getStorageObject($uid, array $recordData=[], &$fileIdentifier=null)
Definition: StorageRepository.php:332
‪TYPO3\CMS\Core\Resource\StorageRepository\initializeLocalCache
‪initializeLocalCache()
Definition: StorageRepository.php:130
‪TYPO3\CMS\Core\Resource\StorageRepository\initializeLocalStorageCache
‪initializeLocalStorageCache()
Definition: StorageRepository.php:435
‪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:125
‪TYPO3\CMS\Core\Resource\StorageRepository\createFromRecord
‪ResourceStorage createFromRecord(array $storageRecord)
Definition: StorageRepository.php:515
‪TYPO3\CMS\Core\Resource\StorageRepository\flush
‪flush()
Definition: StorageRepository.php:183
‪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:43
‪TYPO3\CMS\Core\Resource\StorageRepository\findByCombinedIdentifier
‪ResourceStorage null findByCombinedIdentifier(string $identifier)
Definition: StorageRepository.php:107
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Resource\StorageRepository\getDriverObject
‪DriverInterface getDriverObject(string $driverIdentificationString, array $driverConfiguration)
Definition: StorageRepository.php:502
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Core\Resource\StorageRepository\findByUid
‪findByUid(int $uid)
Definition: StorageRepository.php:92
‪TYPO3\CMS\Core\Resource\StorageRepository\getDefaultStorage
‪ResourceStorage null getDefaultStorage()
Definition: StorageRepository.php:81
‪TYPO3\CMS\Core\Resource\StorageRepository\findAll
‪ResourceStorage[] findAll()
Definition: StorageRepository.php:220
‪TYPO3\CMS\Core\Resource\StorageRepository\createStorageObject
‪ResourceStorage createStorageObject(array $storageRecord, ?array $storageConfiguration=null)
Definition: StorageRepository.php:470