[FEATURE] Find best-matching local storage instead of default-storage 90/18290/10
authorStefan Neufeind <typo3.neufeind@speedpartner.de>
Fri, 15 Feb 2013 02:21:57 +0000 (03:21 +0100)
committerAndreas Wolf <andreas.wolf@typo3.org>
Mon, 5 Aug 2013 16:03:16 +0000 (18:03 +0200)
If no storage-UID is given usually the default-storage will be
used ("legacy-storage"). Some problems with not up-to-date
index-records etc. can be prevented by using a matching storage
instead of falling back to the default-storage (ID 0).

Implement search for best matching storage.

Resolves: #45498
Releases: 6.2
Change-Id: Ife00d68314fe43804227bb26280d0d475cbaf10b
Reviewed-on: https://review.typo3.org/18290
Reviewed-by: Andreas Wolf
Tested-by: Andreas Wolf
typo3/sysext/core/Classes/Resource/ResourceFactory.php
typo3/sysext/core/Classes/Resource/StorageRepository.php
typo3/sysext/core/Tests/Unit/Resource/FactoryTest.php

index a4be1c3..2401a9a 100644 (file)
@@ -69,6 +69,13 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
         */
        protected $fileReferenceInstances = array();
 
+       /**
+        * A list of the base paths of "local" driver storages. Used to make the detection of base paths easier.
+        *
+        * @var array
+        */
+       protected $localDriverStorageCache = NULL;
+
        /**
         * Creates a driver object for a specified storage object.
         *
@@ -91,14 +98,18 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
         *
         * @param integer $uid The uid of the storage to instantiate.
         * @param array $recordData The record row from database.
+        * @param string $fileIdentifier Identifier for a file. Used for auto-detection of a storage, but only if $uid === 0 (Local default storage) is used
         *
         * @throws \InvalidArgumentException
         * @return ResourceStorage
         */
-       public function getStorageObject($uid, array $recordData = array()) {
+       public function getStorageObject($uid, array $recordData = array(), &$fileIdentifier = NULL) {
                if (!is_numeric($uid)) {
                        throw new \InvalidArgumentException('uid of Storage has to be numeric.', 1314085991);
                }
+               if (intval($uid) === 0 && $fileIdentifier !== NULL) {
+                       $uid = $this->findBestMatchingStorageByLocalPath($fileIdentifier);
+               }
                if (!$this->storageInstances[$uid]) {
                        $storageConfiguration = NULL;
                        $storageObject = NULL;
@@ -136,6 +147,56 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
                return $this->storageInstances[$uid];
        }
 
+       /**
+        * Checks whether a file resides within a real storage in local file system.
+        * If no match is found, uid 0 is returned which is a fallback storage pointing to PATH_site.
+        *
+        * The file identifier is adapted accordingly to match the new storage's base path.
+        *
+        * @param string $localPath
+        *
+        * @return integer
+        */
+       protected function findBestMatchingStorageByLocalPath(&$localPath) {
+               if ($this->localDriverStorageCache === NULL) {
+                       $this->initializeLocalStorageCache();
+               }
+
+               $bestMatchStorageUid = 0;
+               $bestMatchLength = 0;
+               foreach ($this->localDriverStorageCache as $storageUid => $basePath) {
+                       $commonPrefix = PathUtility::getCommonPrefix(array($basePath, $localPath));
+                       $matchLength = strlen($commonPrefix);
+                       if ($matchLength > $bestMatchLength) {
+                               $bestMatchStorageUid = intval($storageUid);
+                               $bestMatchLength = $matchLength;
+                       }
+               }
+               if ($bestMatchStorageUid !== 0) {
+                       $localPath = substr($localPath, $bestMatchLength);
+               }
+               return $bestMatchStorageUid;
+       }
+
+       /**
+        * Creates an array mapping all uids to the basePath of storages using the "local" driver.
+        *
+        * @return void
+        */
+       protected function initializeLocalStorageCache() {
+               /** @var $storageRepository StorageRepository */
+               $storageRepository = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\StorageRepository');
+               /** @var $storageObjects ResourceStorage[] */
+               $storageObjects = $storageRepository->findByStorageType('Local');
+
+               $storageCache = array();
+               foreach ($storageObjects as $localStorage) {
+                       $configuration = $localStorage->getConfiguration();
+                       $storageCache[$localStorage->getUid()] = $configuration['basePath'];
+               }
+               $this->localDriverStorageCache = $storageCache;
+       }
+
        /**
         * Converts a flexform data string to a flat array with key value pairs
         *
@@ -288,8 +349,11 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
                        // use virtual Storage (uid=0)
                        $storageUid = 0;
                        $fileIdentifier = $parts[0];
+
+                       // please note that getStorageObject() might modify $fileIdentifier when
+                       // auto-detecting the best-matching storage to use
                }
-               return $this->getStorageObject($storageUid)->getFile($fileIdentifier);
+               return $this->getStorageObject($storageUid, array(), $fileIdentifier)->getFile($fileIdentifier);
        }
 
        /**
@@ -327,7 +391,7 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
                                return $this->getObjectFromCombinedIdentifier($input);
                        } elseif ($prefix == 'EXT') {
                                $input = GeneralUtility::getFileAbsFileName($input);
-                               $input = \TYPO3\CMS\Core\Utility\PathUtility::getRelativePath(PATH_site, dirname($input)) . basename($input);
+                               $input = PathUtility::getRelativePath(PATH_site, dirname($input)) . basename($input);
                                return $this->getFileObjectFromCombinedIdentifier($input);
                        }
                // this is a backwards-compatible way to access "0-storage" files or folders
@@ -356,13 +420,16 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
                        // We only got a path: Go into backwards compatibility mode and
                        // use virtual Storage (uid=0)
                        $storageUid = 0;
+
+                       // please note that getStorageObject() might modify $folderIdentifier when
+                       // auto-detecting the best-matching storage to use
                        $folderIdentifier = $parts[0];
                        // make sure to not use an absolute path, and remove PATH_site if it is prepended
                        if (GeneralUtility::isFirstPartOfStr($folderIdentifier, PATH_site)) {
                                $folderIdentifier = substr($parts[0], strlen(PATH_site));
                        }
                }
-               return $this->getStorageObject($storageUid)->getFolder($folderIdentifier);
+               return $this->getStorageObject($storageUid, array(), $folderIdentifier)->getFolder($folderIdentifier);
        }
 
        /**
@@ -468,4 +535,4 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
 }
 
 
-?>
+?>
\ No newline at end of file
index 38eb5b7..7ad38e0 100644 (file)
@@ -49,6 +49,11 @@ class StorageRepository extends AbstractRepository {
         */
        protected $typeField = 'driver';
 
+       /**
+        * @var string
+        */
+       protected $driverField = 'driver';
+
        /**
         * @var \TYPO3\CMS\Core\Log\Logger
         */
@@ -63,7 +68,7 @@ class StorageRepository extends AbstractRepository {
        }
 
        /**
-        * Finds storages by type.
+        * Finds storages by type, i.e. the driver used
         *
         * @param string $storageType
         * @return ResourceStorage[]
index 02316ca..7c1e442 100644 (file)
@@ -53,7 +53,7 @@ class FactoryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
 
        public function setUp() {
                $this->singletonInstances = \TYPO3\CMS\Core\Utility\GeneralUtility::getSingletonInstances();
-               $this->fixture = $this->getAccessibleMock('TYPO3\\CMS\\Core\\Resource\\ResourceFactory', array('dummy'));
+               $this->fixture = $this->getAccessibleMock('TYPO3\\CMS\\Core\\Resource\\ResourceFactory', array('dummy'), array(), '', FALSE);
        }
 
        public function tearDown() {
@@ -136,6 +136,58 @@ class FactoryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $this->filesCreated[] = PATH_site . $filename;
                $this->fixture->retrieveFileOrFolderObject($filename);
        }
+
+       /***********************************
+        * Storage AutoDetection
+        ***********************************/
+
+       /**
+        * @param array $storageConfiguration
+        * @param string $path
+        * @param integer $expectedStorageId
+        * @test
+        * @dataProvider storageDetectionDataProvider
+        */
+
+       public function findBestMatchingStorageByLocalPathReturnsDefaultStorageIfNoMatchIsFound(array $storageConfiguration, $path, $expectedStorageId) {
+               $this->fixture->_set('localDriverStorageCache', $storageConfiguration);
+               $this->assertSame($expectedStorageId, $this->fixture->_callRef('findBestMatchingStorageByLocalPath', $path));
+       }
+
+
+
+       /**
+        * @return array
+        */
+       public function storageDetectionDataProvider() {
+               return array(
+                       'NoLocalStoragesReturnDefaultStorage' => array(
+                               array(),
+                               'my/dummy/Image.png',
+                               0
+                       ),
+                       'NoMatchReturnsDefaultStorage' => array(
+                               array(1 => 'fileadmin/', 2 => 'fileadmin2/public/'),
+                               'my/dummy/Image.png',
+                               0
+                       ),
+                       'MatchReturnsTheMatch' => array(
+                               array(1 => 'fileadmin/', 2 => 'other/public/'),
+                               'fileadmin/dummy/Image.png',
+                               1
+                       ),
+                       'TwoFoldersWithSameStartReturnsCorrect' => array(
+                               array(1 => 'fileadmin/', 2 => 'fileadmin/public/'),
+                               'fileadmin/dummy/Image.png',
+                               1
+                       ),
+                       'NestedStorageReallyReturnsTheBestMatching' => array(
+                               array(1 => 'fileadmin/', 2 => 'fileadmin/public/'),
+                               'fileadmin/public/Image.png',
+                               2
+                       )
+               );
+       }
 }
 
 ?>