[BUGFIX] Allow processed folders in different storage 84/37584/3
authorHelmut Hummel <helmut.hummel@typo3.org>
Mon, 22 Sep 2014 13:32:52 +0000 (15:32 +0200)
committerNicole Cordes <typo3@cordes.co>
Fri, 6 Mar 2015 09:51:20 +0000 (10:51 +0100)
The processingfolder of a storage can now be a combined identifier.
This makes it possible to have the processed files outside of the
storage in case of a read-only storage for instance.

Releases: master, 6.2
Resolves: #61463
Change-Id: I4f0e187db2aede33be40f62df3bb9f63e9706d46
Reviewed-on: http://review.typo3.org/37584
Reviewed-by: Nicole Cordes <typo3@cordes.co>
Tested-by: Nicole Cordes <typo3@cordes.co>
12 files changed:
typo3/sysext/context_help/locallang_csh_sysfilestorage.xlf
typo3/sysext/core/Classes/Resource/Index/Indexer.php
typo3/sysext/core/Classes/Resource/ProcessedFile.php
typo3/sysext/core/Classes/Resource/ResourceStorage.php
typo3/sysext/core/Classes/Resource/Service/FileProcessingService.php
typo3/sysext/core/Classes/Resource/StorageRepository.php
typo3/sysext/core/Configuration/TCA/sys_file_storage.php
typo3/sysext/core/Documentation/Changelog/master/Feature-61529-AddMultipleParameterToCheckboxViewHelper.rst [deleted file]
typo3/sysext/core/Tests/Unit/Resource/ProcessedFileTest.php
typo3/sysext/core/Tests/Unit/Resource/ResourceStorageTest.php
typo3/sysext/filelist/Classes/Controller/FileListController.php
typo3/sysext/lang/locallang_tca.xlf

index b36ba40..ec866e4 100644 (file)
@@ -34,7 +34,7 @@
                                <source>The driver is the technical background of a storage, and thus also defines whether the location of the storage is local (= on the same machine as this TYPO3 installation) or remote like a WebDAV server connection. Search for extensions in the TYPO3 Extension Repository that extend this driver list.</source>
                        </trans-unit>
                        <trans-unit id="processingfolder.description" xml:space="preserve">
-                               <source>Defines the directory on this storage to be used to save temporary files like resized images.</source>
+                               <source>Defines the directory on this storage to be used to save temporary files like resized images. When on a different storage make sure that the folder exists and is writable.</source>
                        </trans-unit>
                        <trans-unit id="is_browsable.description" xml:space="preserve">
                                <source>If set, backend users can browse through this storage. Otherwise it will show up as a unselectable item in the list.</source>
index 2898c66..49f22a2 100644 (file)
@@ -143,7 +143,7 @@ class Indexer {
        protected function detectChangedFilesInStorage(array $fileIdentifierArray) {
                foreach ($fileIdentifierArray as $fileIdentifier) {
                        // skip processed files
-                       if (strpos($fileIdentifier, $this->storage->getProcessingFolder()->getIdentifier()) === 0) {
+                       if ($this->storage->isWithinProcessingFolder($fileIdentifier)) {
                                continue;
                        }
                        // Get the modification time for file-identifier from the storage
index 0ec1f68..38d342a 100644 (file)
@@ -113,7 +113,7 @@ class ProcessedFile extends AbstractFile {
         */
        public function __construct(File $originalFile, $taskType, array $processingConfiguration, array $databaseRow = NULL) {
                $this->originalFile = $originalFile;
-               $this->storage = $originalFile->getStorage();
+               $this->storage = $originalFile->getStorage()->getProcessingFolder()->getStorage();
                $this->taskType = $taskType;
                $this->processingConfiguration = $processingConfiguration;
                if (is_array($databaseRow)) {
@@ -177,8 +177,8 @@ class ProcessedFile extends AbstractFile {
                if ($this->identifier === NULL) {
                        throw new \RuntimeException('Cannot update original file!', 1350582054);
                }
-               // TODO this should be more generic (in fact it only works for local file paths)
-               $addedFile = $this->storage->updateProcessedFile($filePath, $this);
+               $processingFolder = $this->originalFile->getStorage()->getProcessingFolder();
+               $addedFile = $this->storage->updateProcessedFile($filePath, $this, $processingFolder);
 
                // Update some related properties
                $this->identifier = $addedFile->getIdentifier();
@@ -454,7 +454,7 @@ class ProcessedFile extends AbstractFile {
                }
 
                // hash does not match
-               if (array_key_exists('checksum', $this->properties) && $this->calculateChecksum() !== $this->properties['checksum'])  {
+               if (array_key_exists('checksum', $this->properties) && $this->calculateChecksum() !== $this->properties['checksum']) {
                        $fileMustBeRecreated = TRUE;
                }
 
index 87e885a..d4c04bc 100644 (file)
@@ -14,7 +14,6 @@ namespace TYPO3\CMS\Core\Resource;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderWritePermissionsException;
 use TYPO3\CMS\Core\Resource\Exception\InvalidTargetFolderException;
 use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -1089,17 +1088,22 @@ class ResourceStorage implements ResourceStorageInterface {
        /**
         * Updates a processed file with a new file from the local filesystem.
         *
-        * @param $localFilePath
+        * @param string $localFilePath
         * @param ProcessedFile $processedFile
+        * @param Folder $processingFolder
         * @return FileInterface
         * @throws \InvalidArgumentException
         * @internal use only
         */
-       public function updateProcessedFile($localFilePath, ProcessedFile $processedFile) {
+       public function updateProcessedFile($localFilePath, ProcessedFile $processedFile, Folder $processingFolder = NULL) {
                if (!file_exists($localFilePath)) {
                        throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552746);
                }
-               $fileIdentifier = $this->driver->addFile($localFilePath, $this->getProcessingFolder()->getIdentifier(), $processedFile->getName());
+               if ($processingFolder === NULL) {
+                       $processingFolder = $this->getProcessingFolder();
+               }
+               $fileIdentifier = $this->driver->addFile($localFilePath, $processingFolder->getIdentifier(), $processedFile->getName());
+
                // @todo check if we have to update the processed file other then the identifier
                $processedFile->setIdentifier($fileIdentifier);
                return $processedFile;
@@ -1231,7 +1235,7 @@ class ResourceStorage implements ResourceStorageInterface {
         * @return FileInterface
         */
        public function getFile($identifier) {
-               $file =  $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
+               $file = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
                if (!$this->driver->fileExists($identifier)) {
                        $file->setMissing(TRUE);
                }
@@ -1396,23 +1400,59 @@ class ResourceStorage implements ResourceStorageInterface {
                return $this->driver->getFoldersInFolder($folderIdentifier, 0, 0, $recursive, $filters);
        }
 
-
        /**
-        * Returns TRUE if the specified file exists.
+        * Returns TRUE if the specified file exists
         *
         * @param string $identifier
         * @return bool
         */
        public function hasFile($identifier) {
                // Allow if identifier is in processing folder
-               if (!$this->driver->isWithin($this->getProcessingFolder()->getIdentifier(), $identifier)) {
+               if (!$this->isWithinProcessingFolder($identifier)) {
                        $this->assureFolderReadPermission();
                }
                return $this->driver->fileExists($identifier);
        }
 
        /**
-        * Checks if the queried file in the given folder exists.
+        * Get all processing folders that live in this storage
+        *
+        * @return Folder[]
+        */
+       public function getProcessingFolders() {
+               $processingFolders = array();
+
+               /** @var $storageRepository StorageRepository */
+               $storageRepository = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\StorageRepository');
+               $allStorages = $storageRepository->findAll();
+               foreach ($allStorages as $storage) {
+                       if ($storage->getProcessingFolder()->getStorage() === $this) {
+                               $processingFolders[] = $storage->getProcessingFolder();
+                       }
+               }
+               return $processingFolders;
+       }
+
+       /**
+        * Returns TRUE if folder that is in current storage  is set as
+        * processing folder for one of the existing storages
+        *
+        * @param Folder $folder
+        * @return bool
+        */
+       public function isProcessingFolder(Folder $folder) {
+               $isProcessingFolder = FALSE;
+               foreach ($this->getProcessingFolders() as $processingFolder) {
+                       if ($folder->getCombinedIdentifier() === $processingFolder->getCombinedIdentifier()) {
+                               $isProcessingFolder = TRUE;
+                               break;
+                       }
+               }
+               return $isProcessingFolder;
+       }
+
+       /**
+        * Checks if the queried file in the given folder exists
         *
         * @param string $fileName
         * @param Folder $folder
@@ -1740,10 +1780,12 @@ class ResourceStorage implements ResourceStorageInterface {
                        foreach ($folder->getSubfolders() as $subfolder) {
                                $folderQueue[] = $subfolder;
                        }
-                       foreach ($folder->getFiles() as $file) { /** @var FileInterface $file */
+                       foreach ($folder->getFiles() as $file) {
+                               /** @var FileInterface $file */
                                $files[$file->getIdentifier()] = $file;
                        }
                }
+
                return $files;
        }
 
@@ -1754,7 +1796,7 @@ class ResourceStorage implements ResourceStorageInterface {
         * @param Folder $folderToMove The folder to move.
         * @param Folder $targetParentFolder The target parent folder
         * @param string $newFolderName
-        * @param string $conflictMode  How to handle conflicts; one of "overrideExistingFile", "renameNewFolder", "cancel
+        * @param string $conflictMode How to handle conflicts; one of "overrideExistingFile", "renameNewFolder", "cancel
         *
         * @throws \Exception|\TYPO3\CMS\Core\Exception
         * @throws \InvalidArgumentException
@@ -1818,7 +1860,7 @@ class ResourceStorage implements ResourceStorageInterface {
         * @param FolderInterface $folderToCopy The folder to copy
         * @param FolderInterface $targetParentFolder The target folder
         * @param string $newFolderName
-        * @param string $conflictMode  "overrideExistingFolder", "renameNewFolder", "cancel
+        * @param string $conflictMode "overrideExistingFolder", "renameNewFolder", "cancel
         * @return Folder The new (copied) folder object
         * @throws InvalidTargetFolderException
         */
@@ -1965,9 +2007,12 @@ class ResourceStorage implements ResourceStorageInterface {
                $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
                $folderIdentifiers = $this->driver->getFoldersInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters);
 
-               $processingIdentifier = $this->getProcessingFolder()->getIdentifier();
-               if (isset($folderIdentifiers[$processingIdentifier])) {
-                       unset($folderIdentifiers[$processingIdentifier]);
+               // Exclude processing folders
+               foreach ($this->getProcessingFolders() as $processingFolder) {
+                       $processingIdentifier = $processingFolder->getIdentifier();
+                       if (isset($folderIdentifiers[$processingIdentifier])) {
+                               unset($folderIdentifiers[$processingIdentifier]);
+                       }
                }
                $folders = array();
                foreach ($folderIdentifiers as $folderIdentifier) {
@@ -2101,11 +2146,20 @@ class ResourceStorage implements ResourceStorageInterface {
        }
 
        /**
+        * Returns TRUE if the specified file is in a folder that is set a processing for a storage
+        *
         * @param string $identifier
         * @return bool
         */
        public function isWithinProcessingFolder($identifier) {
-               return $this->driver->isWithin($this->getProcessingFolder()->getIdentifier(), $identifier);
+               $inProcessingFolder = FALSE;
+               foreach ($this->getProcessingFolders() as $processingFolder) {
+                       if ($this->driver->isWithin($processingFolder->getIdentifier(), $identifier)) {
+                               $inProcessingFolder = TRUE;
+                               break;
+                       }
+               }
+               return $inProcessingFolder;
        }
 
        /**
@@ -2538,8 +2592,7 @@ class ResourceStorage implements ResourceStorageInterface {
                                $folderRole = FolderInterface::ROLE_USER_MOUNT;
                        }
                }
-
-               if ($identifier === $this->getProcessingFolder()->getIdentifier()) {
+               if ($folder instanceof Folder && $this->isProcessingFolder($folder)) {
                        $folderRole = FolderInterface::ROLE_PROCESSING;
                }
 
@@ -2559,13 +2612,17 @@ class ResourceStorage implements ResourceStorageInterface {
                                $processingFolder = $this->storageRecord['processingfolder'];
                        }
                        try {
-                               if ($this->driver->folderExists($processingFolder) === FALSE) {
-                                       $this->processingFolder = $this->createFolder($processingFolder);
+                               if (strpos($processingFolder, ':') !== FALSE) {
+                                       $this->processingFolder = ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($processingFolder);
                                } else {
-                                       $data = $this->driver->getFolderInfoByIdentifier($processingFolder);
-                                       $this->processingFolder = ResourceFactory::getInstance()->createFolderObject($this, $data['identifier'], $data['name']);
+                                       if ($this->driver->folderExists($processingFolder) === FALSE) {
+                                               $this->processingFolder = $this->createFolder($processingFolder);
+                                       } else {
+                                               $data = $this->driver->getFolderInfoByIdentifier($processingFolder);
+                                               $this->processingFolder = ResourceFactory::getInstance()->createFolderObject($this, $data['identifier'], $data['name']);
+                                       }
                                }
-                       } catch(InsufficientFolderWritePermissionsException $e) {
+                       } catch(Exception\InsufficientFolderWritePermissionsException $e) {
                                $this->processingFolder = GeneralUtility::makeInstance(
                                        'TYPO3\\CMS\\Core\\Resource\\InaccessibleFolder', $this, $processingFolder, $processingFolder
                                );
index dc9f41a..3c4a7be 100644 (file)
@@ -101,14 +101,8 @@ class FileProcessingService {
         *
         * @param Resource\ProcessedFile $processedFile
         * @param Resource\ResourceStorage $targetStorage The storage to put the processed file into
-        *
-        * @throws \RuntimeException
         */
        protected function process(Resource\ProcessedFile $processedFile, Resource\ResourceStorage $targetStorage) {
-               $targetFolder = $targetStorage->getProcessingFolder();
-               if (!is_object($targetFolder)) {
-                       throw new \RuntimeException('Could not get processing folder for storage ' . $this->storage->getName(), 1350514301);
-               }
 
                // We only have to trigger the file processing if the file either is new, does not exist or the
                // original file has changed since the last processing run (the last case has to trigger a reprocessing
index 1ace30a..387af15 100644 (file)
@@ -22,7 +22,7 @@ namespace TYPO3\CMS\Core\Resource;
 class StorageRepository extends AbstractRepository {
 
        /**
-        * @var null|array‚
+        * @var NULL|array‚
         */
        protected static $storageRowCache = NULL;
 
@@ -68,12 +68,12 @@ class StorageRepository extends AbstractRepository {
        /**
         * @param integer $uid
         *
-        * @return null|ResourceStorage
+        * @return NULL|ResourceStorage
         */
        public function findByUid($uid) {
                $this->initializeLocalCache();
                if (isset(self::$storageRowCache[$uid])) {
-                       return  $this->factory->getStorageObject($uid, self::$storageRowCache[$uid]);
+                       return $this->factory->getStorageObject($uid, self::$storageRowCache[$uid]);
                }
                return NULL;
        }
index 9db74bc..fde0cf6 100644 (file)
@@ -98,7 +98,7 @@ return array(
                        'label' => 'LLL:EXT:lang/locallang_tca.xlf:sys_file_storage.processingfolder',
                        'config' => array(
                                'type' => 'input',
-                               'placeholder' => \TYPO3\CMS\Core\Resource\ResourceStorage::DEFAULT_ProcessingFolder,
+                               'placeholder' => 'LLL:EXT:lang/locallang_tca.xlf:sys_file_storage.processingfolder.placeholder',
                                'size' => '20'
                        )
                ),
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-61529-AddMultipleParameterToCheckboxViewHelper.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-61529-AddMultipleParameterToCheckboxViewHelper.rst
deleted file mode 100644 (file)
index bf8baf0..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-===========================================================
-Feature: #61529 - Add multiple parameter to f:form.checkbox
-===========================================================
-
-Description
-===========
-
-Introduce parameter "multiple" for f:form.checkbox ViewHelper.
-
-::
-
-<f:form action="create" method="POST" name="pizza" object="{pizza}">
-       <f:form.checkbox property="covering" multiple="1" value="salami" /><br />
-       <f:form.checkbox property="covering" multiple="1" value="ham" /><br />
-       <f:form.checkbox property="covering" multiple="1" value="cheese" /><br />
-       <f:form.submit value="Send" />
-</f:form>
-
-..
-
-Impact
-======
-
-If you add the parameter "multiple" to your checkboxes, it automatically
-appends [] to the name of your checkbox.
\ No newline at end of file
index 7d38725..bf2d63c 100644 (file)
@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Core\Tests\Unit\Resource;
 use TYPO3\CMS\Core\Resource\ProcessedFile;
 use TYPO3\CMS\Core\Resource\ResourceStorage;
 use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\Folder;
 
 /**
  * Testcase for the ProcessedFile class of the TYPO3 FAL
@@ -24,6 +25,11 @@ use TYPO3\CMS\Core\Resource\File;
 class ProcessedFileTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
 
        /**
+        * @var \PHPUnit_Framework_MockObject_MockObject|Folder
+        */
+       protected $folderMock;
+
+       /**
         * @var \PHPUnit_Framework_MockObject_MockObject|ResourceStorage
         */
        protected $storageMock;
@@ -40,6 +46,11 @@ class ProcessedFileTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $this->storageMock = $this->getMock('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', array(), array(), '', FALSE);
                $this->storageMock->expects($this->any())->method('getUid')->will($this->returnValue(5));
 
+               $this->folderMock = $this->getMock('TYPO3\\CMS\\Core\\Resource\\Folder', array(), array(), '', FALSE);
+               $this->folderMock->expects($this->any())->method('getStorage')->willReturn($this->storageMock);
+
+               $this->storageMock->expects($this->any())->method('getProcessingFolder')->willReturn($this->folderMock);
+
                $this->databaseRow = array(
                        'uid' => '1234567',
                        'identifier' => 'dummy.txt',
index a64fe6d..3bac493 100644 (file)
@@ -41,7 +41,9 @@ class ResourceStorageTest extends \TYPO3\CMS\Core\Tests\Unit\Resource\BaseTestCa
                        'TYPO3\\CMS\\Core\\Resource\\FileRepository',
                        $this->getMock('TYPO3\\CMS\\Core\\Resource\\FileRepository')
                );
-               $GLOBALS['TYPO3_DB'] = $this->getMock('TYPO3\\CMS\Core\\Database\\DatabaseConnection');
+               $databaseMock = $this->getMock('TYPO3\\CMS\Core\\Database\\DatabaseConnection');
+               $databaseMock->expects($this->any())->method('exec_SELECTgetRows')->with('*', 'sys_file_storage', '1=1', '', 'name', '', 'uid')->willReturn(array());
+               $GLOBALS['TYPO3_DB'] = $databaseMock;
        }
 
        public function tearDown() {
index 31c276c..087b4d8 100644 (file)
@@ -150,8 +150,7 @@ class FileListController {
                                $this->folderObject = $fileFactory->getFolderObjectFromCombinedIdentifier($storage->getUid() . ':' . $identifier);
                                // Disallow the rendering of the processing folder (e.g. could be called manually)
                                // and all folders without any defined storage
-                               if ($this->folderObject && ($this->folderObject->getStorage()->getUid() == 0 || trim($this->folderObject->getStorage()->getProcessingFolder()->getIdentifier(), '/') === trim($this->folderObject->getIdentifier(), '/'))) {
-                                       $storage = $fileFactory->getStorageObjectFromCombinedIdentifier($combinedIdentifier);
+                               if ($this->folderObject && ($storage->getUid() === 0 || $storage->isProcessingFolder($this->folderObject))) {
                                        $this->folderObject = $storage->getRootLevelFolder();
                                }
                        } else {
index e6d68dd..9caf98f 100644 (file)
                        <trans-unit id="sys_file_storage.processingfolder" xml:space="preserve">
                                <source>Folder for manipulated and temporary images etc.</source>
                        </trans-unit>
+                       <trans-unit id="sys_file_storage.processingfolder.placeholder" xml:space="preserve">
+                               <source>_processed_ or 1:/processed_files_storage_x</source>
+                       </trans-unit>
                        <trans-unit id="sys_file_storage.driver" xml:space="preserve">
                                <source>Driver</source>
                        </trans-unit>