[BUGFIX] Prevent folder deletion with referenced files 77/36577/2
authorArmin Ruediger Vieweg <armin@v.ieweg.de>
Fri, 30 Jan 2015 11:57:34 +0000 (12:57 +0100)
committerNicole Cordes <typo3@cordes.co>
Mon, 2 Feb 2015 18:50:57 +0000 (19:50 +0100)
Currently a folder can be deleted without any check if included files
are still in use anywhere within the website. This can lead to lost
files and can be harmful for the user. The patch adds a test if files
within a folder, which should be deleted, are still referenced and
prevents deleting it if files where found. The user is notified with a
warning shown above the file list.

Resolves: #48893
Releases: master, 6.2
Change-Id: If1c79d86e1f6c7a32a6a3e94e639951f882302d7
Reviewed-on: http://review.typo3.org/36577
Reviewed-by: Armin Ruediger Vieweg <armin@v.ieweg.de>
Reviewed-by: Nicole Cordes <typo3@cordes.co>
Tested-by: Nicole Cordes <typo3@cordes.co>
typo3/sysext/core/Classes/Utility/File/ExtendedFileUtility.php
typo3/sysext/core/Tests/Unit/Utility/File/ExtendedFileUtilityTest.php [new file with mode: 0644]
typo3/sysext/lang/locallang_core.xlf

index f43c5fb..c322627 100644 (file)
@@ -385,7 +385,7 @@ class ExtendedFileUtility extends \TYPO3\CMS\Core\Utility\File\BasicFileUtility
         * @return boolean Returns TRUE upon success
         * @todo Define visibility
         */
-       public function func_delete($cmds) {
+       public function func_delete(array $cmds) {
                $result = FALSE;
                if (!$this->isInit) {
                        return $result;
@@ -394,7 +394,7 @@ class ExtendedFileUtility extends \TYPO3\CMS\Core\Utility\File\BasicFileUtility
                // for backwards compatibility: the combined file identifier was the path+filename
                try {
                        $fileObject = $this->getFileObject($cmds['data']);
-               } catch (ResourceDoesNotExistException $ex) {
+               } catch (ResourceDoesNotExistException $e) {
                        $flashMessage = GeneralUtility::makeInstance(
                                'TYPO3\\CMS\\Core\\Messaging\\FlashMessage',
                                sprintf( $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:message.description.fileNotFound'), $cmds['data']),
@@ -485,37 +485,81 @@ class ExtendedFileUtility extends \TYPO3\CMS\Core\Utility\File\BasicFileUtility
                                }
                        }
                } else {
-                       try {
-                               /** @var \TYPO3\CMS\Core\Resource\Folder $fileObject */
-                               $result = $fileObject->delete(TRUE);
-                               if ($result) {
-                                       // notify the user that the folder was deleted
-                                       /** @var \TYPO3\CMS\Core\Messaging\FlashMessage $flashMessage */
-                                       $flashMessage = GeneralUtility::makeInstance(
-                                               '\\TYPO3\\CMS\\Core\\Messaging\\FlashMessage',
-                                               sprintf($GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:message.description.folderDeleted'), $fileObject->getName()),
-                                               $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:message.header.folderDeleted'),
-                                               \TYPO3\CMS\Core\Messaging\FlashMessage::OK,
-                                               TRUE
-                                       );
-                                       $this->addFlashMessage($flashMessage);
-                                       // Log success
-                                       $this->writelog(4, 0, 3, 'Directory "%s" deleted', array($fileObject->getIdentifier()));
+                       /** @var \TYPO3\CMS\Core\Resource\Folder $fileObject */
+                       if (!$this->folderHasFilesInUse($fileObject)) {
+                               try {
+                                       $result = $fileObject->delete(TRUE);
+                                       if ($result) {
+                                               // notify the user that the folder was deleted
+                                               /** @var FlashMessage $flashMessage */
+                                               $flashMessage = GeneralUtility::makeInstance(
+                                                       'TYPO3\\CMS\\Core\\Messaging\\FlashMessage',
+                                                       sprintf($GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:message.description.folderDeleted'), $fileObject->getName()),
+                                                       $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:message.header.folderDeleted'),
+                                                       FlashMessage::OK,
+                                                       TRUE
+                                               );
+                                               $this->addFlashMessage($flashMessage);
+                                               // Log success
+                                               $this->writelog(4, 0, 3, 'Directory "%s" deleted', array($fileObject->getIdentifier()));
+                                       }
+                               } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException $e) {
+                                       $this->writelog(4, 1, 120, 'Could not delete directory! Is directory "%s" empty? (You are not allowed to delete directories recursively).', array($fileObject->getIdentifier()));
+                               } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException $e) {
+                                       $this->writelog(4, 1, 123, 'You are not allowed to access the directory', array($fileObject->getIdentifier()));
+                               } catch (\TYPO3\CMS\Core\Resource\Exception\NotInMountPointException $e) {
+                                       $this->writelog(4, 1, 121, 'Target was not within your mountpoints! T="%s"', array($fileObject->getIdentifier()));
+                               } catch (\TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException $e) {
+                                       $this->writelog(4, 1, 120, 'Could not delete directory "%s"! Write-permission problem?', array($fileObject->getIdentifier()));
                                }
-                       } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException $e) {
-                               $this->writelog(4, 1, 120, 'Could not delete directory! Is directory "%s" empty? (You are not allowed to delete directories recursively).', array($fileObject->getIdentifier()));
-                       } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException $e) {
-                               $this->writelog(4, 1, 123, 'You are not allowed to access the directory', array($fileObject->getIdentifier()));
-                       } catch (\TYPO3\CMS\Core\Resource\Exception\NotInMountPointException $e) {
-                               $this->writelog(4, 1, 121, 'Target was not within your mountpoints! T="%s"', array($fileObject->getIdentifier()));
-                       } catch (\TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException $e) {
-                               $this->writelog(4, 1, 120, 'Could not delete directory "%s"! Write-permission problem?', array($fileObject->getIdentifier()));
                        }
                }
+
                return $result;
        }
 
        /**
+        * Checks files in given folder recursively for for existing references.
+        *
+        * Creates a flash message if there are references.
+        *
+        * @param \TYPO3\CMS\Core\Resource\Folder $folder
+        * @return bool TRUE if folder has files in use, FALSE otherwise
+        */
+       public function folderHasFilesInUse(\TYPO3\CMS\Core\Resource\Folder $folder) {
+               $files = $folder->getFiles(0, 0, \TYPO3\CMS\Core\Resource\Folder::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, TRUE);
+               if (empty($files)) {
+                       return FALSE;
+               }
+
+               /** @var int[] $fileUids */
+               $fileUids = array();
+               foreach ($files as $file) {
+                       $fileUids[] = $file->getUid();
+               }
+               $numberOfReferences = $this->getDatabaseConnection()->exec_SELECTcountRows(
+                       '*',
+                       'sys_refindex',
+                       'deleted=0 AND ref_table="sys_file" AND ref_uid IN (' . implode(',', $fileUids) . ') AND tablename<>"sys_file_metadata"'
+               );
+
+               $hasReferences = $numberOfReferences > 0;
+               if ($hasReferences) {
+                       /** @var FlashMessage $flashMessage */
+                       $flashMessage = GeneralUtility::makeInstance(
+                               'TYPO3\\CMS\\Core\\Messaging\\FlashMessage',
+                               $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:message.description.folderNotDeletedHasFilesWithReferences'),
+                               $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:message.header.folderNotDeletedHasFilesWithReferences'),
+                               FlashMessage::WARNING,
+                               TRUE
+                       );
+                       $this->addFlashMessage($flashMessage);
+               }
+
+               return $hasReferences;
+       }
+
+       /**
         * Maps results from the fal file reference table on the
         * structure of  the normal reference index table.
         *
diff --git a/typo3/sysext/core/Tests/Unit/Utility/File/ExtendedFileUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/File/ExtendedFileUtilityTest.php
new file mode 100644 (file)
index 0000000..71fd842
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\Utility\File;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+use TYPO3\CMS\Core\Resource\Folder;
+use TYPO3\CMS\Core\Resource\File;
+
+/**
+ * Testcase for class \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility
+ *
+ * @author Armin RĂ¼diger Vieweg <armin@v.ieweg.de>
+ */
+class ExtendedFileUtilityTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
+
+       /**
+        * Sets up this testcase
+        */
+       protected function setUp() {
+               $GLOBALS['LANG'] = $this->getMock('TYPO3\\CMS\\Lang\\LanguageService', array('sL'));
+               $GLOBALS['TYPO3_DB'] = $this->getMock('TYPO3\\CMS\\Core\\Database\\DatabaseConnection', array());
+       }
+
+       /**
+        * @test
+        */
+       public function folderHasFilesInUseReturnsTrueIfItHasFiles() {
+               $fileUid = 1;
+               $file = $this->getMock('TYPO3\\CMS\\Core\\Resource\\File', array('getUid'), array(), '', FALSE);
+               $file->expects($this->once())->method('getUid')->will($this->returnValue($fileUid));
+
+               $folder = $this->getMock('TYPO3\\CMS\\Core\\Resource\\Folder', array('getFiles'), array(), '', FALSE);
+               $folder->expects($this->once())
+                       ->method('getFiles')->with(0, 0, Folder::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, TRUE)
+                       ->will($this->returnValue(array($file))
+               );
+
+               /** @var \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility $subject */
+               $subject = $this->getMock('TYPO3\\CMS\\Core\\Utility\File\\ExtendedFileUtility', array('addFlashMessage'), array(), '');
+               $GLOBALS['TYPO3_DB']->expects($this->once())
+                       ->method('exec_SELECTcountRows')->with('*', 'sys_refindex', 'deleted=0 AND ref_table="sys_file" AND ref_uid IN (' . $fileUid . ') AND tablename<>"sys_file_metadata"')
+                       ->will($this->returnValue(1));
+
+               $GLOBALS['LANG']->expects($this->at(0))->method('sL')
+                       ->with('LLL:EXT:lang/locallang_core.xlf:message.description.folderNotDeletedHasFilesWithReferences')
+                       ->will($this->returnValue('folderNotDeletedHasFilesWithReferences'));
+               $GLOBALS['LANG']->expects($this->at(1))->method('sL')
+                       ->with('LLL:EXT:lang/locallang_core.xlf:message.header.folderNotDeletedHasFilesWithReferences')
+                       ->will($this->returnValue('folderNotDeletedHasFilesWithReferences'));
+
+               $result = $subject->folderHasFilesInUse($folder);
+               $this->assertTrue($result);
+       }
+
+       /**
+        * @test
+        */
+       public function folderHasFilesInUseReturnsFalseIfItHasNoFiles() {
+               $folder = $this->getMock('TYPO3\\CMS\\Core\\Resource\\Folder', array('getFiles'), array(), '', FALSE);
+               $folder->expects($this->once())->method('getFiles')->with(0, 0, Folder::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, TRUE)->will(
+                       $this->returnValue(array())
+               );
+
+               /** @var \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility $subject */
+               $subject = $this->getMock('TYPO3\\CMS\\Core\\Utility\File\\ExtendedFileUtility', array('addFlashMessage'), array(), '');
+               $this->assertFalse($subject->folderHasFilesInUse($folder));
+       }
+
+}
\ No newline at end of file
index ea7d2fb..b5878aa 100644 (file)
@@ -729,6 +729,12 @@ Would you like to save now in order to refresh the display?</source>
                        <trans-unit id="message.description.fileNotDeletedHasReferences" xml:space="preserve">
                                <source>The file cannot be deleted since it is still used at the following places:</source>
                        </trans-unit>
+                       <trans-unit id="message.header.folderNotDeletedHasFilesWithReferences" xml:space="preserve">
+                               <source>Folder not deleted</source>
+                       </trans-unit>
+                       <trans-unit id="message.description.folderNotDeletedHasFilesWithReferences" xml:space="preserve">
+                               <source>The folder cannot be deleted since it still contains files in use.</source>
+                       </trans-unit>
                        <trans-unit id="message.header.fileHasBrokenReferences" xml:space="preserve">
                                <source>File has broken references</source>
                        </trans-unit>