[FEATURE] Find best-matching local storage instead of default-storage 60/23560/4
authorStefan Neufeind <typo3.neufeind@speedpartner.de>
Fri, 15 Feb 2013 02:21:57 +0000 (03:21 +0100)
committerSteffen Ritter <info@rs-websystems.de>
Sun, 8 Sep 2013 18:35:02 +0000 (20:35 +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.0, 6.1, 6.2
Change-Id: Ife00d68314fe43804227bb26280d0d475cbaf10b
Reviewed-on: https://review.typo3.org/23560
Reviewed-by: Steffen Ritter
Tested-by: Steffen Ritter
typo3/sysext/core/Classes/Resource/ResourceFactory.php
typo3/sysext/core/Classes/Resource/StorageRepository.php
typo3/sysext/core/Tests/Unit/Resource/ResourceFactoryTest.php

index 1a7384d..fa1fe61 100644 (file)
@@ -27,6 +27,7 @@ namespace TYPO3\CMS\Core\Resource;
  *  This copyright notice MUST APPEAR in all copies of the script!
  ***************************************************************/
 
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\PathUtility;
 
 // TODO implement constructor-level caching
@@ -47,7 +48,7 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
         * @return ResourceFactory
         */
        static public function getInstance() {
-               return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(__CLASS__);
+               return GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\ResourceFactory');
        }
 
        /**
@@ -83,6 +84,13 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
        }
 
        /**
+        * 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.
         *
         * @param string $driverIdentificationString The driver class (or identifier) to use.
@@ -92,9 +100,9 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
         */
        public function getDriverObject($driverIdentificationString, array $driverConfiguration) {
                /** @var $driverRegistry Driver\DriverRegistry */
-               $driverRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Driver\\DriverRegistry');
+               $driverRegistry = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Driver\\DriverRegistry');
                $driverClass = $driverRegistry->getDriverClass($driverIdentificationString);
-               $driverObject = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($driverClass, $driverConfiguration);
+               $driverObject = GeneralUtility::makeInstance($driverClass, $driverConfiguration);
                return $driverObject;
        }
 
@@ -104,14 +112,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;
@@ -137,7 +149,7 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
                                );
                        } elseif (count($recordData) === 0 || $recordData['uid'] !== $uid) {
                                /** @var $storageRepository StorageRepository */
-                               $storageRepository = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\StorageRepository');
+                               $storageRepository = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\StorageRepository');
                                /** @var $storage ResourceStorage */
                                $storageObject = $storageRepository->findByUid($uid);
                        }
@@ -151,6 +163,56 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
        }
 
        /**
+        * 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
         *
         * @param string $flexFormData
@@ -159,7 +221,7 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
        public function convertFlexFormDataToConfigurationArray($flexFormData) {
                $configuration = array();
                if ($flexFormData) {
-                       $flexFormContents = \TYPO3\CMS\Core\Utility\GeneralUtility::xml2array($flexFormData);
+                       $flexFormContents = GeneralUtility::xml2array($flexFormData);
                        if (!empty($flexFormContents['data']['sDEF']['lDEF']) && is_array($flexFormContents['data']['sDEF']['lDEF'])) {
                                foreach ($flexFormContents['data']['sDEF']['lDEF'] as $key => $value) {
                                        if (isset($value['vDEF'])) {
@@ -207,14 +269,14 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
         */
        public function createCollectionObject(array $collectionData) {
                switch ($collectionData['type']) {
-               case 'static':
-                       $collection = Collection\StaticFileCollection::create($collectionData);
-                       break;
-               case 'folder':
-                       $collection = Collection\FolderBasedFileCollection::create($collectionData);
-                       break;
-               default:
-                       $collection = NULL;
+                       case 'static':
+                               $collection = Collection\StaticFileCollection::create($collectionData);
+                               break;
+                       case 'folder':
+                               $collection = Collection\FolderBasedFileCollection::create($collectionData);
+                               break;
+                       default:
+                               $collection = NULL;
                }
                return $collection;
        }
@@ -234,7 +296,7 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
                $driverType = $storageRecord['driver'];
                $driverObject = $this->getDriverObject($driverType, $storageConfiguration);
                /** @var $storage ResourceStorage */
-               $storage = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($className, $driverObject, $storageRecord);
+               $storage = GeneralUtility::makeInstance($className, $driverObject, $storageRecord);
                // TODO handle publisher
                return $storage;
        }
@@ -248,12 +310,12 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
         * @return Folder
         */
        public function createFolderObject(ResourceStorage $storage, $identifier, $name) {
-               return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Folder', $storage, $identifier, $name);
+               return GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Folder', $storage, $identifier, $name);
        }
 
        protected function createPublisherFromConfiguration(array $configuration) {
                $publishingTarget = $this->getStorageObject($configuration['publisherConfiguration']['publishingTarget']);
-               $publisher = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($configuration['publisher'], $publishingTarget, $configuration['publisherConfiguration']);
+               $publisher = GeneralUtility::makeInstance($configuration['publisher'], $publishingTarget, $configuration['publisherConfiguration']);
                return $publisher;
        }
 
@@ -293,7 +355,7 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
         * @return File
         */
        public function getFileObjectFromCombinedIdentifier($identifier) {
-               $parts = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(':', $identifier);
+               $parts = GeneralUtility::trimExplode(':', $identifier);
                if (count($parts) === 2) {
                        $storageUid = $parts[0];
                        $fileIdentifier = $parts[1];
@@ -302,8 +364,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);
        }
 
        /**
@@ -342,8 +407,8 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
                                // path or folder in a valid storageUID
                                return $this->getObjectFromCombinedIdentifier($input);
                        } elseif ($prefix == 'EXT') {
-                               $input = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName($input);
-                               $input = \TYPO3\CMS\Core\Utility\PathUtility::getRelativePath(PATH_site, dirname($input)) . basename($input);
+                               $input = GeneralUtility::getFileAbsFileName($input);
+                               $input = PathUtility::getRelativePath(PATH_site, PathUtility::dirname($input)) . PathUtility::basename($input);
                                return $this->getFileObjectFromCombinedIdentifier($input);
                        }
                // this is a backwards-compatible way to access "0-storage" files or folders
@@ -364,7 +429,7 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
         * @return Folder
         */
        public function getFolderObjectFromCombinedIdentifier($identifier) {
-               $parts = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(':', $identifier);
+               $parts = GeneralUtility::trimExplode(':', $identifier);
                if (count($parts) === 2) {
                        $storageUid = $parts[0];
                        $folderIdentifier = $parts[1];
@@ -372,13 +437,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 (\TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($folderIdentifier, PATH_site)) {
+                       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);
        }
 
        /**
@@ -388,7 +456,7 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
         * @return ResourceStorage
         */
        public function getStorageObjectFromCombinedIdentifier($identifier) {
-               $parts = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(':', $identifier);
+               $parts = GeneralUtility::trimExplode(':', $identifier);
                if (count($parts) === 2) {
                        $storageUid = $parts[0];
                }
@@ -405,7 +473,7 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
         * @return FileInterface|Folder
         */
        public function getObjectFromCombinedIdentifier($identifier) {
-               list($storageId, $objectIdentifier) = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(':', $identifier);
+               list($storageId, $objectIdentifier) = GeneralUtility::trimExplode(':', $identifier);
                $storage = $this->getStorageObject($storageId);
                if ($storage->hasFile($objectIdentifier)) {
                        return $storage->getFile($objectIdentifier);
@@ -425,7 +493,7 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
         */
        public function createFileObject(array $fileData) {
                /** @var File $fileObject */
-               $fileObject = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\File', $fileData);
+               $fileObject = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\File', $fileData);
                if (is_numeric($fileData['storage'])) {
                        $storageObject = $this->getStorageObject($fileData['storage']);
                        $fileObject->setStorage($storageObject);
@@ -478,7 +546,7 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
         */
        public function createFileReferenceObject(array $fileReferenceData) {
                /** @var FileReference $fileReferenceObject */
-               $fileReferenceObject = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\FileReference', $fileReferenceData);
+               $fileReferenceObject = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\FileReference', $fileReferenceData);
                return $fileReferenceObject;
        }
 }
index 58f850d..eba5bff 100644 (file)
@@ -50,6 +50,11 @@ class StorageRepository extends AbstractRepository {
        protected $typeField = 'driver';
 
        /**
+        * @var string
+        */
+       protected $driverField = 'driver';
+
+       /**
         * @var \TYPO3\CMS\Core\Log\Logger
         */
        protected $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 a16e3e2..dfc15ed 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
+                       )
+               );
+       }
 }
 
 ?>