[BUGFIX] FAL: Do not paste a folder into itself 86/36386/10
authorHelmut Hummel <helmut.hummel@typo3.org>
Thu, 29 Jan 2015 21:03:57 +0000 (22:03 +0100)
committerFrans Saris <franssaris@gmail.com>
Sat, 31 Jan 2015 21:54:12 +0000 (22:54 +0100)
If a folder is selected in fileadmin, which is already in
the clipboard, the paste icon is not shown to prevent
a endlessly nested folder structure.

Also the correct exception is thrown in ResourceStorage
if it occurs anyway.

Resolves: #51670
Releases: master, 6.2
Change-Id: I996cb8eede1371e479f756f18e1ede03f65950cb
Reviewed-on: http://review.typo3.org/36386
Reviewed-by: Michael Oehlhof <typo3@oehlhof.de>
Tested-by: Michael Oehlhof <typo3@oehlhof.de>
Reviewed-by: Helmut Hummel <helmut.hummel@typo3.org>
Tested-by: Helmut Hummel <helmut.hummel@typo3.org>
Reviewed-by: Philipp Gampe <philipp.gampe@typo3.org>
Reviewed-by: Frans Saris <franssaris@gmail.com>
Tested-by: Frans Saris <franssaris@gmail.com>
typo3/sysext/backend/Classes/ClickMenu/ClickMenu.php
typo3/sysext/core/Classes/Resource/ResourceStorage.php
typo3/sysext/core/Classes/Utility/File/ExtendedFileUtility.php
typo3/sysext/filelist/Classes/FileList.php

index fc230ad..c4874fb 100644 (file)
@@ -917,7 +917,11 @@ class ClickMenu {
                                        basename($identifier),
                                        $this->clipObj->currentMode()
                                );
-                               $menuItems['pasteinto'] = $this->FILE_paste($identifier, $selItem, $elInfo);
+                               $clickedFileOrFolder = ResourceFactory::getInstance()->retrieveFileOrFolderObject($combinedIdentifier);
+                               $fileOrFolderInClipBoard = ResourceFactory::getInstance()->retrieveFileOrFolderObject($selItem);
+                               if (!$fileOrFolderInClipBoard instanceof Folder || !$fileOrFolderInClipBoard->getStorage()->isWithinFolder($fileOrFolderInClipBoard, $clickedFileOrFolder)) {
+                                       $menuItems['pasteinto'] = $this->FILE_paste($identifier, $selItem, $elInfo);
+                               }
                        }
                        $menuItems[] = 'spacer';
                        // Delete:
index 9e76bec..dd3312c 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Core\Resource;
  */
 
 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;
 use TYPO3\CMS\Core\Utility\PathUtility;
@@ -1721,6 +1722,7 @@ class ResourceStorage implements ResourceStorageInterface {
         *
         * @throws \Exception|\TYPO3\CMS\Core\Exception
         * @throws \InvalidArgumentException
+        * @throws InvalidTargetFolderException
         * @return Folder
         */
        public function moveFolder(Folder $folderToMove, Folder $targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
@@ -1735,6 +1737,16 @@ class ResourceStorage implements ResourceStorageInterface {
                // Get all file objects now so we are able to update them after moving the folder
                $fileObjects = $this->getAllFileObjectsInFolder($folderToMove);
                if ($sourceStorage === $this) {
+                       if ($this->isWithinFolder($folderToMove, $targetParentFolder)) {
+                               throw new InvalidTargetFolderException(
+                                       sprintf(
+                                               'Cannot move folder "%s" into target folder "%s", because the target folder is already within the folder to be moved!',
+                                               $folderToMove->getName(),
+                                               $targetParentFolder->getName()
+                                       ),
+                                       1422723050
+                               );
+                       }
                        $fileMappings = $this->driver->moveFolderWithinStorage($folderToMove->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
                } else {
                        $fileMappings = $this->moveFolderBetweenStorages($folderToMove, $targetParentFolder, $sanitizedNewFolderName);
@@ -1772,6 +1784,7 @@ class ResourceStorage implements ResourceStorageInterface {
         * @param string $newFolderName
         * @param string $conflictMode  "overrideExistingFolder", "renameNewFolder", "cancel
         * @return Folder The new (copied) folder object
+        * @throws InvalidTargetFolderException
         */
        public function copyFolder(FolderInterface $folderToCopy, FolderInterface $targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
                // @todo implement the $conflictMode handling
@@ -1784,15 +1797,21 @@ class ResourceStorage implements ResourceStorageInterface {
                $sourceStorage = $folderToCopy->getStorage();
                // call driver method to move the file
                // that also updates the file object properties
-               try {
-                       if ($sourceStorage === $this) {
-                               $this->driver->copyFolderWithinStorage($folderToCopy->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
-                               $returnObject = $this->getFolder($targetParentFolder->getSubfolder($sanitizedNewFolderName)->getIdentifier());
-                       } else {
-                               $this->copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
+               if ($sourceStorage === $this) {
+                       if ($this->isWithinFolder($folderToCopy, $targetParentFolder)) {
+                               throw new InvalidTargetFolderException(
+                                       sprintf(
+                                               'Cannot copy folder "%s" into target folder "%s", because the target folder is already within the folder to be copied!',
+                                               $folderToCopy->getName(),
+                                               $targetParentFolder->getName()
+                                       ),
+                                       1422723059
+                               );
                        }
-               } catch (\TYPO3\CMS\Core\Exception $e) {
-                       echo $e->getMessage();
+                       $this->driver->copyFolderWithinStorage($folderToCopy->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
+                       $returnObject = $this->getFolder($targetParentFolder->getSubfolder($sanitizedNewFolderName)->getIdentifier());
+               } else {
+                       $this->copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
                }
                $this->emitPostFolderCopySignal($folderToCopy, $targetParentFolder, $returnObject->getName());
                return $returnObject;
@@ -2010,6 +2029,23 @@ class ResourceStorage implements ResourceStorageInterface {
        }
 
        /**
+        * Checks if a resource (file or folder) is within the given folder
+        *
+        * @param Folder $folder
+        * @param ResourceInterface $resource
+        * @return bool
+        */
+       public function isWithinFolder(Folder $folder, ResourceInterface $resource) {
+               if ($folder->getStorage() !== $this) {
+                       throw new \InvalidArgumentException('Given folder "' . $folder->getIdentifier() . '" is not part of this storage!', 1422709241);
+               }
+               if ($folder->getStorage() !== $resource->getStorage()) {
+                       return FALSE;
+               }
+               return $this->driver->isWithin($folder->getIdentifier(), $resource->getIdentifier());
+       }
+
+       /**
         * Returns the folders on the root level of the storage
         * or the first mount point of this storage for this user.
         *
index fae4a0c..0229341 100644 (file)
@@ -564,7 +564,7 @@ class ExtendedFileUtility extends BasicFileUtility {
                        } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException $e) {
                                $this->writelog(2, 1, 121, 'You don\'t have full access to the destination directory "%s"!', array($targetFolderObject->getIdentifier()));
                        } catch (\TYPO3\CMS\Core\Resource\Exception\InvalidTargetFolderException $e) {
-                               $this->writelog(2, 1, 122, 'Destination cannot be inside the target! D="%s", T="%s"', array($targetFolderObject->getIdentifier(), $sourceFolderObject->getIdentifier()));
+                               $this->writelog(2, 1, 122, 'Cannot copy folder "%s" into target folder "%s", because the target folder is already within the folder to be copied!', array($sourceFolderObject->getName(), $targetFolderObject->getName()));
                        } catch (\TYPO3\CMS\Core\Resource\Exception\ExistingTargetFolderException $e) {
                                $this->writelog(2, 1, 123, 'Target "%s" already exists!', array($targetFolderObject->getIdentifier()));
                        } catch (\BadMethodCallException $e) {
@@ -648,7 +648,7 @@ class ExtendedFileUtility extends BasicFileUtility {
                        } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException $e) {
                                $this->writelog(3, 1, 121, 'You don\'t have full access to the destination directory "%s"!', array($targetFolderObject->getIdentifier()));
                        } catch (\TYPO3\CMS\Core\Resource\Exception\InvalidTargetFolderException $e) {
-                               $this->writelog(3, 1, 122, 'Destination cannot be inside the target! D="%s", T="%s"', array($targetFolderObject->getIdentifier(), $sourceFolderObject->getIdentifier()));
+                               $this->writelog(3, 1, 122, 'Cannot move folder "%s" into target folder "%s", because the target folder is already within the folder to be moved!', array($sourceFolderObject->getName(), $targetFolderObject->getName()));
                        } catch (\TYPO3\CMS\Core\Resource\Exception\ExistingTargetFolderException $e) {
                                $this->writelog(3, 1, 123, 'Target "%s" already exists!', array($targetFolderObject->getIdentifier()));
                        } catch (\BadMethodCallException $e) {
index fd94b35..8792e9f 100644 (file)
@@ -25,6 +25,7 @@ use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Resource\InaccessibleFolder;
 use TYPO3\CMS\Core\Resource\ProcessedFile;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Resource\Utility\ListUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Resource\FolderInterface;
@@ -168,6 +169,18 @@ class FileList extends AbstractRecordList {
        public $clipObj;
 
        /**
+        * @var ResourceFactory
+        */
+       protected $resourceFactory;
+
+       /**
+        * @param ResourceFactory $resourceFactory
+        */
+       public function injectResourceFactory(ResourceFactory $resourceFactory) {
+               $this->resourceFactory = $resourceFactory;
+       }
+
+       /**
         * Initialization of class
         *
         * @param Folder $folderObject The folder to work on
@@ -194,6 +207,7 @@ class FileList extends AbstractRecordList {
                // Setting the maximum length of the filenames to the user's settings or minimum 30 (= $this->fixedL)
                $this->fixedL = max($this->fixedL, $this->getBackendUser()->uc['titleLen']);
                $this->getLanguageService()->includeLLFile('EXT:lang/locallang_common.xlf');
+               $this->resourceFactory = ResourceFactory::getInstance();
        }
 
        /**
@@ -245,7 +259,16 @@ class FileList extends AbstractRecordList {
                        if ($this->clipObj instanceof Clipboard && $folderObject->checkActionPermission('write')) {
                                $elFromTable = $this->clipObj->elFromTable('_FILE');
                                if (count($elFromTable)) {
-                                       $buttons['PASTE'] = '<a href="' . htmlspecialchars($this->clipObj->pasteUrl('_FILE', $this->folderObject->getCombinedIdentifier())) . '" onclick="return ' . htmlspecialchars($this->clipObj->confirmMsg('_FILE', $this->path, 'into', $elFromTable)) . '" title="' . $this->getLanguageService()->getLL('clip_paste', TRUE) . '">' . IconUtility::getSpriteIcon('actions-document-paste-after') . '</a>';
+                                       $addPasteButton = TRUE;
+                                       foreach ($elFromTable as $element) {
+                                               $clipBoardElement = $this->resourceFactory->retrieveFileOrFolderObject($element);
+                                               if ($clipBoardElement instanceof Folder && $this->folderObject->getStorage()->isWithinFolder($clipBoardElement, $folderObject)) {
+                                                       $addPasteButton = FALSE;
+                                               }
+                                       }
+                                       if ($addPasteButton) {
+                                               $buttons['PASTE'] = '<a href="' . htmlspecialchars($this->clipObj->pasteUrl('_FILE', $this->folderObject->getCombinedIdentifier())) . '" onclick="return ' . htmlspecialchars($this->clipObj->confirmMsg('_FILE', $this->path, 'into', $elFromTable)) . '" title="' . $this->getLanguageService()->getLL('clip_paste', TRUE) . '">' . IconUtility::getSpriteIcon('actions-document-paste-after') . '</a>';
+                                       }
                                }
                        }
 
@@ -370,7 +393,16 @@ class FileList extends AbstractRecordList {
                                        $table = '_FILE';
                                        $elFromTable = $this->clipObj->elFromTable($table);
                                        if (count($elFromTable) && $this->folderObject->checkActionPermission('write')) {
-                                               $cells[] = '<a class="btn" href="' . htmlspecialchars($this->clipObj->pasteUrl('_FILE', $this->folderObject->getCombinedIdentifier())) . '" onclick="return ' . htmlspecialchars($this->clipObj->confirmMsg('_FILE', $this->path, 'into', $elFromTable)) . '" title="' . $this->getLanguageService()->getLL('clip_paste', 1) . '">' . IconUtility::getSpriteIcon('actions-document-paste-after') . '</a>';
+                                               $addPasteButton = TRUE;
+                                               foreach ($elFromTable as $element) {
+                                                       $clipBoardElement = $this->resourceFactory->retrieveFileOrFolderObject($element);
+                                                       if ($clipBoardElement instanceof Folder && $this->folderObject->getStorage()->isWithinFolder($clipBoardElement, $this->folderObject)) {
+                                                               $addPasteButton = FALSE;
+                                                       }
+                                               }
+                                               if ($addPasteButton) {
+                                                       $cells[] = '<a class="btn" href="' . htmlspecialchars($this->clipObj->pasteUrl('_FILE', $this->folderObject->getCombinedIdentifier())) . '" onclick="return ' . htmlspecialchars($this->clipObj->confirmMsg('_FILE', $this->path, 'into', $elFromTable)) . '" title="' . $this->getLanguageService()->getLL('clip_paste', 1) . '">' . IconUtility::getSpriteIcon('actions-document-paste-after') . '</a>';
+                                               }
                                        }
                                        if ($this->clipObj->current != 'normal' && $iOut) {
                                                $cells[] = $this->linkClipboardHeaderIcon(IconUtility::getSpriteIcon('actions-edit-copy', array('title' => $this->getLanguageService()->getLL('clip_selectMarked', TRUE))), $table, 'setCB');
@@ -793,7 +825,16 @@ class FileList extends AbstractRecordList {
                // Display PASTE button, if directory:
                $elFromTable = $this->clipObj->elFromTable('_FILE');
                if (is_a($fileOrFolderObject, Folder::class) && count($elFromTable) && $fileOrFolderObject->checkActionPermission('write')) {
-                       $cells[] = '<a class="btn" href="' . htmlspecialchars($this->clipObj->pasteUrl('_FILE', $fullIdentifier)) . '" onclick="return ' . htmlspecialchars($this->clipObj->confirmMsg('_FILE', $fullIdentifier, 'into', $elFromTable)) . '" title="' . $this->getLanguageService()->getLL('clip_pasteInto', TRUE) . '">' . IconUtility::getSpriteIcon('actions-document-paste-into') . '</a>';
+                       $addPasteButton = TRUE;
+                       foreach ($elFromTable as $element) {
+                               $clipBoardElement = $this->resourceFactory->retrieveFileOrFolderObject($element);
+                               if ($clipBoardElement instanceof Folder && $fileOrFolderObject->getStorage()->isWithinFolder($clipBoardElement, $fileOrFolderObject)) {
+                                       $addPasteButton = FALSE;
+                               }
+                       }
+                       if ($addPasteButton) {
+                               $cells[] = '<a class="btn" href="' . htmlspecialchars($this->clipObj->pasteUrl('_FILE', $fullIdentifier)) . '" onclick="return ' . htmlspecialchars($this->clipObj->confirmMsg('_FILE', $fullIdentifier, 'into', $elFromTable)) . '" title="' . $this->getLanguageService()->getLL('clip_pasteInto', TRUE) . '">' . IconUtility::getSpriteIcon('actions-document-paste-into') . '</a>';
+                       }
                }
                // Compile items into a DIV-element:
                return ' <div class="btn-group">' . implode('', $cells) . '</div>';