[BUGFIX] File identifiers are case-insensitive 98/23398/10
authorAndreas Wolf <andreas.wolf@typo3.org>
Wed, 28 Aug 2013 11:25:37 +0000 (13:25 +0200)
committerMarkus Klein <klein.t3@mfc-linz.at>
Tue, 12 Nov 2013 15:19:01 +0000 (16:19 +0100)
With the default collations used by most databases, casing is ignored.
This leads to problems when searching for an entry in the file database:
If there are two files with the same name in different casings, only one
of the two will be indexed. This index record will be used for both
files, as the database does not see a difference when querying for their
identifiers.

The problem is solved by this commit with the help of a new field
identifier_hash. To also solve the problems arising on case-insensitive
file systems (where all casings are possible and thus the same file
could be indexed multiple times when being created with differently
cased identifiers), file identifiers for these storages are converted to
all-lowercase before hashing.

Change-Id: I805085948e01544efd692498f5e3537612c62050
Resolves: #43027
Resolves: #46553
Resolves: #45504
Releases: 6.2
Reviewed-on: https://review.typo3.org/23398
Reviewed-by: Alexander Opitz
Tested-by: Alexander Opitz
Reviewed-by: Markus Klein
Tested-by: Markus Klein
22 files changed:
typo3/sysext/core/Classes/Resource/AbstractFile.php
typo3/sysext/core/Classes/Resource/Driver/AbstractDriver.php
typo3/sysext/core/Classes/Resource/Driver/AbstractHierarchicalFilesystemDriver.php
typo3/sysext/core/Classes/Resource/Driver/LocalDriver.php
typo3/sysext/core/Classes/Resource/File.php
typo3/sysext/core/Classes/Resource/FileReference.php
typo3/sysext/core/Classes/Resource/Folder.php
typo3/sysext/core/Classes/Resource/Index/FileIndexRepository.php
typo3/sysext/core/Classes/Resource/ProcessedFileRepository.php
typo3/sysext/core/Classes/Resource/ResourceInterface.php
typo3/sysext/core/Classes/Resource/ResourceStorage.php
typo3/sysext/core/Classes/Resource/Service/IndexerService.php
typo3/sysext/core/Classes/Resource/StorageRepository.php
typo3/sysext/core/Configuration/Resource/Driver/LocalDriverFlexForm.xml
typo3/sysext/core/Tests/BaseTestCase.php
typo3/sysext/core/Tests/Unit/Resource/Driver/DriverRegistryTest.php
typo3/sysext/core/Tests/Unit/Resource/ResourceFactoryTest.php
typo3/sysext/core/Tests/Unit/Resource/ResourceStorageTest.php
typo3/sysext/core/ext_tables.sql
typo3/sysext/install/Classes/Updates/FileIdentifierHashUpdate.php [new file with mode: 0644]
typo3/sysext/install/ext_localconf.php
typo3/sysext/lang/locallang_mod_file_list.xlf

index c61caf0..4184c4f 100644 (file)
@@ -165,6 +165,15 @@ abstract class AbstractFile implements FileInterface {
        }
 
        /**
+        * Get hashed identifier
+        *
+        * @return string
+        */
+       public function getHashedIdentifier() {
+               return $this->properties['identifier_hash'];
+       }
+
+       /**
         * Returns the name of this file
         *
         * @return string
@@ -349,10 +358,12 @@ abstract class AbstractFile implements FileInterface {
        /****************************************
         * STORAGE AND MANAGEMENT RELATED METHDOS
         ****************************************/
+
        /**
         * Get the storage this file is located in
         *
         * @return ResourceStorage
+        * @throws \RuntimeException
         */
        public function getStorage() {
                if ($this->storage === NULL) {
index 9c41d27..ef41420 100644 (file)
@@ -27,6 +27,8 @@ namespace TYPO3\CMS\Core\Resource\Driver;
  * This copyright notice MUST APPEAR in all copies of the script!
  ***************************************************************/
 
+use TYPO3\CMS\Core\Resource\FileInterface;
+use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Utility\PathUtility;
 
 /**
@@ -56,14 +58,14 @@ abstract class AbstractDriver {
        /**
         * The storage folder that forms the root of this FS tree
         *
-        * @var \TYPO3\CMS\Core\Resource\Folder
+        * @var Folder
         */
        protected $rootLevelFolder;
 
        /**
         * The default folder new files should be put into.
         *
-        * @var \TYPO3\CMS\Core\Resource\Folder
+        * @var Folder
         */
        protected $defaultLevelFolder;
 
@@ -214,10 +216,10 @@ abstract class AbstractDriver {
        /**
         * Returns a temporary path for a given file, including the file extension.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @return string
         */
-       protected function getTemporaryPathForFile(\TYPO3\CMS\Core\Resource\FileInterface $file) {
+       protected function getTemporaryPathForFile(FileInterface $file) {
                return \TYPO3\CMS\Core\Utility\GeneralUtility::tempnam('fal-tempfile-') . '.' . $file->getExtension();
        }
 
@@ -244,21 +246,48 @@ abstract class AbstractDriver {
         * Creates a (cryptographic) hash for a file.
         *
         * @abstract
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @param string $hashAlgorithm The hash algorithm to use
         * @return string
         */
-       abstract public function hash(\TYPO3\CMS\Core\Resource\FileInterface $file, $hashAlgorithm);
+       abstract public function hash(FileInterface $file, $hashAlgorithm);
+
+       /**
+        * Hashes a file identifier, taking the case sensitivity of the file system
+        * into account. This helps mitigating problems with case-insensitive
+        * databases.
+        *
+        * @param string $identifier
+        * @return string
+        */
+       public function hashIdentifier($identifier) {
+               $identifier = $this->canonicalizeAndCheckFileIdentifier($identifier);
+               return sha1($this->getUniqueIdentifier($identifier));
+       }
+
+       /**
+        * Calculate unique identifier by taking the case sensitivity of the file system
+        * into account
+        *
+        * @param string $identifier
+        * @return string
+        */
+       protected function getUniqueIdentifier($identifier) {
+               if (!$this->isCaseSensitiveFileSystem()) {
+                       $identifier = strtolower($identifier);
+               }
+               return $identifier;
+       }
 
        /**
         * Creates a new file and returns the matching file object for it.
         *
         * @abstract
         * @param string $fileName
-        * @param \TYPO3\CMS\Core\Resource\Folder $parentFolder
+        * @param Folder $parentFolder
         * @return \TYPO3\CMS\Core\Resource\File
         */
-       abstract public function createFile($fileName, \TYPO3\CMS\Core\Resource\Folder $parentFolder);
+       abstract public function createFile($fileName, Folder $parentFolder);
 
        /**
         * Returns the contents of a file. Beware that this requires to load the
@@ -266,20 +295,20 @@ abstract class AbstractDriver {
         * external location. So this might be an expensive operation (both in terms
         * of processing resources and money) for large files.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @return string The file contents
         */
-       abstract public function getFileContents(\TYPO3\CMS\Core\Resource\FileInterface $file);
+       abstract public function getFileContents(FileInterface $file);
 
        /**
         * Sets the contents of a file to the specified value.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @param string $contents
         * @return integer The number of bytes written to the file
         * @throws \RuntimeException if the operation failed
         */
-       abstract public function setFileContents(\TYPO3\CMS\Core\Resource\FileInterface $file, $contents);
+       abstract public function setFileContents(FileInterface $file, $contents);
 
        /**
         * Adds a file from the local server hard disk to a given path in TYPO3s virtual file system.
@@ -287,12 +316,12 @@ abstract class AbstractDriver {
         * This assumes that the local file exists, so no further check is done here!
         *
         * @param string $localFilePath
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
+        * @param Folder $targetFolder
         * @param string $fileName The name to add the file under
         * @param \TYPO3\CMS\Core\Resource\AbstractFile $updateFileObject Optional file object to update (instead of creating a new object). With this parameter, this function can be used to "populate" a dummy file object with a real file underneath.
-        * @return \TYPO3\CMS\Core\Resource\FileInterface
+        * @return FileInterface
         */
-       abstract public function addFile($localFilePath, \TYPO3\CMS\Core\Resource\Folder $targetFolder, $fileName, \TYPO3\CMS\Core\Resource\AbstractFile $updateFileObject = NULL);
+       abstract public function addFile($localFilePath, Folder $targetFolder, $fileName, \TYPO3\CMS\Core\Resource\AbstractFile $updateFileObject = NULL);
 
        /**
         * Checks if a resource exists - does not care for the type (file or folder).
@@ -316,51 +345,51 @@ abstract class AbstractDriver {
         *
         * @abstract
         * @param string $fileName
-        * @param \TYPO3\CMS\Core\Resource\Folder $folder
+        * @param Folder $folder
         * @return boolean
         */
-       abstract public function fileExistsInFolder($fileName, \TYPO3\CMS\Core\Resource\Folder $folder);
+       abstract public function fileExistsInFolder($fileName, Folder $folder);
 
        /**
         * Returns a (local copy of) a file for processing it. When changing the
         * file, you have to take care of replacing the current version yourself!
         *
         * @abstract
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @param bool $writable Set this to FALSE if you only need the file for read operations. This might speed up things, e.g. by using a cached local version. Never modify the file if you have set this flag!
         * @return string The path to the file on the local disk
         */
        // TODO decide if this should return a file handle object
-       abstract public function getFileForLocalProcessing(\TYPO3\CMS\Core\Resource\FileInterface $file, $writable = TRUE);
+       abstract public function getFileForLocalProcessing(FileInterface $file, $writable = TRUE);
 
        /**
         * Returns the permissions of a file as an array (keys r, w) of boolean flags
         *
         * @abstract
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @return array
         */
-       abstract public function getFilePermissions(\TYPO3\CMS\Core\Resource\FileInterface $file);
+       abstract public function getFilePermissions(FileInterface $file);
 
        /**
         * Returns the permissions of a folder as an array (keys r, w) of boolean flags
         *
         * @abstract
-        * @param \TYPO3\CMS\Core\Resource\Folder $folder
+        * @param Folder $folder
         * @return array
         */
-       abstract public function getFolderPermissions(\TYPO3\CMS\Core\Resource\Folder $folder);
+       abstract public function getFolderPermissions(Folder $folder);
 
        /**
         * Renames a file
         *
         * @abstract
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @param string $newName
         * @return string The new identifier of the file if the operation succeeds
         * @throws \RuntimeException if renaming the file failed
         */
-       abstract public function renameFile(\TYPO3\CMS\Core\Resource\FileInterface $file, $newName);
+       abstract public function renameFile(FileInterface $file, $newName);
 
        /**
         * Replaces the contents (and file-specific metadata) of a file object with a local file.
@@ -396,11 +425,11 @@ abstract class AbstractDriver {
        /**
         * Returns information about a file for a given file object.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @param array $propertiesToExtract Array of properties which should be extracted, if empty all will be extracted
         * @return array
         */
-       public function getFileInfo(\TYPO3\CMS\Core\Resource\FileInterface $file, array $propertiesToExtract = array()) {
+       public function getFileInfo(FileInterface $file, array $propertiesToExtract = array()) {
                return $this->getFileInfoByIdentifier($file->getIdentifier(), $propertiesToExtract);
        }
 
@@ -409,7 +438,7 @@ abstract class AbstractDriver {
         *
         * @param string $identifier
         * @throws \TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException
-        * @return \TYPO3\CMS\Core\Resource\FileInterface
+        * @return FileInterface
         */
        public function getFile($identifier) {
                $fileObject = NULL;
@@ -436,7 +465,7 @@ abstract class AbstractDriver {
         * Returns a folder by its identifier.
         *
         * @param string $identifier
-        * @return \TYPO3\CMS\Core\Resource\Folder
+        * @return Folder
         */
        public function getFolder($identifier) {
                $name = $this->getNameFromIdentifier($identifier);
@@ -448,10 +477,10 @@ abstract class AbstractDriver {
         * on the identifiers because non-hierarchical storages might fail otherwise.
         *
         * @param $name
-        * @param \TYPO3\CMS\Core\Resource\Folder $parentFolder
-        * @return \TYPO3\CMS\Core\Resource\Folder
+        * @param Folder $parentFolder
+        * @return Folder
         */
-       abstract public function getFolderInFolder($name, \TYPO3\CMS\Core\Resource\Folder $parentFolder);
+       abstract public function getFolderInFolder($name, Folder $parentFolder);
 
        /**
         * Applies a set of filter methods to a file name to find out if it should be used or not. This is e.g. used by
@@ -501,65 +530,65 @@ abstract class AbstractDriver {
         * Copies a file to a temporary path and returns that path.
         *
         * @abstract
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @return string The temporary path
         */
-       abstract public function copyFileToTemporaryPath(\TYPO3\CMS\Core\Resource\FileInterface $file);
+       abstract public function copyFileToTemporaryPath(FileInterface $file);
 
        /**
         * Moves a file *within* the current storage.
         * Note that this is only about an intra-storage move action, where a file is just
         * moved to another folder in the same storage.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
+        * @param FileInterface $file
+        * @param Folder $targetFolder
         * @param string $fileName
         * @return string The new identifier of the file
         */
-       abstract public function moveFileWithinStorage(\TYPO3\CMS\Core\Resource\FileInterface $file, \TYPO3\CMS\Core\Resource\Folder $targetFolder, $fileName);
+       abstract public function moveFileWithinStorage(FileInterface $file, Folder $targetFolder, $fileName);
 
        /**
         * Copies a file *within* the current storage.
         * Note that this is only about an intra-storage copy action, where a file is just
         * copied to another folder in the same storage.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
+        * @param FileInterface $file
+        * @param Folder $targetFolder
         * @param string $fileName
-        * @return \TYPO3\CMS\Core\Resource\FileInterface The new (copied) file object.
+        * @return FileInterface The new (copied) file object.
         */
-       abstract public function copyFileWithinStorage(\TYPO3\CMS\Core\Resource\FileInterface $file, \TYPO3\CMS\Core\Resource\Folder $targetFolder, $fileName);
+       abstract public function copyFileWithinStorage(FileInterface $file, Folder $targetFolder, $fileName);
 
        /**
         * Folder equivalent to moveFileWithinStorage().
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $folderToMove
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
+        * @param Folder $folderToMove
+        * @param Folder $targetFolder
         * @param string $newFolderName
         * @return array A map of old to new file identifiers
         */
-       abstract public function moveFolderWithinStorage(\TYPO3\CMS\Core\Resource\Folder $folderToMove, \TYPO3\CMS\Core\Resource\Folder $targetFolder, $newFolderName);
+       abstract public function moveFolderWithinStorage(Folder $folderToMove, Folder $targetFolder, $newFolderName);
 
        /**
         * Folder equivalent to copyFileWithinStorage().
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $folderToCopy
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
+        * @param Folder $folderToCopy
+        * @param Folder $targetFolder
         * @param string $newFileName
         * @return boolean
         */
-       abstract public function copyFolderWithinStorage(\TYPO3\CMS\Core\Resource\Folder $folderToCopy, \TYPO3\CMS\Core\Resource\Folder $targetFolder, $newFileName);
+       abstract public function copyFolderWithinStorage(Folder $folderToCopy, Folder $targetFolder, $newFileName);
 
        /**
         * Move a folder from another storage.
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $folderToMove
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetParentFolder
+        * @param Folder $folderToMove
+        * @param Folder $targetParentFolder
         * @param string $newFolderName
         * @throws \BadMethodCallException
         * @return boolean
         */
-       public function moveFolderBetweenStorages(\TYPO3\CMS\Core\Resource\Folder $folderToMove, \TYPO3\CMS\Core\Resource\Folder $targetParentFolder, $newFolderName) {
+       public function moveFolderBetweenStorages(Folder $folderToMove, Folder $targetParentFolder, $newFolderName) {
                // This is not implemented for now as moving files between storages might cause quite some headaches when
                // something goes wrong. It is also not that common of a use case, so it does not hurt that much to leave it out
                // for now.
@@ -569,13 +598,13 @@ abstract class AbstractDriver {
        /**
         * Copy a folder from another storage.
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $folderToCopy
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetParentFolder
+        * @param Folder $folderToCopy
+        * @param Folder $targetParentFolder
         * @param string $newFolderName
         * @throws \BadMethodCallException
         * @return boolean
         */
-       public function copyFolderBetweenStorages(\TYPO3\CMS\Core\Resource\Folder $folderToCopy, \TYPO3\CMS\Core\Resource\Folder $targetParentFolder, $newFolderName) {
+       public function copyFolderBetweenStorages(Folder $folderToCopy, Folder $targetParentFolder, $newFolderName) {
                throw new \BadMethodCallException('Not yet implemented!', 1330262731);
        }
 
@@ -585,31 +614,31 @@ abstract class AbstractDriver {
         * this has to be taken care of in the upper layers (e.g. the Storage)!
         *
         * @abstract
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @return boolean TRUE if deleting the file succeeded
         */
-       abstract public function deleteFile(\TYPO3\CMS\Core\Resource\FileInterface $file);
+       abstract public function deleteFile(FileInterface $file);
 
        /**
         * Removes a folder from this storage.
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $folder
+        * @param Folder $folder
         * @param boolean $deleteRecursively
         * @return boolean
         */
-       abstract public function deleteFolder(\TYPO3\CMS\Core\Resource\Folder $folder, $deleteRecursively = FALSE);
+       abstract public function deleteFolder(Folder $folder, $deleteRecursively = FALSE);
 
        /**
         * Adds a file at the specified location. This should only be used internally.
         *
         * @abstract
         * @param string $localFilePath
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
+        * @param Folder $targetFolder
         * @param string $targetFileName
         * @return string The new identifier of the file
         */
        // TODO check if this is still necessary if we move more logic to the storage
-       abstract public function addFileRaw($localFilePath, \TYPO3\CMS\Core\Resource\Folder $targetFolder, $targetFileName);
+       abstract public function addFileRaw($localFilePath, Folder $targetFolder, $targetFileName);
 
        /**
         * Deletes a file without access and usage checks.
@@ -632,7 +661,7 @@ abstract class AbstractDriver {
         * Returns the root level folder of the storage.
         *
         * @abstract
-        * @return \TYPO3\CMS\Core\Resource\Folder
+        * @return Folder
         */
        abstract public function getRootLevelFolder();
 
@@ -640,7 +669,7 @@ abstract class AbstractDriver {
         * Returns the default folder new files should be put into.
         *
         * @abstract
-        * @return \TYPO3\CMS\Core\Resource\Folder
+        * @return Folder
         */
        abstract public function getDefaultFolder();
 
@@ -648,10 +677,10 @@ abstract class AbstractDriver {
         * Creates a folder.
         *
         * @param string $newFolderName
-        * @param \TYPO3\CMS\Core\Resource\Folder $parentFolder
-        * @return \TYPO3\CMS\Core\Resource\Folder The new (created) folder object
+        * @param Folder $parentFolder
+        * @return Folder The new (created) folder object
         */
-       abstract public function createFolder($newFolderName, \TYPO3\CMS\Core\Resource\Folder $parentFolder);
+       abstract public function createFolder($newFolderName, Folder $parentFolder);
 
        /**
         * Returns a list of all folders in a given path
@@ -681,20 +710,20 @@ abstract class AbstractDriver {
         *
         * @abstract
         * @param string $folderName
-        * @param \TYPO3\CMS\Core\Resource\Folder $folder
+        * @param Folder $folder
         * @return boolean
         */
-       abstract public function folderExistsInFolder($folderName, \TYPO3\CMS\Core\Resource\Folder $folder);
+       abstract public function folderExistsInFolder($folderName, Folder $folder);
 
        /**
         * Renames a folder in this storage.
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $folder
+        * @param Folder $folder
         * @param string $newName The target path (including the file name!)
         * @return array A map of old to new file identifiers
         * @throws \RuntimeException if renaming the folder failed
         */
-       abstract public function renameFolder(\TYPO3\CMS\Core\Resource\Folder $folder, $newName);
+       abstract public function renameFolder(Folder $folder, $newName);
 
        /**
         * Checks if a given object or identifier is within a container, e.g. if
@@ -702,18 +731,59 @@ abstract class AbstractDriver {
         * This can e.g. be used to check for webmounts.
         *
         * @abstract
-        * @param \TYPO3\CMS\Core\Resource\Folder $container
+        * @param Folder $container
         * @param mixed $content An object or an identifier to check
         * @return boolean TRUE if $content is within $container
         */
-       abstract public function isWithin(\TYPO3\CMS\Core\Resource\Folder $container, $content);
+       abstract public function isWithin(Folder $container, $content);
 
        /**
         * Checks if a folder contains files and (if supported) other folders.
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $folder
+        * @param Folder $folder
         * @return boolean TRUE if there are no files and folders within $folder
         */
-       abstract public function isFolderEmpty(\TYPO3\CMS\Core\Resource\Folder $folder);
+       abstract public function isFolderEmpty(Folder $folder);
 
-}
+       /**
+        * Returns TRUE if this driver uses case-sensitive identifiers. NOTE: This
+        * is a configurable setting, but the setting does not change the way the
+        * underlying file system treats the identifiers; the setting should
+        * therefore always reflect the file system and not try to change its
+        * behaviour
+        *
+        * @return boolean
+        */
+       public function isCaseSensitiveFileSystem() {
+               if (isset($this->configuration['caseSensitive'])) {
+                       return (bool)$this->configuration['caseSensitive'];
+               }
+               return TRUE;
+       }
+
+       /**
+        * Makes sure the path given as parameter is valid
+        *
+        * @param string $fileIdentifier The file path (most times filePath)
+        * @return string
+        * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidPathException
+        */
+       abstract protected function canonicalizeAndCheckFileIdentifier($fileIdentifier);
+
+       /**
+        * Makes sure the path given as parameter is valid
+        *
+        * @param string $folderIdentifier The folder path (most times filePath)
+        * @return string
+        */
+       abstract protected function canonicalizeAndCheckFolderIdentifier($folderIdentifier);
+
+       /**
+        * Returns the identifier of the folder the file resides in
+        *
+        * @param $fileIdentifier
+        *
+        * @return mixed
+        */
+       abstract public function getFolderIdentifierForFile($fileIdentifier);
+}
\ No newline at end of file
index 4e011e6..197add8 100644 (file)
@@ -52,8 +52,9 @@ abstract class AbstractHierarchicalFilesystemDriver extends AbstractDriver {
         * @return string
         * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidPathException
         */
-       protected function canonicalizeAndCheckFilePath($filePath) {
+       protected function canonicalizeAndCheckFileIdentifier($filePath) {
                $filePath = \TYPO3\CMS\Core\Utility\PathUtility::getCanonicalPath($filePath);
+
                // filePath must be valid
                if (!$this->isPathValid($filePath)) {
                        throw new \TYPO3\CMS\Core\Resource\Exception\InvalidPathException('File ' . $filePath . ' is not valid (".." and "//" is not allowed in path).', 1320286857);
@@ -67,7 +68,20 @@ abstract class AbstractHierarchicalFilesystemDriver extends AbstractDriver {
         * @param string $folderPath The file path (including the file name!)
         * @return string
         */
-       protected function canonicalizeAndCheckFolderPath($folderPath) {
-               return $this->canonicalizeAndCheckFilePath($folderPath) . '/';
+       protected function canonicalizeAndCheckFolderIdentifier($folderPath) {
+               return $this->canonicalizeAndCheckFileIdentifier($folderPath) . '/';
+       }
+
+       /**
+        * Returns the identifier of the folder the file resides in
+        *
+        * @param string $fileIdentifier
+        * @return mixed
+        */
+       public function getFolderIdentifierForFile($fileIdentifier) {
+               $fileIdentifier = $this->canonicalizeAndCheckFileIdentifier($fileIdentifier);
+               return \TYPO3\CMS\Core\Utility\PathUtility::dirname($fileIdentifier) . '/';
        }
+
+
 }
index 57d78e1..03ca9d7 100644 (file)
@@ -28,6 +28,8 @@ namespace TYPO3\CMS\Core\Resource\Driver;
  * This copyright notice MUST APPEAR in all copies of the script!
  ***************************************************************/
 
+use TYPO3\CMS\Core\Resource\FileInterface;
+use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Resource\FolderInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\PathUtility;
@@ -81,7 +83,7 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidConfigurationException
         */
        static public function verifyConfiguration(array $configuration) {
-               self::calculateBasePath($configuration);
+               static::calculateBasePath($configuration);
        }
 
        /**
@@ -102,7 +104,9 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        public function initialize() {
                $this->determineBaseUrl();
                // The capabilities of this driver. See CAPABILITY_* constants for possible values
-               $this->capabilities = \TYPO3\CMS\Core\Resource\ResourceStorage::CAPABILITY_BROWSABLE | \TYPO3\CMS\Core\Resource\ResourceStorage::CAPABILITY_PUBLIC | \TYPO3\CMS\Core\Resource\ResourceStorage::CAPABILITY_WRITABLE;
+               $this->capabilities = \TYPO3\CMS\Core\Resource\ResourceStorage::CAPABILITY_BROWSABLE
+                       | \TYPO3\CMS\Core\Resource\ResourceStorage::CAPABILITY_PUBLIC
+                       | \TYPO3\CMS\Core\Resource\ResourceStorage::CAPABILITY_WRITABLE;
        }
 
        /**
@@ -112,11 +116,11 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @return void
         */
        protected function determineBaseUrl() {
-               if (\TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($this->absoluteBasePath, PATH_site)) {
+               if (GeneralUtility::isFirstPartOfStr($this->absoluteBasePath, PATH_site)) {
                        // use site-relative URLs
                        // TODO add unit test
                        $this->baseUri = substr($this->absoluteBasePath, strlen(PATH_site));
-               } elseif (isset($this->configuration['baseUri']) && \TYPO3\CMS\Core\Utility\GeneralUtility::isValidUrl($this->configuration['baseUri'])) {
+               } elseif (isset($this->configuration['baseUri']) && GeneralUtility::isValidUrl($this->configuration['baseUri'])) {
                        $this->baseUri = rtrim($this->configuration['baseUri'], '/') . '/';
                } else {
 
@@ -152,9 +156,10 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * Returns the public URL to a file. For the local driver, this will always
         * return a path relative to PATH_site.
         *
-        * @param \TYPO3\CMS\Core\Resource\ResourceInterface  $fileOrFolder
-        * @param bool $relativeToCurrentScript Determines whether the URL returned should be relative to the current script, in case it is relative at all (only for the LocalDriver)
+        * @param \TYPO3\CMS\Core\Resource\ResourceInterface $fileOrFolder
+        * @param boolean $relativeToCurrentScript Determines whether the URL returned should be relative to the current script, in case it is relative at all (only for the LocalDriver)
         * @return string
+        * @throws \TYPO3\CMS\Core\Resource\Exception
         */
        public function getPublicUrl(\TYPO3\CMS\Core\Resource\ResourceInterface $fileOrFolder, $relativeToCurrentScript = FALSE) {
                if ($this->configuration['pathType'] === 'relative' && rtrim($this->configuration['basePath'], '/') !== '') {
@@ -175,7 +180,7 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Returns the root level folder of the storage.
         *
-        * @return \TYPO3\CMS\Core\Resource\Folder
+        * @return Folder
         */
        public function getRootLevelFolder() {
                if (!$this->rootLevelFolder) {
@@ -187,7 +192,7 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Returns the default folder new files should be put into.
         *
-        * @return \TYPO3\CMS\Core\Resource\Folder
+        * @return Folder
         */
        public function getDefaultFolder() {
                if (!$this->defaultLevelFolder) {
@@ -203,13 +208,13 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * Creates a folder.
         *
         * @param string $newFolderName
-        * @param \TYPO3\CMS\Core\Resource\Folder $parentFolder
-        * @return \TYPO3\CMS\Core\Resource\Folder The new (created) folder object
+        * @param Folder $parentFolder
+        * @return Folder The new (created) folder object
         */
-       public function createFolder($newFolderName, \TYPO3\CMS\Core\Resource\Folder $parentFolder) {
+       public function createFolder($newFolderName, Folder $parentFolder) {
                $newFolderName = trim($this->sanitizeFileName($newFolderName), '/');
-               $newFolderPath = $this->canonicalizeAndCheckFolderPath($parentFolder->getIdentifier() . '/' . $newFolderName);
-               \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir($this->getAbsoluteBasePath() . $newFolderPath);
+               $newFolderPath = $this->canonicalizeAndCheckFolderIdentifier($parentFolder->getIdentifier() . '/' . $newFolderName);
+               GeneralUtility::mkdir($this->getAbsoluteBasePath() . $newFolderPath);
                return \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->createFolderObject($this->storage, $newFolderPath, $newFolderName);
        }
 
@@ -223,7 +228,7 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         */
        public function getFileInfoByIdentifier($fileIdentifier, array $propertiesToExtract = array()) {
                // Makes sure the Path given as parameter is valid
-               $fileIdentifier = $this->canonicalizeAndCheckFilePath($fileIdentifier);
+               $fileIdentifier = $this->canonicalizeAndCheckFileIdentifier($fileIdentifier);
                $dirPath = PathUtility::dirname($fileIdentifier);
                if ($dirPath === '' || $dirPath === '.') {
                        $dirPath = '/';
@@ -294,10 +299,11 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @param array $itemRows
         * @param boolean $recursive
         * @return array
+        * @throws \InvalidArgumentException
         */
-       // TODO add unit tests
        protected function getDirectoryItemList($basePath, $start, $numberOfItems, array $filterMethods, $itemHandlerMethod, $itemRows = array(), $recursive = FALSE) {
-               $basePath = $this->canonicalizeAndCheckFolderPath($basePath);
+               // TODO add unit tests
+               $basePath = $this->canonicalizeAndCheckFolderIdentifier($basePath);
                $realPath = rtrim($this->absoluteBasePath . trim($basePath, '/'), '/') . '/';
                if (!is_dir($realPath)) {
                        throw new \InvalidArgumentException('Cannot list items in directory ' . $basePath . ' - does not exist or is no directory', 1314349666);
@@ -337,7 +343,15 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
                        $iterator->next();
                        $identifier = $basePath . $iteratorItem['path'];
 
-                       if ($this->applyFilterMethodsToDirectoryItem($filterMethods, $iteratorItem['name'], $identifier, dirname($identifier) . '/', isset($itemRows[$identifier]) ? array('indexData' => $itemRows[$identifier]) : array()) === FALSE) {
+                       if (
+                               !$this->applyFilterMethodsToDirectoryItem(
+                                       $filterMethods,
+                                       $iteratorItem['name'],
+                                       $identifier,
+                                       dirname($identifier) . '/',
+                                       isset($itemRows[$identifier]) ? array('indexData' => $itemRows[$identifier]) : array()
+                               )
+                       ) {
                                continue;
                        }
 
@@ -374,7 +388,7 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @return array
         */
        protected function getFileList_itemCallback($fileName, $path, array $fileRow = array()) {
-               $filePath = $this->getAbsolutePath($this->canonicalizeAndCheckFilePath($path . $fileName));
+               $filePath = $this->getAbsolutePath($this->canonicalizeAndCheckFileIdentifier($path . $fileName));
                if (!is_file($filePath)) {
                        return array('', array());
                }
@@ -396,7 +410,7 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @return array
         */
        protected function getFolderList_itemCallback($folderName, $parentPath, array $folderRow = array()) {
-               $folderPath = $this->getAbsolutePath($this->canonicalizeAndCheckFilePath($parentPath . $folderName));
+               $folderPath = $this->getAbsolutePath($this->canonicalizeAndCheckFileIdentifier($parentPath . $folderName));
 
                if (!is_dir($folderPath)) {
                        return array('', array());
@@ -470,7 +484,7 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         */
        protected function extractFileInformation($filePath, $containerPath, array $propertiesToExtract = array()) {
                if (count($propertiesToExtract) === 0) {
-                       $propertiesToExtract = array('size', 'atime', 'atime', 'mtime', 'ctime', 'mimetype', 'name', 'identifier', 'storage');
+                       $propertiesToExtract = array('size', 'atime', 'atime', 'mtime', 'ctime', 'mimetype', 'name', 'identifier', 'identifier_hash', 'storage', 'folder_hash');
                }
                $fileInformation = array();
                foreach ($propertiesToExtract as $property) {
@@ -490,6 +504,8 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @throws \InvalidArgumentException
         */
        public function getSpecificFileInformation($filePath, $containerPath, $property) {
+               $identifier = $this->getUniqueIdentifier($containerPath . PathUtility::basename($filePath));
+
                switch ($property) {
                        case 'size':
                                return filesize($filePath);
@@ -504,9 +520,13 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
                        case 'mimetype':
                                return $this->getMimeTypeOfFile($filePath);
                        case 'identifier':
-                               return $containerPath . PathUtility::basename($filePath);
+                               return $identifier;
                        case 'storage':
                                return $this->storage->getUid();
+                       case 'identifier_hash':
+                               return $this->hashIdentifier($identifier);
+                       case 'folder_hash':
+                               return $this->hashIdentifier($this->getFolderIdentifierForFile($identifier));
                        default:
                                throw new \InvalidArgumentException(sprintf('The information "%s" is not available.', $property));
                }
@@ -521,11 +541,12 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         */
        protected function extractFolderInformation($folderPath, $containerPath) {
                $folderName = PathUtility::basename($folderPath);
+               $identifier = $this->getUniqueIdentifier($containerPath . PathUtility::basename($folderName));
                $folderInformation = array(
                        'ctime' => filectime($folderPath),
                        'mtime' => filemtime($folderPath),
                        'name' => $folderName,
-                       'identifier' => $containerPath . $folderName . '/',
+                       'identifier' => $identifier . '/',
                        'storage' => $this->storage->getUid()
                );
                return $folderInformation;
@@ -543,15 +564,16 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Returns the absolute path of a file or folder.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface|\TYPO3\CMS\Core\Resource\Folder|string $file
+        * @param FileInterface|Folder|string $file
         * @return string
+        * @throws \RuntimeException
         */
        public function getAbsolutePath($file) {
-               if ($file instanceof \TYPO3\CMS\Core\Resource\FileInterface) {
-                       $path = $this->absoluteBasePath . $this->canonicalizeAndCheckFilePath(ltrim($file->getIdentifier(), '/'));
-               } elseif ($file instanceof \TYPO3\CMS\Core\Resource\Folder) {
+               if ($file instanceof FileInterface) {
+                       $path = $this->absoluteBasePath . $this->canonicalizeAndCheckFileIdentifier(ltrim($file->getIdentifier(), '/'));
+               } elseif ($file instanceof Folder) {
                        // We can assume a trailing slash here because it is added by the folder object on construction.
-                       $path = $this->absoluteBasePath . $this->canonicalizeAndCheckFolderPath(ltrim($file->getIdentifier(), '/'));
+                       $path = $this->absoluteBasePath . $this->canonicalizeAndCheckFolderIdentifier(ltrim($file->getIdentifier(), '/'));
                } elseif (is_string($file)) {
                        $path = $this->absoluteBasePath . ltrim($file, '/');
                } else {
@@ -579,11 +601,13 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Creates a (cryptographic) hash for a file.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @param string $hashAlgorithm The hash algorithm to use
         * @return string
+        * @throws \RuntimeException
+        * @throws \InvalidArgumentException
         */
-       public function hash(\TYPO3\CMS\Core\Resource\FileInterface $file, $hashAlgorithm) {
+       public function hash(FileInterface $file, $hashAlgorithm) {
                if (!in_array($hashAlgorithm, $this->getSupportedHashAlgorithms())) {
                        throw new \InvalidArgumentException('Hash algorithm "' . $hashAlgorithm . '" is not supported.', 1304964032);
                }
@@ -606,17 +630,19 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * This assumes that the local file exists, so no further check is done here!
         *
         * @param string $localFilePath
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
+        * @param Folder $targetFolder
         * @param string $fileName The name to add the file under
         * @param \TYPO3\CMS\Core\Resource\AbstractFile $updateFileObject File object to update (instead of creating a new object). With this parameter, this function can be used to "populate" a dummy file object with a real file underneath.
         * @todo \TYPO3\CMS\Core\Resource\File $updateFileObject should be \TYPO3\CMS\Core\Resource\FileInterface, but indexer logic is only in \TYPO3\CMS\Core\Resource\File
-        * @return \TYPO3\CMS\Core\Resource\FileInterface
+        * @return FileInterface
+        * @throws \RuntimeException
+        * @throws \InvalidArgumentException
         */
-       public function addFile($localFilePath, \TYPO3\CMS\Core\Resource\Folder $targetFolder, $fileName, \TYPO3\CMS\Core\Resource\AbstractFile $updateFileObject = NULL) {
-               $localFilePath = $this->canonicalizeAndCheckFilePath($localFilePath);
+       public function addFile($localFilePath, Folder $targetFolder, $fileName, \TYPO3\CMS\Core\Resource\AbstractFile $updateFileObject = NULL) {
+               $localFilePath = $this->canonicalizeAndCheckFileIdentifier($localFilePath);
                // as for the "virtual storage" for backwards-compatibility, this check always fails, as the file probably lies under PATH_site
                // thus, it is not checked here
-               if (\TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($localFilePath, $this->absoluteBasePath) && $this->storage->getUid() > 0) {
+               if (GeneralUtility::isFirstPartOfStr($localFilePath, $this->absoluteBasePath) && $this->storage->getUid() > 0) {
                        throw new \InvalidArgumentException('Cannot add a file that is already part of this storage.', 1314778269);
                }
                $relativeTargetPath = ltrim($targetFolder->getIdentifier(), '/');
@@ -632,7 +658,7 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
                }
                clearstatcache();
                // Change the permissions of the file
-               \TYPO3\CMS\Core\Utility\GeneralUtility::fixPermissions($targetPath);
+               GeneralUtility::fixPermissions($targetPath);
                $fileInfo = $this->getFileInfoByIdentifier($relativeTargetPath);
                if ($updateFileObject) {
                        $updateFileObject->updateProperties($fileInfo);
@@ -650,7 +676,7 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @return boolean
         */
        public function resourceExists($identifier) {
-               $identifier = $this->canonicalizeAndCheckFilePath(ltrim($identifier, '/'));
+               $identifier = $this->canonicalizeAndCheckFileIdentifier(ltrim($identifier, '/'));
                $absoluteResourcePath = $this->absoluteBasePath . $identifier;
                return file_exists($absoluteResourcePath);
        }
@@ -662,7 +688,7 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @return boolean
         */
        public function fileExists($identifier) {
-               $identifier = $this->canonicalizeAndCheckFilePath(ltrim($identifier, '/'));
+               $identifier = $this->canonicalizeAndCheckFileIdentifier(ltrim($identifier, '/'));
                $absoluteFilePath = $this->absoluteBasePath . $identifier;
                return is_file($absoluteFilePath);
        }
@@ -671,10 +697,10 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * Checks if a file inside a storage folder exists
         *
         * @param string $fileName
-        * @param \TYPO3\CMS\Core\Resource\Folder $folder
+        * @param Folder $folder
         * @return boolean
         */
-       public function fileExistsInFolder($fileName, \TYPO3\CMS\Core\Resource\Folder $folder) {
+       public function fileExistsInFolder($fileName, Folder $folder) {
                $identifier = ltrim($folder->getIdentifier(), '/') . $fileName;
                return $this->fileExists($identifier);
        }
@@ -686,7 +712,7 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @return boolean
         */
        public function folderExists($identifier) {
-               $identifier = $this->canonicalizeAndCheckFilePath(ltrim($identifier, '/'));
+               $identifier = $this->canonicalizeAndCheckFileIdentifier(ltrim($identifier, '/'));
                $absoluteFilePath = $this->absoluteBasePath . $identifier;
                return is_dir($absoluteFilePath);
        }
@@ -695,12 +721,12 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * Checks if a file inside a storage folder exists.
         *
         * @param string $folderName
-        * @param \TYPO3\CMS\Core\Resource\Folder $folder
+        * @param Folder $folder
         * @return boolean
         */
-       public function folderExistsInFolder($folderName, \TYPO3\CMS\Core\Resource\Folder $folder) {
+       public function folderExistsInFolder($folderName, Folder $folder) {
                $identifier = $folder->getIdentifier() . $folderName;
-               $identifier = $this->canonicalizeAndCheckFilePath($identifier);
+               $identifier = $this->canonicalizeAndCheckFileIdentifier($identifier);
                return $this->folderExists($identifier);
        }
 
@@ -708,10 +734,10 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * Returns a folder within the given folder.
         *
         * @param string $name The name of the folder to get
-        * @param \TYPO3\CMS\Core\Resource\Folder $parentFolder
-        * @return \TYPO3\CMS\Core\Resource\Folder
+        * @param Folder $parentFolder
+        * @return Folder
         */
-       public function getFolderInFolder($name, \TYPO3\CMS\Core\Resource\Folder $parentFolder) {
+       public function getFolderInFolder($name, Folder $parentFolder) {
                $folderIdentifier = $parentFolder->getIdentifier() . $name . '/';
                return $this->getFolder($folderIdentifier);
        }
@@ -722,6 +748,7 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @param \TYPO3\CMS\Core\Resource\AbstractFile $file
         * @param string $localFilePath
         * @return boolean TRUE if the operation succeeded
+        * @throws \RuntimeException
         */
        public function replaceFile(\TYPO3\CMS\Core\Resource\AbstractFile $file, $localFilePath) {
                $filePath = $this->getAbsolutePath($file);
@@ -739,11 +766,12 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * Adds a file at the specified location. This should only be used internally.
         *
         * @param string $localFilePath
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
+        * @param Folder $targetFolder
         * @param string $targetFileName
         * @return boolean TRUE if adding the file succeeded
+        * @throws \RuntimeException
         */
-       public function addFileRaw($localFilePath, \TYPO3\CMS\Core\Resource\Folder $targetFolder, $targetFileName) {
+       public function addFileRaw($localFilePath, Folder $targetFolder, $targetFileName) {
                $fileIdentifier = $targetFolder->getIdentifier() . $targetFileName;
                $absoluteFilePath = $this->absoluteBasePath . $fileIdentifier;
                $result = copy($localFilePath, $absoluteFilePath);
@@ -761,9 +789,10 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         *
         * @param string $identifier
         * @return boolean TRUE if removing the file succeeded
+        * @throws \RuntimeException
         */
        public function deleteFileRaw($identifier) {
-               $identifier = $this->canonicalizeAndCheckFilePath(ltrim($identifier, '/'));
+               $identifier = $this->canonicalizeAndCheckFileIdentifier(ltrim($identifier, '/'));
 
                $targetPath = $this->absoluteBasePath . $identifier;
                $result = unlink($targetPath);
@@ -778,16 +807,16 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * Note that this is only about an intra-storage move action, where a file is just
         * moved to another folder in the same storage.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
+        * @param FileInterface $file
+        * @param Folder $targetFolder
         * @param string $fileName
-        * @return \TYPO3\CMS\Core\Resource\FileInterface The new (copied) file object.
+        * @return FileInterface The new (copied) file object.
         */
-       public function copyFileWithinStorage(\TYPO3\CMS\Core\Resource\FileInterface $file, \TYPO3\CMS\Core\Resource\Folder $targetFolder, $fileName) {
+       public function copyFileWithinStorage(FileInterface $file, Folder $targetFolder, $fileName) {
                // TODO add unit test
                $sourcePath = $this->getAbsolutePath($file);
                $targetPath = ltrim($targetFolder->getIdentifier(), '/') . $fileName;
-               $targetPath = $this->canonicalizeAndCheckFilePath($targetPath);
+               $targetPath = $this->canonicalizeAndCheckFileIdentifier($targetPath);
 
                copy($sourcePath, $this->absoluteBasePath . $targetPath);
                return $this->getFile($targetPath);
@@ -798,15 +827,16 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * Note that this is only about an intra-storage move action, where a file is just
         * moved to another folder in the same storage.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
+        * @param FileInterface $file
+        * @param Folder $targetFolder
         * @param string $fileName
         * @return boolean
+        * @throws \RuntimeException
         */
-       public function moveFileWithinStorage(\TYPO3\CMS\Core\Resource\FileInterface $file, \TYPO3\CMS\Core\Resource\Folder $targetFolder, $fileName) {
+       public function moveFileWithinStorage(FileInterface $file, Folder $targetFolder, $fileName) {
                $sourcePath = $this->getAbsolutePath($file);
                $targetIdentifier = $targetFolder->getIdentifier() . $fileName;
-               $targetIdentifier = $this->canonicalizeAndCheckFilePath($targetIdentifier);
+               $targetIdentifier = $this->canonicalizeAndCheckFileIdentifier($targetIdentifier);
                $result = rename($sourcePath, $this->absoluteBasePath . $targetIdentifier);
                if ($result === FALSE) {
                        throw new \RuntimeException('Moving file ' . $sourcePath . ' to ' . $targetIdentifier . ' failed.', 1315314712);
@@ -817,10 +847,11 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Copies a file to a temporary path and returns that path.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @return string The temporary path
+        * @throws \RuntimeException
         */
-       public function copyFileToTemporaryPath(\TYPO3\CMS\Core\Resource\FileInterface $file) {
+       public function copyFileToTemporaryPath(FileInterface $file) {
                $sourcePath = $this->getAbsolutePath($file);
                $temporaryPath = $this->getTemporaryPathForFile($file);
                $result = copy($sourcePath, $temporaryPath);
@@ -838,6 +869,7 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @param string $relativeSourcePath
         * @param string $relativeTargetPath
         * @return array
+        * @throws \TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException
         */
        protected function createIdentifierMap(array $filesAndFolders, $relativeSourcePath, $relativeTargetPath) {
                $identifierMap = array();
@@ -856,15 +888,16 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Folder equivalent to moveFileWithinStorage().
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $folderToMove
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
+        * @param Folder $folderToMove
+        * @param Folder $targetFolder
         * @param string $newFolderName
         * @return array A map of old to new file identifiers
+        * @throws \RuntimeException
         */
-       public function moveFolderWithinStorage(\TYPO3\CMS\Core\Resource\Folder $folderToMove, \TYPO3\CMS\Core\Resource\Folder $targetFolder, $newFolderName) {
+       public function moveFolderWithinStorage(Folder $folderToMove, Folder $targetFolder, $newFolderName) {
                $relativeSourcePath = $folderToMove->getIdentifier();
                $sourcePath = $this->getAbsolutePath($relativeSourcePath);
-               $relativeTargetPath = $this->canonicalizeAndCheckFolderPath($targetFolder->getIdentifier() . $newFolderName);
+               $relativeTargetPath = $this->canonicalizeAndCheckFolderIdentifier($targetFolder->getIdentifier() . $newFolderName);
                $targetPath = $this->getAbsolutePath($relativeTargetPath);
                // get all files and folders we are going to move, to have a map for updating later.
                $filesAndFolders = $this->getFileAndFoldernamesInPath($sourcePath, TRUE);
@@ -880,16 +913,16 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Folder equivalent to copyFileWithinStorage().
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $folderToCopy
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetFolder
+        * @param Folder $folderToCopy
+        * @param Folder $targetFolder
         * @param string $newFolderName
-        * @throws \TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException
         * @return boolean
+        * @throws \TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException
         */
-       public function copyFolderWithinStorage(\TYPO3\CMS\Core\Resource\Folder $folderToCopy, \TYPO3\CMS\Core\Resource\Folder $targetFolder, $newFolderName) {
+       public function copyFolderWithinStorage(Folder $folderToCopy, Folder $targetFolder, $newFolderName) {
                // This target folder path already includes the topmost level, i.e. the folder this method knows as $folderToCopy.
                // We can thus rely on this folder being present and just create the subfolder we want to copy to.
-               $newFolderName = $this->canonicalizeAndCheckFolderPath($targetFolder->getIdentifier() . '/' . $newFolderName);
+               $newFolderName = $this->canonicalizeAndCheckFolderIdentifier($targetFolder->getIdentifier() . '/' . $newFolderName);
                $targetFolderPath = $this->getAbsoluteBasePath() . $newFolderName . '/';
                mkdir($targetFolderPath);
                $sourceFolderPath = $this->getAbsolutePath($folderToCopy);
@@ -921,12 +954,12 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Move a folder from another storage.
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $folderToMove
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetParentFolder
+        * @param Folder $folderToMove
+        * @param Folder $targetParentFolder
         * @param string $newFolderName
         * @return boolean
         */
-       public function moveFolderBetweenStorages(\TYPO3\CMS\Core\Resource\Folder $folderToMove, \TYPO3\CMS\Core\Resource\Folder $targetParentFolder, $newFolderName) {
+       public function moveFolderBetweenStorages(Folder $folderToMove, Folder $targetParentFolder, $newFolderName) {
                // TODO implement a clever shortcut here if both storages are of type local
                return parent::moveFolderBetweenStorages($folderToMove, $targetParentFolder, $newFolderName);
        }
@@ -934,12 +967,12 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Copy a folder from another storage.
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $folderToCopy
-        * @param \TYPO3\CMS\Core\Resource\Folder $targetParentFolder
+        * @param Folder $folderToCopy
+        * @param Folder $targetParentFolder
         * @param string $newFolderName
         * @return boolean
         */
-       public function copyFolderBetweenStorages(\TYPO3\CMS\Core\Resource\Folder $folderToCopy, \TYPO3\CMS\Core\Resource\Folder $targetParentFolder, $newFolderName) {
+       public function copyFolderBetweenStorages(Folder $folderToCopy, Folder $targetParentFolder, $newFolderName) {
                // TODO implement a clever shortcut here if both storages are of type local
                return parent::copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $newFolderName);
        }
@@ -947,13 +980,15 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Renames a file in this storage.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @param string $newName The target path (including the file name!)
         * @return string The identifier of the file after renaming
+        * @throws \TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException
+        * @throws \RuntimeException
         */
-       public function renameFile(\TYPO3\CMS\Core\Resource\FileInterface $file, $newName) {
+       public function renameFile(FileInterface $file, $newName) {
                // Makes sure the Path given as parameter is valid
-               $newName = $this->canonicalizeAndCheckFilePath($newName);
+               $newName = $this->canonicalizeAndCheckFileIdentifier($newName);
                $newName = $this->sanitizeFileName($newName);
                $newIdentifier = rtrim(GeneralUtility::fixWindowsFilePath(PathUtility::dirname($file->getIdentifier())), '/') . '/' . $newName;
                // The target should not exist already
@@ -973,18 +1008,18 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Renames a folder in this storage.
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $folder
+        * @param Folder $folder
         * @param string $newName The target path (including the file name!)
         * @return array A map of old to new file identifiers
         * @throws \RuntimeException if renaming the folder failed
         */
-       public function renameFolder(\TYPO3\CMS\Core\Resource\Folder $folder, $newName) {
+       public function renameFolder(Folder $folder, $newName) {
                // Makes sure the path given as parameter is valid
                $newName = $this->sanitizeFileName($newName);
-               $newName = $this->canonicalizeAndCheckFolderPath($newName);
+               $newName = $this->canonicalizeAndCheckFolderIdentifier($newName);
                $relativeSourcePath = $folder->getIdentifier();
                $sourcePath = $this->getAbsolutePath($relativeSourcePath);
-               $relativeTargetPath = $this->canonicalizeAndCheckFolderPath(PathUtility::dirname($relativeSourcePath). '/' . $newName);
+               $relativeTargetPath = $this->canonicalizeAndCheckFolderIdentifier(PathUtility::dirname($relativeSourcePath). '/' . $newName);
                $targetPath = $this->getAbsolutePath($relativeTargetPath);
                // get all files and folders we are going to move, to have a map for updating later.
                $filesAndFolders = $this->getFileAndFoldernamesInPath($sourcePath, TRUE);
@@ -1005,11 +1040,11 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Removes a file from this storage.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @return boolean TRUE if deleting the file succeeded
         * @throws \RuntimeException
         */
-       public function deleteFile(\TYPO3\CMS\Core\Resource\FileInterface $file) {
+       public function deleteFile(FileInterface $file) {
                $filePath = $this->getAbsolutePath($file);
                $result = unlink($filePath);
                if ($result === FALSE) {
@@ -1021,13 +1056,14 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Removes a folder from this storage.
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $folder
+        * @param Folder $folder
         * @param bool $deleteRecursively
         * @return boolean
+        * @throws \TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException
         */
-       public function deleteFolder(\TYPO3\CMS\Core\Resource\Folder $folder, $deleteRecursively = FALSE) {
+       public function deleteFolder(Folder $folder, $deleteRecursively = FALSE) {
                $folderPath = $this->getAbsolutePath($folder);
-               $result = \TYPO3\CMS\Core\Utility\GeneralUtility::rmdir($folderPath, $deleteRecursively);
+               $result = GeneralUtility::rmdir($folderPath, $deleteRecursively);
                if ($result === FALSE) {
                        throw new \TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException('Deleting folder "' . $folder->getIdentifier() . '" failed.', 1330119451);
                }
@@ -1037,10 +1073,10 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Checks if a folder contains files and (if supported) other folders.
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $folder
+        * @param Folder $folder
         * @return boolean TRUE if there are no files and folders within $folder
         */
-       public function isFolderEmpty(\TYPO3\CMS\Core\Resource\Folder $folder) {
+       public function isFolderEmpty(Folder $folder) {
                $path = $this->getAbsolutePath($folder);
                $dirHandle = opendir($path);
                while ($entry = readdir($dirHandle)) {
@@ -1057,11 +1093,11 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * first when in writable mode, so if you change the file,
         * you have to update it yourself afterwards.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @param boolean $writable Set this to FALSE if you only need the file for read operations. This might speed up things, e.g. by using a cached local version. Never modify the file if you have set this flag!
         * @return string The path to the file on the local disk
         */
-       public function getFileForLocalProcessing(\TYPO3\CMS\Core\Resource\FileInterface $file, $writable = TRUE) {
+       public function getFileForLocalProcessing(FileInterface $file, $writable = TRUE) {
                if ($writable === FALSE) {
                        // TODO check if this is ok or introduce additional measures against file changes
                        return $this->getAbsolutePath($file);
@@ -1074,11 +1110,11 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Returns the permissions of a file as an array (keys r, w) of boolean flags
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file The file object to check
+        * @param FileInterface $file The file object to check
         * @return array
         * @throws \RuntimeException If fetching the permissions failed
         */
-       public function getFilePermissions(\TYPO3\CMS\Core\Resource\FileInterface $file) {
+       public function getFilePermissions(FileInterface $file) {
                $filePath = $this->getAbsolutePath($file);
                return $this->getPermissions($filePath);
        }
@@ -1086,11 +1122,11 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Returns the permissions of a folder as an array (keys r, w) of boolean flags
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $folder
+        * @param Folder $folder
         * @return array
         * @throws \RuntimeException If fetching the permissions failed
         */
-       public function getFolderPermissions(\TYPO3\CMS\Core\Resource\Folder $folder) {
+       public function getFolderPermissions(Folder $folder) {
                $folderPath = $this->getAbsolutePath($folder);
                return $this->getPermissions($folderPath);
        }
@@ -1108,8 +1144,8 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
                        throw new \RuntimeException('Error while fetching permissions for ' . $path, 1319455097);
                }
                return array(
-                       'r' => (bool) is_readable($path),
-                       'w' => (bool) is_writable($path)
+                       'r' => (bool)is_readable($path),
+                       'w' => (bool)is_writable($path)
                );
        }
 
@@ -1118,30 +1154,32 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * a file or folder is within another folder.
         * This can e.g. be used to check for webmounts.
         *
-        * @param \TYPO3\CMS\Core\Resource\Folder $container
+        * @param Folder $container
         * @param mixed $content An object or an identifier to check
         * @return boolean TRUE if $content is within $container, always FALSE if $container is not within this storage
         */
-       public function isWithin(\TYPO3\CMS\Core\Resource\Folder $container, $content) {
+       public function isWithin(Folder $container, $content) {
                if ($container->getStorage() != $this->storage) {
                        return FALSE;
                }
-               if ($content instanceof \TYPO3\CMS\Core\Resource\FileInterface || $content instanceof \TYPO3\CMS\Core\Resource\Folder) {
+               if ($content instanceof FileInterface || $content instanceof Folder) {
                        $content = $container->getIdentifier();
                }
                $folderPath = $container->getIdentifier();
                $content = '/' . ltrim($content, '/');
-               return \TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($content, $folderPath);
+               return GeneralUtility::isFirstPartOfStr($content, $folderPath);
        }
 
        /**
         * Creates a new file and returns the matching file object for it.
         *
         * @param string $fileName
-        * @param \TYPO3\CMS\Core\Resource\Folder $parentFolder
+        * @param Folder $parentFolder
         * @return \TYPO3\CMS\Core\Resource\File
+        * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException
+        * @throws \RuntimeException
         */
-       public function createFile($fileName, \TYPO3\CMS\Core\Resource\Folder $parentFolder) {
+       public function createFile($fileName, Folder $parentFolder) {
                if (!$this->isValidFilename($fileName)) {
                        throw new \TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException('Invalid characters in fileName "' . $fileName . '"', 1320572272);
                }
@@ -1162,10 +1200,10 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * external location. So this might be an expensive operation (both in terms of
         * processing resources and money) for large files.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @return string The file contents
         */
-       public function getFileContents(\TYPO3\CMS\Core\Resource\FileInterface $file) {
+       public function getFileContents(FileInterface $file) {
                $filePath = $this->getAbsolutePath($file);
                return file_get_contents($filePath);
        }
@@ -1173,12 +1211,12 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
        /**
         * Sets the contents of a file to the specified value.
         *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param FileInterface $file
         * @param string $contents
         * @return integer The number of bytes written to the file
         * @throws \RuntimeException if the operation failed
         */
-       public function setFileContents(\TYPO3\CMS\Core\Resource\FileInterface $file, $contents) {
+       public function setFileContents(FileInterface $file, $contents) {
                $filePath = $this->getAbsolutePath($file);
                $result = file_put_contents($filePath, $contents);
                if ($result === FALSE) {
@@ -1201,7 +1239,7 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
                                $this->charsetConversion = $GLOBALS['LANG']->csConvObj;
                        } else {
                                // The object may not exist yet, so we need to create it now. Happens in the Install Tool for example.
-                               $this->charsetConversion = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Charset\\CharsetConverter');
+                               $this->charsetConversion = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Charset\\CharsetConverter');
                        }
                }
                return $this->charsetConversion;
index 5ab7e26..8fbac8b 100644 (file)
@@ -27,7 +27,6 @@ namespace TYPO3\CMS\Core\Resource;
  *  This copyright notice MUST APPEAR in all copies of the script!
  ***************************************************************/
 
-use TYPO3\CMS\Core\Resource\Index\MetaDataRepository;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -74,6 +73,11 @@ class File extends AbstractFile {
        protected $updatedProperties = array();
 
        /**
+        * @var \TYPO3\CMS\Core\Resource\Service\IndexerService
+        */
+       protected $indexerService = NULL;
+
+       /**
         * Constructor for a file object. Should normally not be used directly, use
         * the corresponding factory methods instead.
         *
index c514bb6..1529239 100644 (file)
@@ -406,7 +406,7 @@ class FileReference implements FileInterface {
                // TODO: Implement this function. This should only delete the
                // FileReference (sys_file_reference) record, not the file itself.
                throw new \BadMethodCallException('Function not implemented FileReference::delete().', 1333754461);
-               return $this->fileRepository->removeUsageRecord($this);
+               //return $this->fileRepository->removeUsageRecord($this);
        }
 
        /**
@@ -421,7 +421,7 @@ class FileReference implements FileInterface {
                // TODO: Implement this function. This should only rename the
                // FileReference (sys_file_reference) record, not the file itself.
                throw new \BadMethodCallException('Function not implemented FileReference::rename().', 1333754473);
-               return $this->fileRepository->renameUsageRecord($this, $newName);
+               //return $this->fileRepository->renameUsageRecord($this, $newName);
        }
 
        /*****************
@@ -484,4 +484,12 @@ class FileReference implements FileInterface {
                return $this->originalFile;
        }
 
+       /**
+        * Get hashed identifier
+        *
+        * @return string
+        */
+       public function getHashedIdentifier() {
+               return $this->getStorage()->hashFileIdentifier($this->getIdentifier());
+       }
 }
index d2f0845..1ff7f6d 100644 (file)
@@ -141,19 +141,22 @@ class Folder implements FolderInterface {
        }
 
        /**
+        * Get hashed identifier
+        *
+        * @return string
+        */
+       public function getHashedIdentifier() {
+               return $this->storage->hashFileIdentifier($this->identifier);
+       }
+
+       /**
         * Returns a combined identifier of this folder, i.e. the storage UID and
         * the folder identifier separated by a colon ":".
         *
         * @return string Combined storage and folder identifier, e.g. StorageUID:folder/path/
         */
        public function getCombinedIdentifier() {
-               // @todo $this->properties is never defined nor used here
-               if (is_array($this->properties) && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($this->properties['storage'])) {
-                       $combinedIdentifier = $this->properties['storage'] . ':' . $this->getIdentifier();
-               } else {
-                       $combinedIdentifier = $this->getStorage()->getUid() . ':' . $this->getIdentifier();
-               }
-               return $combinedIdentifier;
+               return $this->getStorage()->getUid() . ':' . $this->getIdentifier();
        }
 
        /**
@@ -179,16 +182,13 @@ class Folder implements FolderInterface {
         * @param integer $numberOfItems The number of items to return
         * @param integer $filterMode The filter mode to use for the file list.
         * @param boolean $recursive
-        * @return File[]
+        * @return \TYPO3\CMS\Core\Resource\File[]
         */
        public function getFiles($start = 0, $numberOfItems = 0, $filterMode = self::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, $recursive = FALSE) {
-               $useFilters = TRUE;
-
                // Fallback for compatibility with the old method signature variable $useFilters that was used instead of $filterMode
-               if ($filterMode === TRUE) {
-                       $filterMode = self::FILTER_MODE_USE_STORAGE_FILTERS;
-               } elseif ($filterMode === FALSE) {
+               if ($filterMode === FALSE) {
                        $useFilters = FALSE;
+                       $backedUpFilters = array();
                } else {
                        list($backedUpFilters, $useFilters) = $this->prepareFiltersInStorage($filterMode);
                }
index 9e8c881..9571394 100644 (file)
@@ -53,8 +53,8 @@ class FileIndexRepository implements SingletonInterface {
         * @var array
         */
        protected $fields = array(
-               'uid', 'pid', 'missing', 'type', 'storage', 'identifier', 'extension',
-               'mime_type', 'name', 'sha1', 'size', 'creation_date', 'modification_date',
+               'uid', 'pid', 'missing', 'type', 'storage', 'identifier', 'identifier_hash', 'extension',
+               'mime_type', 'name', 'sha1', 'size', 'creation_date', 'modification_date', 'folder_hash'
        );
 
        /**
@@ -67,6 +67,16 @@ class FileIndexRepository implements SingletonInterface {
        }
 
        /**
+        * Gets the Resource Factory
+        *
+        * @return \TYPO3\CMS\Core\Resource\ResourceFactory
+        */
+       protected function getResourceFactory() {
+               return \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance();
+       }
+
+
+       /**
         * Returns an Instance of the Repository
         *
         * @return FileIndexRepository
@@ -79,7 +89,7 @@ class FileIndexRepository implements SingletonInterface {
         * Retrieves Index record for a given $combinedIdentifier
         *
         * @param string $combinedIdentifier
-        * @return array|bool
+        * @return array|boolean
         */
        public function findOneByCombinedIdentifier($combinedIdentifier) {
                list($storageUid, $identifier) = GeneralUtility::trimExplode(':', $combinedIdentifier, FALSE, 2);
@@ -90,7 +100,7 @@ class FileIndexRepository implements SingletonInterface {
         * Retrieves Index record for a given $fileUid
         *
         * @param int $fileUid
-        * @return array|bool
+        * @return array|boolean
         */
        public function findOneByUid($fileUid) {
                $row = $this->getDatabase()->exec_SELECTgetSingleRow(
@@ -106,15 +116,29 @@ class FileIndexRepository implements SingletonInterface {
         *
         * @param int $storageUid
         * @param string $identifier
-        * @return array|bool
+        * @return array|boolean
         *
         * @internal only for use from FileRepository
         */
        public function findOneByStorageUidAndIdentifier($storageUid, $identifier) {
+               $identifierHash = $this->getResourceFactory()->getStorageObject($storageUid)->hashFileIdentifier($identifier);
+               return $this->findOneByStorageUidAndIdentifierHash($storageUid, $identifierHash);
+       }
+
+       /**
+        * Retrieves Index record for a given $storageUid and $identifier
+        *
+        * @param integer $storageUid
+        * @param string $identifierHash
+        * @return array|boolean
+        *
+        * @internal only for use from FileRepository
+        */
+       public function findOneByStorageUidAndIdentifierHash($storageUid, $identifierHash) {
                $row = $this->getDatabase()->exec_SELECTgetSingleRow(
                        implode(',', $this->fields),
                        $this->table,
-                       sprintf('storage=%u AND identifier=%s', intval($storageUid), $this->getDatabase()->fullQuoteStr($identifier, $this->table))
+                       sprintf('storage=%u AND identifier_hash=%s', intval($storageUid), $this->getDatabase()->fullQuoteStr($identifierHash, $this->table))
                );
                return is_array($row) ? $row : FALSE;
        }
@@ -123,14 +147,14 @@ class FileIndexRepository implements SingletonInterface {
         * Retrieves Index record for a given $fileObject
         *
         * @param \TYPO3\CMS\Core\Resource\FileInterface $fileObject
-        * @return array|bool
+        * @return array|boolean
         *
         * @internal only for use from FileRepository
         */
        public function findOneByFileObject(\TYPO3\CMS\Core\Resource\FileInterface $fileObject) {
                $storageUid = $fileObject->getStorage()->getUid();
-               $identifier = $fileObject->getIdentifier();
-               return $this->findOneByStorageUidAndIdentifier($storageUid, $identifier);
+               $identifierHash = $fileObject->getHashedIdentifier();
+               return $this->findOneByStorageUidAndIdentifierHash($storageUid, $identifierHash);
        }
 
        /**
@@ -176,7 +200,7 @@ class FileIndexRepository implements SingletonInterface {
         * Checks if a file is indexed
         *
         * @param File $file
-        * @return bool
+        * @return boolean
         */
        public function hasIndexRecord(File $file) {
                return $this->getDatabase()->exec_SELECTcountRows('uid', $this->table, $this->getWhereClauseForFile($file)) >= 1;
index e3d5b99..8b61ef9 100644 (file)
@@ -167,7 +167,7 @@ class ProcessedFileRepository extends AbstractRepository {
 
        /**
         * @param FileInterface $file
-        * @return array<ProcessedFile>
+        * @return ProcessedFile[]
         * @throws \InvalidArgumentException
         */
        public function findAllByOriginalFile(FileInterface $file) {
index ea90c35..0ccc2b8 100644 (file)
@@ -53,4 +53,10 @@ interface ResourceInterface {
         */
        public function getStorage();
 
+       /**
+        * Get hashed identifier
+        *
+        * @return string
+        */
+       public function getHashedIdentifier();
 }
index c40e7f3..5257c0c 100644 (file)
@@ -30,7 +30,6 @@ namespace TYPO3\CMS\Core\Resource;
 use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\PathUtility;
-use TYPO3\CMS\Core\Resource\File;
 
 /**
  * A "mount point" inside the TYPO3 file handling.
@@ -399,6 +398,15 @@ class ResourceStorage {
        }
 
        /**
+        * Returns TRUE if the identifiers used by this storage are case-sensitive
+        *
+        * @return boolean
+        */
+       public function usesCaseSensitiveIdentifiers() {
+               return $this->driver->isCaseSensitiveFileSystem();
+       }
+
+       /**
         * Returns TRUE if this storage is browsable by a (backend) user of TYPO3.
         *
         * @return boolean
@@ -997,23 +1005,30 @@ class ResourceStorage {
         *
         * @param FolderInterface $folderToCopy
         * @param FolderInterface $targetParentFolder
+        * @return void
         *
         * @throws Exception
         * @throws Exception\InsufficientFolderWritePermissionsException
         * @throws Exception\IllegalFileExtensionException
         * @throws Exception\InsufficientFileReadPermissionsException
         * @throws Exception\InsufficientUserPermissionsException
-        * @return void
+        * @throws \RuntimeException
         */
        protected function assureFolderCopyPermissions(FolderInterface $folderToCopy, FolderInterface $targetParentFolder) {
                // Check if targetFolder is within this storage, this should never happen
                if ($this->getUid() !== $targetParentFolder->getStorage()->getUid()) {
                        throw new Exception('The operation of the folder cannot be called by this storage "' . $this->getUid() . '"', 1377777624);
                }
+               if (!$folderToCopy instanceof Folder) {
+                       throw new \RuntimeException('The folder "' . $folderToCopy->getIdentifier() . '" to copy is not of type Folder.', 1384209020);
+               }
                // Check if user is allowed to copy and the folder is readable
                if (!$this->checkFolderActionPermission('copy', $folderToCopy)) {
                        throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to copy the folder "' . $folderToCopy->getIdentifier() . '"', 1377777629);
                }
+               if (!$targetParentFolder instanceof Folder) {
+                       throw new \RuntimeException('The target folder "' . $targetParentFolder->getIdentifier() . '" is not of type Folder.', 1384209021);
+               }
                // Check if targetFolder is writable
                if (!$this->checkFolderActionPermission('write', $targetParentFolder)) {
                        throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetParentFolder->getIdentifier() . '"', 1377777635);
@@ -1032,6 +1047,7 @@ class ResourceStorage {
         * @throws Exception\IllegalFileExtensionException
         * @throws Exception\InsufficientFileReadPermissionsException
         * @throws Exception\InsufficientUserPermissionsException
+        * @throws \RuntimeException
         * @return void
         */
        protected function assureFolderMovePermissions(FolderInterface $folderToMove, FolderInterface $targetParentFolder) {
@@ -1039,12 +1055,18 @@ class ResourceStorage {
                if ($this->getUid() !== $targetParentFolder->getStorage()->getUid()) {
                        throw new \InvalidArgumentException('Cannot move a folder into a folder that does not belong to this storage.', 1325777289);
                }
+               if (!$folderToMove instanceof Folder) {
+                       throw new \RuntimeException('The folder "' . $folderToMove->getIdentifier() . '" to move is not of type Folder.', 1384209022);
+               }
                // Check if user is allowed to move and the folder is writable
                // In fact we would need to check if the parent folder of the folder to move is writable also
                // But as of now we cannot extract the parent folder from this folder
                if (!$this->checkFolderActionPermission('move', $folderToMove)) {
                        throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to copy the folder "' . $folderToMove->getIdentifier() . '"', 1377778045);
                }
+               if (!$targetParentFolder instanceof Folder) {
+                       throw new \RuntimeException('The target folder "' . $targetParentFolder->getIdentifier() . '" is not of type Folder.', 1384209023);
+               }
                // Check if targetFolder is writable
                if (!$this->checkFolderActionPermission('write', $targetParentFolder)) {
                        throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetParentFolder->getIdentifier() . '"', 1377778049);
@@ -1122,13 +1144,29 @@ class ResourceStorage {
        }
 
        /**
+        * Hashes a file identifier, taking the case sensitivity of the file system
+        * into account. This helps mitigating problems with case-insensitive
+        * databases.
+        *
+        * @param string|FileInterface $file
+        * @return string
+        */
+       public function hashFileIdentifier($file) {
+               if (is_object($file) && $file instanceof FileInterface) {
+                       /** @var FileInterface $file */
+                       $file = $file->getIdentifier();
+               }
+               return $this->driver->hashIdentifier($file);
+       }
+
+       /**
         * Returns a publicly accessible URL for a file.
         *
         * WARNING: Access to the file may be restricted by further means, e.g.
         * some web-based authentication. You have to take care of this yourself.
         *
         * @param ResourceInterface $resourceObject The file or folder object
-        * @param bool $relativeToCurrentScript Determines whether the URL returned should be relative to the current script, in case it is relative at all (only for the LocalDriver)
+        * @param boolean $relativeToCurrentScript Determines whether the URL returned should be relative to the current script, in case it is relative at all (only for the LocalDriver)
         * @return string
         */
        public function getPublicUrl(ResourceInterface $resourceObject, $relativeToCurrentScript = FALSE) {
@@ -1167,7 +1205,7 @@ class ResourceStorage {
         * Copies a file from the storage for local processing.
         *
         * @param FileInterface $fileObject
-        * @param bool $writable
+        * @param boolean $writable
         * @return string Path to local file (either original or copied to some temporary local location)
         */
        public function getFileForLocalProcessing(FileInterface $fileObject, $writable = TRUE) {
@@ -1251,14 +1289,22 @@ class ResourceStorage {
        }
 
        /**
+        * @param string $fileIdentifier
+        *
+        * @return string
+        */
+       public function getFolderIdentifierFromFileIdentifier($fileIdentifier) {
+               return $this->driver->getFolderIdentifierForFile($fileIdentifier);
+       }
+       /**
         * Returns a list of files in a given path, filtered by some custom filter methods.
         *
         * @see getUnfilteredFileList(), getFileListWithDefaultFilters()
         * @param string $path The path to list
         * @param integer $start The position to start the listing; if not set or 0, start from the beginning
         * @param integer $numberOfItems The number of items to list; if not set, return all items
-        * @param bool $useFilters If FALSE, the list is returned without any filtering; otherwise, the filters defined for this storage are used.
-        * @param bool $loadIndexRecords If set to TRUE, the index records for all files are loaded from the database. This can greatly improve performance of this method, especially with a lot of files.
+        * @param boolean $useFilters If FALSE, the list is returned without any filtering; otherwise, the filters defined for this storage are used.
+        * @param boolean $loadIndexRecords If set to TRUE, the index records for all files are loaded from the database. This can greatly improve performance of this method, especially with a lot of files.
         * @param boolean $recursive
         * @return array Information about the files found.
         */
@@ -1439,6 +1485,7 @@ class ResourceStorage {
         * @param string $conflictMode "overrideExistingFile", "renameNewFile", "cancel
         *
         * @throws Exception\ExistingTargetFileNameException
+        * @throws \RuntimeException
         * @return FileInterface
         */
        public function moveFile($file, $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
@@ -1461,6 +1508,9 @@ class ResourceStorage {
                try {
                        if ($sourceStorage === $this) {
                                $newIdentifier = $this->driver->moveFileWithinStorage($file, $targetFolder, $targetFileName);
+                               if (!$file instanceof AbstractFile) {
+                                       throw new \RuntimeException('The given file is not of type AbstractFile.', 1384209025);
+                               }
                                $this->updateFile($file, $newIdentifier);
                        } else {
                                $tempPath = $file->getForLocalProcessing();
@@ -1624,8 +1674,8 @@ class ResourceStorage {
         * @throws \InvalidArgumentException
         * @return Folder
         */
-       // TODO add tests
        public function moveFolder(Folder $folderToMove, Folder $targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
+               // TODO add tests
                $this->assureFolderMovePermissions($folderToMove, $targetParentFolder);
                $sourceStorage = $folderToMove->getStorage();
                $returnObject = NULL;
index 41361df..402fc5c 100644 (file)
@@ -256,6 +256,8 @@ class IndexerService implements \TYPO3\CMS\Core\SingletonInterface {
                                'modification_date' => $info['mtime'],
                                'size' => $info['size'],
                                'identifier' => $file->getIdentifier(),
+                               'identifier_hash' => $storage->hashFileIdentifier($file->getIdentifier()),
+                               'folder_hash' => $storage->hashFileIdentifier($storage->getFolderIdentifierFromFileIdentifier($file->getIdentifier())),
                                'storage' => $storage->getUid(),
                                'name' => $file->getName(),
                                'sha1' => $storage->hashFile($file, 'sha1'),
index 7908492..76308a4 100644 (file)
@@ -59,12 +59,18 @@ class StorageRepository extends AbstractRepository {
         */
        protected $logger;
 
+       /**
+        * @var \TYPO3\CMS\Core\Database\DatabaseConnection
+        */
+       protected $db;
+
        public function __construct() {
                parent::__construct();
 
                /** @var $logManager \TYPO3\CMS\Core\Log\LogManager */
-               $logManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Core\Log\LogManager');
+               $logManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Log\\LogManager');
                $this->logger = $logManager->getLogger(__CLASS__);
+               $this->db = $GLOBALS['TYPO3_DB'];
        }
 
        /**
@@ -75,15 +81,15 @@ class StorageRepository extends AbstractRepository {
         */
        public function findByStorageType($storageType) {
                /** @var $driverRegistry Driver\DriverRegistry */
-               $driverRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Core\Resource\Driver\DriverRegistry');
+               $driverRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Driver\\DriverRegistry');
                $storageObjects = array();
-               $whereClause = $this->typeField . ' = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($storageType, $this->table);
-               $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
+               $whereClause = $this->typeField . ' = ' . $this->db->fullQuoteStr($storageType, $this->table);
+               $res = $this->db->exec_SELECTquery(
                        '*',
                        $this->table,
                        $whereClause . $this->getWhereClauseForEnabledFields()
                );
-               while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
+               while ($row = $this->db->sql_fetch_assoc($res)) {
                        if ($driverRegistry->driverExists($row['driver'])) {
                                $storageObjects[] = $this->createDomainObject($row);
                        } else {
@@ -93,7 +99,7 @@ class StorageRepository extends AbstractRepository {
                                );
                        }
                }
-               $GLOBALS['TYPO3_DB']->sql_free_result($res);
+               $this->db->sql_free_result($res);
                return $storageObjects;
        }
 
@@ -106,7 +112,7 @@ class StorageRepository extends AbstractRepository {
        public function findAll() {
                        // check if we have never created a storage before (no records, regardless of the enableFields),
                        // only fetch one record for that (is enough). If no record is found, create the fileadmin/ storage
-               $storageObjectsCount = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', $this->table, '1=1');
+               $storageObjectsCount = $this->db->exec_SELECTcountRows('uid', $this->table, '1=1');
                if ($storageObjectsCount === 0) {
                        $this->createLocalStorage(
                                'fileadmin/ (auto-created)',
@@ -119,18 +125,18 @@ class StorageRepository extends AbstractRepository {
                $storageObjects = array();
                $whereClause = NULL;
                if ($this->type != '') {
-                       $whereClause = $this->typeField . ' = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->type, $this->table);
+                       $whereClause = $this->typeField . ' = ' . $this->db->fullQuoteStr($this->type, $this->table);
                }
-               $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
+               $res = $this->db->exec_SELECTquery(
                        '*',
                        $this->table,
                        ($whereClause ? $whereClause : '1=1') . $this->getWhereClauseForEnabledFields()
                );
 
                /** @var $driverRegistry Driver\DriverRegistry */
-               $driverRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Core\Resource\Driver\DriverRegistry');
+               $driverRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Driver\\DriverRegistry');
 
-               while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
+               while ($row = $this->db->sql_fetch_assoc($res)) {
                        if ($driverRegistry->driverExists($row['driver'])) {
                                $storageObjects[] = $this->createDomainObject($row);
                        } else {
@@ -140,7 +146,7 @@ class StorageRepository extends AbstractRepository {
                                );
                        }
                }
-               $GLOBALS['TYPO3_DB']->sql_free_result($res);
+               $this->db->sql_free_result($res);
                return $storageObjects;
        }
 
@@ -154,14 +160,15 @@ class StorageRepository extends AbstractRepository {
         * @return integer uid of the inserted record
         */
        public function createLocalStorage($name, $basePath, $pathType, $description = '') {
-
-                       // create the FlexForm for the driver configuration
+               $caseSensitive = $this->testCaseSensitivity($pathType === 'relative' ? PATH_site . $basePath : $basePath);
+               // create the FlexForm for the driver configuration
                $flexFormData = array(
                        'data' => array(
                                'sDEF' => array(
                                        'lDEF' => array(
                                                'basePath' => array('vDEF' => rtrim($basePath, '/') . '/'),
-                                               'pathType' => array('vDEF' => $pathType)
+                                               'pathType' => array('vDEF' => $pathType),
+                                               'caseSensitive' => array('vDEF' => $caseSensitive)
                                        )
                                )
                        )
@@ -185,8 +192,8 @@ class StorageRepository extends AbstractRepository {
                        'is_public' => 1,
                        'is_writable' => 1
                );
-               $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_file_storage', $field_values);
-               return (int) $GLOBALS['TYPO3_DB']->sql_insert_id();
+               $this->db->exec_INSERTquery('sys_file_storage', $field_values);
+               return (int)$this->db->sql_insert_id();
        }
 
        /**
@@ -199,4 +206,33 @@ class StorageRepository extends AbstractRepository {
                return $this->factory->getStorageObject($databaseRow['uid'], $databaseRow);
        }
 
+       /**
+        * Test if the local filesystem is case sensitive
+        *
+        * @param string $absolutePath
+        * @return boolean
+        */
+       protected function testCaseSensitivity($absolutePath) {
+               $caseSensitive = TRUE;
+               $path = rtrim($absolutePath, '/') . '/aAbB';
+               $testFileExists = @file_exists($path);
+
+               // create test file
+               if (!$testFileExists) {
+                       touch($path);
+               }
+
+               // do the actual sensitivity check
+               if (@file_exists(strtoupper($path)) && @file_exists(strtolower($path))) {
+                       $caseSensitive = FALSE;
+               }
+
+               // clean filesystem
+               if (!$testFileExists) {
+                       unlink($path);
+               }
+
+               return $caseSensitive;
+       }
+
 }
index 04cfafd..6f9775a 100644 (file)
                                        </config>
                                </TCEforms>
                        </pathType>
+                       <caseSensitive>
+                               <TCEforms>
+                                       <label>LLL:EXT:lang/locallang_mod_file_list.xlf:localDriverFlexform_caseSensitive</label>
+                                       <config>
+                                               <type>check</type>
+                                               <default>1</default>
+                                       </config>
+                               </TCEforms>
+                       </caseSensitive>
                </el>
        </ROOT>
 </T3DataStructure>
\ No newline at end of file
index 020a709..64ca9d1 100644 (file)
@@ -61,6 +61,8 @@ abstract class BaseTestCase extends \PHPUnit_Framework_TestCase {
         * @return \PHPUnit_Framework_MockObject_MockObject|AccessibleObjectInterface
         *         a mock of $originalClassName with access methods added
         *
+        * @throws \InvalidArgumentException
+        *
         * @see \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase::getAccessibleMock
         */
        protected function getAccessibleMock(
index 33f1c24..21c854c 100644 (file)
@@ -51,7 +51,7 @@ class DriverRegistryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         * @test
         */
        public function registeredDriverClassesCanBeRetrieved() {
-               $className = $this->getMockClass('TYPO3\\CMS\\Core\\Resource\\Driver\\AbstractDriver');
+               $className = get_class($this->getMockForAbstractClass('TYPO3\\CMS\\Core\\Resource\\Driver\\AbstractDriver'));
                $this->fixture->registerDriverClass($className, 'foobar');
                $returnedClassName = $this->fixture->getDriverClass('foobar');
                $this->assertEquals($className, $returnedClassName);
@@ -70,7 +70,7 @@ class DriverRegistryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         */
        public function registerDriverClassThrowsExceptionIfShortnameIsAlreadyTakenByAnotherDriverClass() {
                $this->setExpectedException('InvalidArgumentException', '', 1314979451);
-               $className = $this->getMockClass('TYPO3\\CMS\\Core\\Resource\\Driver\\AbstractDriver');
+               $className = get_class($this->getMockForAbstractClass('TYPO3\\CMS\\Core\\Resource\\Driver\\AbstractDriver'));
                $className2 = '\stdClass';
                $this->fixture->registerDriverClass($className, 'foobar');
                $this->fixture->registerDriverClass($className2, 'foobar');
@@ -88,7 +88,7 @@ class DriverRegistryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         * @test
         */
        public function getDriverClassAcceptsClassNameIfClassIsRegistered() {
-               $className = $this->getMockClass('TYPO3\\CMS\\Core\\Resource\\Driver\\AbstractDriver');
+               $className = get_class($this->getMockForAbstractClass('TYPO3\\CMS\\Core\\Resource\\Driver\\AbstractDriver'));
                $this->fixture->registerDriverClass($className, 'foobar');
                $this->assertEquals($className, $this->fixture->getDriverClass($className));
        }
@@ -97,7 +97,7 @@ class DriverRegistryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         * @test
         */
        public function driverRegistryIsInitializedWithPreconfiguredDrivers() {
-               $className = $this->getMockClass('TYPO3\\CMS\\Core\\Resource\\Driver\\AbstractDriver');
+               $className = get_class($this->getMockForAbstractClass('TYPO3\\CMS\\Core\\Resource\\Driver\\AbstractDriver'));
                $shortName = uniqid();
                $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['registeredDrivers'] = array(
                        $shortName => array(
@@ -112,7 +112,7 @@ class DriverRegistryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         * @test
         */
        public function driverExistsReturnsTrueForAllExistingDrivers() {
-               $className = $this->getMockClass('TYPO3\\CMS\\Core\\Resource\\Driver\\AbstractDriver');
+               $className = get_class($this->getMockForAbstractClass('TYPO3\\CMS\\Core\\Resource\\Driver\\AbstractDriver'));
                $shortName = uniqid();
                $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['registeredDrivers'] = array(
                        $shortName => array(
index 1c00265..fae89ec 100644 (file)
@@ -86,7 +86,7 @@ class FactoryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         * @test
         */
        public function getDriverObjectAcceptsDriverClassName() {
-               $mockedDriver = $this->getMock('TYPO3\\CMS\\Core\\Resource\\Driver\\AbstractDriver', array(), array(), '', FALSE);
+               $mockedDriver = $this->getMockForAbstractClass('TYPO3\\CMS\\Core\\Resource\\Driver\\AbstractDriver');
                $driverFixtureClass = get_class($mockedDriver);
                \TYPO3\CMS\Core\Utility\GeneralUtility::addInstance($driverFixtureClass, $mockedDriver);
                $mockedMount = $this->getMock('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', array(), array(), '', FALSE);
@@ -125,13 +125,11 @@ class FactoryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         * @test
         */
        public function retrieveFileOrFolderObjectReturnsFileIfPathIsGiven() {
+               $this->fixture = $this->getAccessibleMock('TYPO3\\CMS\\Core\\Resource\\ResourceFactory', array('getFileObjectFromCombinedIdentifier'), array(), '', FALSE);
                $filename = 'typo3temp/4711.txt';
-               $storage = $this->getMock('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', array('getFileInfoByIdentifier', 'getFolder'), array(), '', FALSE);
-               $storage->expects($this->once())
-                       ->method('getFileInfoByIdentifier')
-                       ->with($filename)
-                       ->will($this->returnValue(array('uid' => 4811)));
-               $this->fixture->_set('storageInstances', array(0 => $storage));
+               $this->fixture->expects($this->once())
+                       ->method('getFileObjectFromCombinedIdentifier')
+                       ->with($filename);
                // Create and prepare test file
                \TYPO3\CMS\Core\Utility\GeneralUtility::writeFileToTypo3tempDir(PATH_site . $filename, '42');
                $this->filesCreated[] = PATH_site . $filename;
index 9305118..afb6024 100644 (file)
@@ -575,7 +575,7 @@ class ResourceStorageTest extends \TYPO3\CMS\Core\Tests\Unit\Resource\BaseTestCa
                /** @var \TYPO3\CMS\Core\Resource\Folder|\PHPUnit_Framework_MockObject_MockObject $folderMock */
                $folderMock = $this->getMock('TYPO3\\CMS\\Core\\Resource\\Folder', array(), array(), '', FALSE);
                /** @var \TYPO3\CMS\Core\Resource\Driver\AbstractDriver|\PHPUnit_Framework_MockObject_MockObject $driverMock */
-               $driverMock = $this->getMock('TYPO3\\CMS\\Core\\Resource\\Driver\\AbstractDriver', array(), array(), '', FALSE);
+               $driverMock = $this->getMockForAbstractClass('TYPO3\\CMS\\Core\\Resource\\Driver\\AbstractDriver');
                $driverMock->expects($this->once())->method('isFolderEmpty')->will($this->returnValue(FALSE));
                /** @var \TYPO3\CMS\Core\Resource\ResourceStorage|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface $fixture */
                $fixture = $this->getAccessibleMock('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', array('checkFolderActionPermission'), array(), '', FALSE);
index 035ae94..57e4f39 100644 (file)
@@ -292,7 +292,9 @@ CREATE TABLE sys_file (
        metadata int(11) DEFAULT '0' NOT NULL,
 
        # file info data
-       identifier varchar(200) DEFAULT '' NOT NULL,
+       identifier text,
+       identifier_hash varchar(40) DEFAULT '' NOT NULL,
+       folder_hash varchar(40) DEFAULT '' NOT NULL,
        extension varchar(255) DEFAULT '' NOT NULL,
        mime_type varchar(255) DEFAULT '' NOT NULL,
        name tinytext,
@@ -302,7 +304,7 @@ CREATE TABLE sys_file (
        modification_date int(11) DEFAULT '0' NOT NULL,
 
        PRIMARY KEY (uid),
-       KEY sel01 (storage,identifier(20)),
+       KEY sel01 (storage,identifier_hash),
        KEY sha1 (sha1(40))
 );
 
diff --git a/typo3/sysext/install/Classes/Updates/FileIdentifierHashUpdate.php b/typo3/sysext/install/Classes/Updates/FileIdentifierHashUpdate.php
new file mode 100644 (file)
index 0000000..36f5c64
--- /dev/null
@@ -0,0 +1,249 @@
+<?php
+namespace TYPO3\CMS\Install\Updates;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2013 Andreas Wolf <andreas.wolf@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use TYPO3\CMS\Core\Resource\ResourceStorage;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
+
+/**
+ * Class FileIdentifierHashUpdate adds IdentifierHashes
+ */
+class FileIdentifierHashUpdate extends AbstractUpdate {
+
+       /**
+        * @var string
+        */
+       protected $title = 'Add the file identifier hash to existing sys_file records and detects the settings for local storages';
+
+       /**
+        * @var \TYPO3\CMS\Core\Database\DatabaseConnection
+        */
+       protected $db;
+
+       /**
+        * @var array
+        */
+       protected $sqlQueries = array();
+
+       /**
+        * @var array<\TYPO3\CMS\Core\Resource\ResourceStorage>
+        */
+       protected $storages;
+
+       /**
+        * @var \TYPO3\CMS\Core\Resource\StorageRepository
+        */
+       protected $storageRepository;
+
+       /**
+        * Creates this object
+        */
+       public function __construct() {
+               $this->db = $GLOBALS['TYPO3_DB'];
+       }
+
+       /**
+        * Initialize the storage repository.
+        */
+       public function init() {
+               $this->storageRepository = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\StorageRepository');
+               $this->storages = $this->storageRepository->findAll();
+       }
+
+       /**
+        * Checks if an update is needed.
+        *
+        * @param string &$description The description for the update
+        * @return boolean TRUE if an update is needed, FALSE otherwise
+        */
+       public function checkForUpdate(&$description) {
+               $description = 'Add file identifier hash where it is missing.';
+               $unhashedFileCount = $this->db->exec_SELECTcountRows(
+                       'uid',
+                       'sys_file',
+                       'identifier_hash = "" OR folder_hash = ""'
+               );
+
+               $unmigratedStorageCount = $this->db->exec_SELECTcountRows(
+                       'uid',
+                       'sys_file_storage',
+                       'driver = "Local" AND configuration NOT LIKE "%caseSensitive%"'
+               );
+
+               return ($unhashedFileCount > 0 || $unmigratedStorageCount > 0) && !$this->isWizardDone();
+       }
+
+       /**
+        * Performs the database update.
+        *
+        * @param array &$dbQueries Queries done in this update
+        * @param mixed &$customMessages Custom messages
+        * @return boolean TRUE on success, FALSE on error
+        */
+       public function performUpdate(array &$dbQueries, &$customMessages) {
+               $this->init();
+               foreach ($this->storages as $storage) {
+                       $dbQueries = array_merge($dbQueries, $this->updateIdentifierHashesForStorage($storage));
+               }
+
+               $dbQueries = array_merge($dbQueries, $this->migrateStorages());
+
+               $this->markWizardAsDone();
+               return TRUE;
+       }
+
+       /**
+        * @return array
+        */
+       protected function migrateStorages() {
+               $dbQueries = array();
+               $unmigratedStorages = $this->db->exec_SELECTgetRows(
+                       'uid, configuration',
+                       'sys_file_storage',
+                       'driver = "Local" AND configuration NOT LIKE "%caseSensitive%"'
+               );
+
+               /** @var $flexObj \TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools */
+               $flexObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Configuration\\FlexForm\\FlexFormTools');
+
+
+               foreach ($unmigratedStorages as $storage) {
+                       $flexFormXml = $storage['configuration'];
+                       $configurationArray = \TYPO3\CMS\Core\Utility\GeneralUtility::xml2array($flexFormXml);
+
+                       $caseSensitive = $this->testCaseSensitivity(
+                               $configurationArray['data']['sDEF']['lDEF']['pathType']['vDEF'] == 'relative' ?
+                                       PATH_site . $configurationArray['data']['sDEF']['lDEF']['basePath']['vDEF'] :
+                                       $configurationArray['data']['sDEF']['lDEF']['basePath']['vDEF']
+                       );
+                       $configurationArray['data']['sDEF']['lDEF']['caseSensitive'] = array('vDEF' => $caseSensitive);
+
+                       $configuration = $flexObj->flexArray2Xml($configurationArray);
+                       $dbQueries[] = $query = $this->db->UPDATEquery(
+                               'sys_file_storage',
+                               'uid=' . $storage['uid'],
+                               array(
+                                       'configuration' => $configuration
+                               )
+                       );
+                       $this->db->sql_query($query);
+               }
+               return $dbQueries;
+       }
+
+       /**
+        * Creates file identifier hashes for a single storage.
+        *
+        * @param ResourceStorage $storage The storage to update
+        * @return array The executed database queries
+        */
+       protected function updateIdentifierHashesForStorage(ResourceStorage $storage) {
+               $queries = array();
+
+               if (!ExtensionManagementUtility::isLoaded('dbal')) {
+                       // if DBAL is not loaded, we're using MySQL and can thus use their
+                       // SHA1() function
+                       if ($storage->usesCaseSensitiveIdentifiers()) {
+                               $updateCall = 'SHA1(identifier)';
+                       } else {
+                               $updateCall = 'SHA1(LOWER(identifier))';
+                       }
+                       $queries[] = $query = sprintf('UPDATE sys_file SET identifier_hash = %s WHERE storage=%d', $updateCall, $storage);
+                       $this->db->sql_query($query);
+
+                       // folder hashes cannot be done with one call: so do it manually
+                       $files = $this->db->exec_SELECTgetRows('uid, storage, identifier', 'sys_file',
+                               sprintf('storage=%d AND folder_hash=""', $storage->getUid())
+                       );
+
+                       foreach ($files as $file) {
+                               $folderHash = $storage->hashFileIdentifier($storage->getFolderIdentifierFromFileIdentifier($file['identifier']));
+
+                               $queries[] = $query = $this->db->UPDATEquery(
+                                       'sys_file',
+                                       'uid=' . $file['uid'],
+                                       array(
+                                               'folder_hash' => $folderHash
+                                       )
+                               );
+
+                               $this->db->sql_query($query);
+                       }
+               } else {
+                       // manually hash the identifiers when using DBAL
+                       $files = $this->db->exec_SELECTgetRows('uid, storage, identifier', 'sys_file',
+                               sprintf('storage=%d AND identifier_hash=""', $storage->getUid())
+                       );
+
+                       foreach ($files as $file) {
+                               $hash = $storage->hashFileIdentifier($file['identifier']);
+                               $folderHash = $storage->hashFileIdentifier($storage->getFolderIdentifierFromFileIdentifier($file['identifier']));
+
+                               $queries[] = $query = $this->db->UPDATEquery(
+                                       'sys_file',
+                                       'uid=' . $file['uid'],
+                                       array(
+                                               'identifier_hash' => $hash,
+                                               'folder_hash' => $folderHash
+                                       )
+                               );
+
+                               $this->db->sql_query($query);
+                       }
+               }
+
+               return $queries;
+       }
+
+
+       /**
+        * Test if the local filesystem is case sensitive
+        *
+        * @param $absolutePath
+        * @return boolean
+        */
+       protected function testCaseSensitivity($absolutePath) {
+               $caseSensitive = TRUE;
+               $path = rtrim($absolutePath, '/') . '/aAbB';
+               $testFileExists = file_exists($path);
+
+               // create test file
+               if (!$testFileExists) {
+                       touch($path);
+               }
+
+               // do the actual sensitivity check
+               if (file_exists(strtoupper($path)) && file_exists(strtolower($path))) {
+                       $caseSensitive = FALSE;
+               }
+
+               // clean filesystem
+               if (!$testFileExists) {
+                       unlink($path);
+               }
+
+               return $caseSensitive;
+       }
+}
index 7a851ac..06007b4 100644 (file)
@@ -46,6 +46,7 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['sysext_file_
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['referenceIntegrity'] = 'TYPO3\\CMS\\Install\\Updates\\ReferenceIntegrityUpdateWizard';
 
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['sysext_file_filemounts'] = 'TYPO3\\CMS\\Install\\Updates\\FilemountUpdateWizard';
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['fal_identifierhash'] = 'TYPO3\\CMS\\Install\\Updates\\FileIdentifierHashUpdate';
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['sysext_file_rtemagicimages'] = 'TYPO3\\CMS\\Install\\Updates\\RteMagicImagesUpdateWizard';
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['sysext_file_rtefilelinks'] = 'TYPO3\\CMS\\Install\\Updates\\RteFileLinksUpdateWizard';
 
index 17503dc..6718ced 100644 (file)
@@ -96,6 +96,9 @@
                        <trans-unit id="localDriverFlexform_pathType_absolute" xml:space="preserve">
                                <source>absolute</source>
                        </trans-unit>
+                       <trans-unit id="localDriverFlexform_caseSensitive" xml:space="preserve">
+                               <source>Uses case sensitive identifiers</source>
+                       </trans-unit>
                        <trans-unit id="storageNotBrowsableTitle" xml:space="preserve">
                                <source>Access denied.</source>
                        </trans-unit>