[!!!][FEATURE] Add count methods and sort functionality to FAL drivers 60/28560/8
authorChristian Weiske <christian.weiske@netresearch.de>
Thu, 20 Mar 2014 12:02:00 +0000 (13:02 +0100)
committerFrans Saris <franssaris@gmail.com>
Wed, 8 Apr 2015 08:37:01 +0000 (10:37 +0200)
Instead of fetching all files and folders to count them,
two new FAL driver methods are introduced:
getFoldersInFolderCount() and getFilesInFolderCount()

Filelist makes use of the new counting methods, reducing the
processing time folders which contain a large number of files.

Sorting files is also moved into the driver.

Change-Id: I79c18fb697c976649adb40637f22df5341396d1e
Resolves: #56746
Releases: master
Reviewed-on: http://review.typo3.org/28560
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Frans Saris <franssaris@gmail.com>
Tested-by: Frans Saris <franssaris@gmail.com>
typo3/sysext/core/Classes/Resource/Driver/DriverInterface.php
typo3/sysext/core/Classes/Resource/Driver/LocalDriver.php
typo3/sysext/core/Classes/Resource/Folder.php
typo3/sysext/core/Classes/Resource/ResourceStorage.php
typo3/sysext/core/Documentation/Changelog/master/Breaking-56746-AddCountMethodsAndSortFunctionalityToFalDrivers.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Resource/Driver/LocalDriverTest.php
typo3/sysext/filelist/Classes/FileList.php

index aa3d488..598cb65 100644 (file)
@@ -52,12 +52,11 @@ interface DriverInterface {
        public function getCapabilities();
 
        /**
-        * Merges the capabilites merged by the user at the storage
+        * Merges the capabilities merged by the user at the storage
         * configuration into the actual capabilities of the driver
         * and returns the result.
         *
         * @param int $capabilities
-        *
         * @return int
         */
        public function mergeConfigurationCapabilities($capabilities);
@@ -119,7 +118,6 @@ interface DriverInterface {
         * Returns the identifier of the folder the file resides in
         *
         * @param string $fileIdentifier
-        *
         * @return string
         */
        public function getParentFolderIdentifierOfIdentifier($fileIdentifier);
@@ -128,7 +126,6 @@ interface DriverInterface {
         * Returns the public URL to a file.
         * Either fully qualified URL or relative to PATH_site (rawurlencoded).
         *
-        *
         * @param string $identifier
         * @return string
         */
@@ -167,7 +164,6 @@ interface DriverInterface {
         * Checks if a file exists.
         *
         * @param string $fileIdentifier
-        *
         * @return bool
         */
        public function fileExists($fileIdentifier);
@@ -176,7 +172,6 @@ interface DriverInterface {
         * Checks if a folder exists.
         *
         * @param string $folderIdentifier
-        *
         * @return bool
         */
        public function folderExists($folderIdentifier);
@@ -271,7 +266,6 @@ interface DriverInterface {
         * @param string $fileIdentifier
         * @param string $targetFolderIdentifier
         * @param string $newFileName
-        *
         * @return string
         */
        public function moveFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $newFileName);
@@ -283,7 +277,6 @@ interface DriverInterface {
         * @param string $sourceFolderIdentifier
         * @param string $targetFolderIdentifier
         * @param string $newFolderName
-        *
         * @return array All files which are affected, map of old => new file identifiers
         */
        public function moveFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName);
@@ -294,7 +287,6 @@ interface DriverInterface {
         * @param string $sourceFolderIdentifier
         * @param string $targetFolderIdentifier
         * @param string $newFolderName
-        *
         * @return bool
         */
        public function copyFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName);
@@ -410,10 +402,15 @@ interface DriverInterface {
         * @param int $numberOfItems
         * @param bool $recursive
         * @param array $filenameFilterCallbacks callbacks for filtering the items
-        *
+        * @param string $sort Property name used to sort the items.
+        *                     Among them may be: '' (empty, no sorting), name,
+        *                     fileext, size, tstamp and rw.
+        *                     If a driver does not support the given property, it
+        *                     should fall back to "name".
+        * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
         * @return array of FileIdentifiers
         */
-       public function getFilesInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = FALSE, array $filenameFilterCallbacks = array());
+       public function getFilesInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = FALSE, array $filenameFilterCallbacks = array(), $sort = '', $sortRev = FALSE);
 
        /**
         * Returns a list of folders inside the specified path
@@ -423,9 +420,34 @@ interface DriverInterface {
         * @param int $numberOfItems
         * @param bool $recursive
         * @param array $folderNameFilterCallbacks callbacks for filtering the items
-        *
+        * @param string $sort Property name used to sort the items.
+        *                     Among them may be: '' (empty, no sorting), name,
+        *                     fileext, size, tstamp and rw.
+        *                     If a driver does not support the given property, it
+        *                     should fall back to "name".
+        * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
         * @return array of Folder Identifier
         */
-       public function getFoldersInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = FALSE, array $folderNameFilterCallbacks = array());
+       public function getFoldersInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = FALSE, array $folderNameFilterCallbacks = array(), $sort = '', $sortRev = FALSE);
+
+       /**
+        * Returns the number of files inside the specified path
+        *
+        * @param string  $folderIdentifier
+        * @param boolean $recursive
+        * @param array   $filenameFilterCallbacks callbacks for filtering the items
+        * @return integer Number of files in folder
+        */
+       public function getFilesInFolderCount($folderIdentifier, $recursive = FALSE, array $filenameFilterCallbacks = array());
+
+       /**
+        * Returns the number of folders inside the specified path
+        *
+        * @param string  $folderIdentifier
+        * @param boolean $recursive
+        * @param array   $folderNameFilterCallbacks callbacks for filtering the items
+        * @return integer Number of folders in folder
+        */
+       public function getFoldersInFolderCount($folderIdentifier, $recursive = FALSE, array $folderNameFilterCallbacks = array());
 
 }
index dbf5789..c60b5e7 100644 (file)
@@ -84,7 +84,6 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * and returns the result.
         *
         * @param int $capabilities
-        *
         * @return int
         */
        public function mergeConfigurationCapabilities($capabilities) {
@@ -339,11 +338,16 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @param bool $includeFiles
         * @param bool $includeDirs
         * @param bool $recursive
-        *
+        * @param string $sort Property name used to sort the items.
+        *                     Among them may be: '' (empty, no sorting), name,
+        *                     fileext, size, tstamp and rw.
+        *                     If a driver does not support the given property, it
+        *                     should fall back to "name".
+        * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
         * @return array
         * @throws \InvalidArgumentException
         */
-       protected function getDirectoryItemList($folderIdentifier, $start = 0, $numberOfItems = 0, array $filterMethods, $includeFiles = TRUE, $includeDirs = TRUE, $recursive = FALSE) {
+       protected function getDirectoryItemList($folderIdentifier, $start = 0, $numberOfItems = 0, array $filterMethods, $includeFiles = TRUE, $includeDirs = TRUE, $recursive = FALSE, $sort = '', $sortRev = FALSE) {
                $folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier);
                $realPath = $this->getAbsolutePath($folderIdentifier);
                if (!is_dir($realPath)) {
@@ -357,15 +361,7 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
                        $start--;
                }
 
-               // Fetch the files and folders and sort them by name; we have to do
-               // this here because the directory iterator does return them in
-               // an arbitrary order
-               $items = $this->retrieveFileAndFoldersInPath($realPath, $recursive, $includeFiles, $includeDirs);
-               uksort(
-                       $items,
-                       array(\TYPO3\CMS\Core\Utility\ResourceUtility::class, 'recursiveFileListSortingHelper')
-               );
-
+               $items = $this->retrieveFileAndFoldersInPath($realPath, $recursive, $includeFiles, $includeDirs, $sort, $sortRev);
                $iterator = new \ArrayIterator($items);
                if ($iterator->count() === 0) {
                        return array();
@@ -392,7 +388,6 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
                                continue;
                        }
 
-
                        $items[$iteratorItem['identifier']] = $iteratorItem['identifier'];
                        // Decrement item counter to make sure we only return $numberOfItems
                        // we cannot do this earlier in the method (unlike moving the iterator forward) because we only add the
@@ -437,11 +432,28 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @param int $numberOfItems
         * @param bool $recursive
         * @param array $filenameFilterCallbacks The method callbacks to use for filtering the items
-        *
+        * @param string $sort Property name used to sort the items.
+        *                     Among them may be: '' (empty, no sorting), name,
+        *                     fileext, size, tstamp and rw.
+        *                     If a driver does not support the given property, it
+        *                     should fall back to "name".
+        * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
         * @return array of FileIdentifiers
         */
-       public function getFilesInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = FALSE, array $filenameFilterCallbacks = array()) {
-               return $this->getDirectoryItemList($folderIdentifier, $start, $numberOfItems, $filenameFilterCallbacks, TRUE, FALSE, $recursive);
+       public function getFilesInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = FALSE, array $filenameFilterCallbacks = array(), $sort = '', $sortRev = FALSE) {
+               return $this->getDirectoryItemList($folderIdentifier, $start, $numberOfItems, $filenameFilterCallbacks, TRUE, FALSE, $recursive, $sort, $sortRev);
+       }
+
+       /**
+        * Returns the number of files inside the specified path
+        *
+        * @param string $folderIdentifier
+        * @param bool $recursive
+        * @param array $filenameFilterCallbacks callbacks for filtering the items
+        * @return int Number of files in folder
+        */
+       public function getFilesInFolderCount($folderIdentifier, $recursive = FALSE, array $filenameFilterCallbacks = array()) {
+               return count($this->getFilesInFolder($folderIdentifier, 0, 0, $recursive, $filenameFilterCallbacks));
        }
 
        /**
@@ -452,11 +464,28 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @param int $numberOfItems
         * @param bool $recursive
         * @param array $folderNameFilterCallbacks The method callbacks to use for filtering the items
-        *
+        * @param string $sort Property name used to sort the items.
+        *                     Among them may be: '' (empty, no sorting), name,
+        *                     fileext, size, tstamp and rw.
+        *                     If a driver does not support the given property, it
+        *                     should fall back to "name".
+        * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
         * @return array of Folder Identifier
         */
-       public function getFoldersInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = FALSE, array $folderNameFilterCallbacks = array()) {
-               return $this->getDirectoryItemList($folderIdentifier, $start, $numberOfItems, $folderNameFilterCallbacks, FALSE, TRUE, $recursive);
+       public function getFoldersInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = FALSE, array $folderNameFilterCallbacks = array(), $sort = '', $sortRev = FALSE) {
+               return $this->getDirectoryItemList($folderIdentifier, $start, $numberOfItems, $folderNameFilterCallbacks, FALSE, TRUE, $recursive, $sort, $sortRev);
+       }
+
+       /**
+        * Returns the number of folders inside the specified path
+        *
+        * @param string  $folderIdentifier
+        * @param boolean $recursive
+        * @param array   $folderNameFilterCallbacks callbacks for filtering the items
+        * @return integer Number of folders in folder
+        */
+       public function getFoldersInFolderCount($folderIdentifier, $recursive = FALSE, array $folderNameFilterCallbacks = array()) {
+               return count($this->getFoldersInFolder($folderIdentifier, 0, 0, $recursive, $folderNameFilterCallbacks));
        }
 
        /**
@@ -466,9 +495,15 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @param bool $recursive If TRUE, recursively fetches files and folders
         * @param bool $includeFiles
         * @param bool $includeDirs
+        * @param string $sort Property name used to sort the items.
+        *                     Among them may be: '' (empty, no sorting), name,
+        *                     fileext, size, tstamp and rw.
+        *                     If a driver does not support the given property, it
+        *                     should fall back to "name".
+        * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
         * @return array
         */
-       protected function retrieveFileAndFoldersInPath($path, $recursive = FALSE, $includeFiles = TRUE, $includeDirs = TRUE) {
+       protected function retrieveFileAndFoldersInPath($path, $recursive = FALSE, $includeFiles = TRUE, $includeDirs = TRUE, $sort = '', $sortRev = FALSE) {
                $pathLength = strlen($this->getAbsoluteBasePath());
                $iteratorMode = \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::FOLLOW_SYMLINKS;
                if ($recursive) {
@@ -503,7 +538,68 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
                        $directoryEntries[$entryIdentifier] = $entryArray;
                        $iterator->next();
                }
-               return $directoryEntries;
+               return $this->sortDirectoryEntries($directoryEntries, $sort, $sortRev);
+       }
+
+       /**
+        * Sort the directory entries by a certain key
+        *
+        * @param array $directoryEntries Array of directory entry arrays from
+        *                                retrieveFileAndFoldersInPath()
+        * @param string $sort Property name used to sort the items.
+        *                     Among them may be: '' (empty, no sorting), name,
+        *                     fileext, size, tstamp and rw.
+        *                     If a driver does not support the given property, it
+        *                     should fall back to "name".
+        * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
+        * @return array Sorted entries. Content of the keys is undefined.
+        */
+       protected function sortDirectoryEntries($directoryEntries, $sort = '', $sortRev = FALSE) {
+               $entriesToSort = array();
+               foreach ($directoryEntries as $entryArray) {
+                       $dir      = pathinfo($entryArray['name'], PATHINFO_DIRNAME) . '/';
+                       $fullPath = $this->getAbsoluteBasePath() . $entryArray['identifier'];
+                       switch ($sort) {
+                               case 'size':
+                                       if ($entryArray['type'] === 'file') {
+                                               $sortingKey = $this->getSpecificFileInformation($fullPath, $dir, 'size');
+                                       } else {
+                                               $sortingKey = '0';
+                                       }
+                                       break;
+                               case 'rw':
+                                       $perms = $this->getPermissions($entryArray['identifier']);
+                                       $sortingKey = ($perms['r'] ? 'R' : '')
+                                               . ($perms['w'] ? 'W' : '');
+                                       break;
+                               case 'fileext':
+                                       $sortingKey = pathinfo($entryArray['name'], PATHINFO_EXTENSION);
+                                       break;
+                               case 'tstamp':
+                                       if ($entryArray['type'] === 'file') {
+                                               $sortingKey = $this->getSpecificFileInformation($fullPath, $dir, 'mtime');
+                                       } else {
+                                               $sortingKey = '0';
+                                       }
+                                       break;
+                               case 'name':
+                               case 'file':
+                               default:
+                                       $sortingKey = $entryArray['name'];
+                       }
+                       $i = 0;
+                       while (isset($entriesToSort[$sortingKey . $i])) {
+                               $i++;
+                       }
+                       $entriesToSort[$sortingKey . $i] = $entryArray;
+               }
+               uksort($entriesToSort, 'strnatcasecmp');
+
+               if ($sortRev) {
+                       $entriesToSort = array_reverse($entriesToSort);
+               }
+
+               return $entriesToSort;
        }
 
        /**
@@ -715,7 +811,6 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         *
         * @param string $folderName The name of the target folder
         * @param string $folderIdentifier
-        *
         * @return string
         */
        public function getFolderInFolder($folderName, $folderIdentifier) {
@@ -769,7 +864,6 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * @param string $fileIdentifier
         * @param string $targetFolderIdentifier
         * @param string $newFileName
-        *
         * @return string
         * @throws \RuntimeException
         */
@@ -1212,12 +1306,10 @@ class LocalDriver extends AbstractHierarchicalFilesystemDriver {
         * buffer before. Will be taken care of by the Storage.
         *
         * @param string $identifier
-        *
         * @return void
         */
        public function dumpFileContents($identifier) {
                readfile($this->getAbsolutePath($this->canonicalizeAndCheckFileIdentifier($identifier)), 0);
        }
 
-
 }
index b10cd6a..607905b 100644 (file)
@@ -169,9 +169,15 @@ class Folder implements FolderInterface {
         * @param int $numberOfItems The number of items to return
         * @param int $filterMode The filter mode to use for the file list.
         * @param bool $recursive
+        * @param string $sort Property name used to sort the items.
+        *                     Among them may be: '' (empty, no sorting), name,
+        *                     fileext, size, tstamp and rw.
+        *                     If a driver does not support the given property, it
+        *                     should fall back to "name".
+        * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
         * @return \TYPO3\CMS\Core\Resource\File[]
         */
-       public function getFiles($start = 0, $numberOfItems = 0, $filterMode = self::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, $recursive = FALSE) {
+       public function getFiles($start = 0, $numberOfItems = 0, $filterMode = self::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, $recursive = FALSE, $sort = '', $sortRev = FALSE) {
                // Fallback for compatibility with the old method signature variable $useFilters that was used instead of $filterMode
                if ($filterMode === FALSE) {
                        $useFilters = FALSE;
@@ -180,7 +186,7 @@ class Folder implements FolderInterface {
                        list($backedUpFilters, $useFilters) = $this->prepareFiltersInStorage($filterMode);
                }
 
-               $fileObjects = $this->storage->getFilesInFolder($this, $start, $numberOfItems, $useFilters, $recursive);
+               $fileObjects = $this->storage->getFilesInFolder($this, $start, $numberOfItems, $useFilters, $recursive, $sort, $sortRev);
 
                $this->restoreBackedUpFiltersInStorage($backedUpFilters);
 
@@ -193,20 +199,18 @@ class Folder implements FolderInterface {
         *
         * @param array $filterMethods
         * @param bool $recursive
-        *
         * @return int
         */
        public function getFileCount(array $filterMethods = array(), $recursive = FALSE) {
-               return count($this->storage->getFileIdentifiersInFolder($this->identifier, TRUE, $recursive));
+               return $this->storage->getFilesInFolderCount($this, TRUE, $recursive);
        }
 
        /**
         * Returns the object for a subfolder of the current folder, if it exists.
         *
         * @param string $name Name of the subfolder
-        *
-        * @throws \InvalidArgumentException
         * @return Folder
+        * @throws \InvalidArgumentException
         */
        public function getSubfolder($name) {
                if (!$this->storage->hasFolderInFolder($name, $this)) {
index 66bf676..163aae7 100644 (file)
@@ -1304,23 +1304,25 @@ class ResourceStorage implements ResourceStorageInterface {
         * @param int $maxNumberOfItems
         * @param bool $useFilters
         * @param bool $recursive
+        * @param string $sort Property name used to sort the items.
+        *                     Among them may be: '' (empty, no sorting), name,
+        *                     fileext, size, tstamp and rw.
+        *                     If a driver does not support the given property, it
+        *                     should fall back to "name".
+        * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
         * @return File[]
+        * @throws Exception\InsufficientFolderAccessPermissionsException
         */
-       public function getFilesInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = TRUE, $recursive = FALSE) {
+       public function getFilesInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = TRUE, $recursive = FALSE, $sort = '', $sortRev = FALSE) {
                $this->assureFolderReadPermission($folder);
 
                $rows = $this->getFileIndexRepository()->findByFolder($folder);
 
                $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
-               $fileIdentifiers = array_values($this->driver->getFilesInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters));
-               $fileIdentifiersCount = count($fileIdentifiers);
+               $fileIdentifiers = array_values($this->driver->getFilesInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev));
+
                $items = array();
-               if ($maxNumberOfItems === 0) {
-                       $maxNumberOfItems = $fileIdentifiersCount;
-               }
-               $end = min($fileIdentifiersCount, $start + $maxNumberOfItems);
-               for ($i = $start; $i < $end; $i++) {
-                       $identifier = $fileIdentifiers[$i];
+               foreach ($fileIdentifiers as $identifier) {
                        if (isset($rows[$identifier])) {
                                $fileObject = $this->getFileFactory()->getFileObject($rows[$identifier]['uid'], $rows[$identifier]);
                        } else {
@@ -1334,7 +1336,6 @@ class ResourceStorage implements ResourceStorageInterface {
                                $items[$key] = $fileObject;
                        }
                }
-               uksort($items, 'strnatcasecmp');
 
                return $items;
        }
@@ -1343,7 +1344,6 @@ class ResourceStorage implements ResourceStorageInterface {
         * @param string $folderIdentifier
         * @param bool $useFilters
         * @param bool $recursive
-        *
         * @return array
         */
        public function getFileIdentifiersInFolder($folderIdentifier, $useFilters = TRUE, $recursive = FALSE) {
@@ -1352,10 +1352,21 @@ class ResourceStorage implements ResourceStorageInterface {
        }
 
        /**
+        * @param Folder $folder
+        * @param bool $useFilters
+        * @param bool $recursive
+        * @return int Number of files in folder
+        */
+       public function getFilesInFolderCount(Folder $folder, $useFilters = TRUE, $recursive = FALSE) {
+               $this->assureFolderReadPermission($folder);
+               $filters = $useFilters ? $this->fileAndFolderNameFilters : array();
+               return $this->driver->getFilesInFolderCount($folder->getIdentifier(), $recursive, $filters);
+       }
+
+       /**
         * @param string $folderIdentifier
         * @param bool $useFilters
         * @param bool $recursive
-        *
         * @return array
         */
        public function getFolderIdentifiersInFolder($folderIdentifier, $useFilters = TRUE, $recursive = FALSE) {
@@ -1946,12 +1957,18 @@ class ResourceStorage implements ResourceStorageInterface {
         * @param int $maxNumberOfItems
         * @param bool $useFilters
         * @param bool $recursive
-        *
+        * @param string $sort Property name used to sort the items.
+        *                     Among them may be: '' (empty, no sorting), name,
+        *                     fileext, size, tstamp and rw.
+        *                     If a driver does not support the given property, it
+        *                     should fall back to "name".
+        * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
         * @return Folder[]
         */
-       public function getFoldersInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = TRUE, $recursive = FALSE) {
+       public function getFoldersInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = TRUE, $recursive = FALSE, $sort = '', $sortRev = FALSE) {
                $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
-               $folderIdentifiers = $this->driver->getFoldersInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters);
+
+               $folderIdentifiers = $this->driver->getFoldersInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev);
 
                // Exclude processing folders
                foreach ($this->getProcessingFolders() as $processingFolder) {
@@ -1968,6 +1985,18 @@ class ResourceStorage implements ResourceStorageInterface {
        }
 
        /**
+        * @param Folder  $folder
+        * @param bool $useFilters
+        * @param bool $recursive
+        * @return integer Number of subfolders
+        */
+       public function getFoldersInFolderCount(Folder $folder, $useFilters = TRUE, $recursive = FALSE) {
+               $this->assureFolderReadPermission($folder);
+               $filters = $useFilters ? $this->fileAndFolderNameFilters : array();
+               return $this->driver->getFoldersInFolderCount($folder->getIdentifier(), $recursive, $filters);
+       }
+
+       /**
         * Returns TRUE if the specified folder exists.
         *
         * @param string $identifier
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-56746-AddCountMethodsAndSortFunctionalityToFalDrivers.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-56746-AddCountMethodsAndSortFunctionalityToFalDrivers.rst
new file mode 100644 (file)
index 0000000..ee700cc
--- /dev/null
@@ -0,0 +1,40 @@
+==========================================================================
+Breaking: #56746 - Add count methods and sort functionality to FAL drivers
+==========================================================================
+
+Description
+===========
+
+To improve the performance of the file list when showing (remote)storages with a lot of
+files and folders the sorting and ordering needs to be done by the driver. Also the pagination of
+the file list can be improved by moving the counting to the driver instead of fetching all files and
+folders objects to count them.
+
+
+Impact
+======
+
+Installations with custom FAL drivers will break after update.
+
+
+Affected installations
+======================
+
+TYPO3 CMS 7 installations using custom FAL drivers
+
+
+Migration
+=========
+
+The custom FAL drivers need to be updated to be in line with the updated DriverInterface.
+
+2 new functions need to be added:
+
+ - ``getFoldersInFolderCount()``
+ - ``getFilesInFolderCount()``
+
+2 functions need to be extended with the parameters $sort and $sortRev:
+
+ - ``getFilesInFolder()``
+ - ``getFoldersInFolder()``
+
index 9a93662..23ee16b 100644 (file)
@@ -675,7 +675,7 @@ class LocalDriverTest extends \TYPO3\CMS\Core\Tests\Unit\Resource\BaseTestCase {
                        array('getMimeTypeOfFile')
                );
                $fileList = $subject->getFilesInFolder('/', 0, 0, TRUE);
-               $this->assertEquals(array('/aDir/subdir/file4', '/aDir/file3', '/file1', '/file2'), array_keys($fileList));
+               $this->assertEquals(array('/file1', '/file2', '/aDir/file3', '/aDir/subdir/file4'), array_keys($fileList));
        }
 
        /**
index 01ec07a..356069e 100644 (file)
@@ -150,11 +150,6 @@ class FileList extends AbstractRecordList {
        public $eCounter = 0;
 
        /**
-        * @var int
-        */
-       public $dirCounter = 0;
-
-       /**
         * @var string
         */
        public $totalItems = '';
@@ -311,49 +306,39 @@ class FileList extends AbstractRecordList {
 
                // Only render the contents of a browsable storage
                if ($this->folderObject->getStorage()->isBrowsable()) {
-                       $folders = $storage->getFolderIdentifiersInFolder($this->folderObject->getIdentifier());
-                       $files = $this->folderObject->getFiles();
-                       $this->sort = trim($this->sort);
-                       if ($this->sort !== '') {
-                               $filesToSort = array();
-                               /** @var $fileObject File */
-                               foreach ($files as $fileObject) {
-                                       switch ($this->sort) {
-                                               case 'size':
-                                                       $sortingKey = $fileObject->getSize();
-                                                       break;
-                                               case 'rw':
-                                                       $sortingKey = ($fileObject->checkActionPermission('read') ? 'R' : '' . $fileObject->checkActionPermission('write')) ? 'W' : '';
-                                                       break;
-                                               case 'fileext':
-                                                       $sortingKey = $fileObject->getExtension();
-                                                       break;
-                                               case 'tstamp':
-                                                       $sortingKey = $fileObject->getModificationTime();
-                                                       break;
-                                               case 'file':
-                                                       $sortingKey = $fileObject->getName();
-                                                       break;
-                                               default:
-                                                       if ($fileObject->hasProperty($this->sort)) {
-                                                               $sortingKey = $fileObject->getProperty($this->sort);
-                                                       } else {
-                                                               $sortingKey = $fileObject->getName();
-                                                       }
-                                       }
-                                       $i = 1000000;
-                                       while (isset($filesToSort[$sortingKey . $i])) {
-                                               $i++;
-                                       }
-                                       $filesToSort[$sortingKey . $i] = $fileObject;
+                       $foldersCount = $storage->getFoldersInFolderCount($this->folderObject);
+                       $filesCount = $storage->getFilesInFolderCount($this->folderObject);
+
+                       if ($foldersCount <= $this->firstElementNumber) {
+                               $foldersFrom = FALSE;
+                               $foldersNum = FALSE;
+                       } else {
+                               $foldersFrom = $this->firstElementNumber;
+                               if ($this->firstElementNumber + $this->iLimit > $foldersCount) {
+                                       $foldersNum = $foldersCount - $this->firstElementNumber;
+                               } else {
+                                       $foldersNum = $this->iLimit;
                                }
-                               uksort($filesToSort, 'strnatcasecmp');
-                               if ((int)$this->sortRev === 1) {
-                                       $filesToSort = array_reverse($filesToSort);
+                       }
+                       if ($foldersCount >= $this->firstElementNumber + $this->iLimit) {
+                               $filesFrom = FALSE;
+                               $filesNum  = FALSE;
+                       } else {
+                               if ($this->firstElementNumber <= $foldersCount) {
+                                       $filesFrom = 0;
+                                       $filesNum  = $this->iLimit - $foldersNum;
+                               } else {
+                                       $filesFrom = $this->firstElementNumber - $foldersCount;
+                                       if ($filesFrom + $this->iLimit > $filesCount) {
+                                               $filesNum = $filesCount - $filesFrom;
+                                       } else {
+                                               $filesNum  = $this->iLimit;
+                                       }
                                }
-                               $files = $filesToSort;
                        }
-                       $this->totalItems = count($folders) + count($files);
+                       $folders = $storage->getFoldersInFolder($this->folderObject, $foldersFrom, $foldersNum, TRUE, FALSE, trim($this->sort), (bool)$this->sortRev);
+                       $files = $this->folderObject->getFiles($filesFrom, $filesNum, Folder::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, FALSE, trim($this->sort), (bool)$this->sortRev);
+                       $this->totalItems = $foldersCount + $filesCount;
                        // Adds the code of files/dirs
                        $out = '';
                        $titleCol = 'file';
@@ -371,21 +356,29 @@ class FileList extends AbstractRecordList {
                                $this->addElement_tdCssClass['_CONTROL_'] = 'col-control';
                        }
                        $this->fieldArray = explode(',', $rowlist);
-                       $folderObjects = array();
-                       foreach ($folders as $folder) {
-                               $folderObjects[] = $storage->getFolder($folder, TRUE);
-                       }
+
                        // Add classes to table cells
                        $this->addElement_tdCssClass[$titleCol] = 'col-title';
                        $this->addElement_tdCssClass['_LOCALIZATION_'] = 'col-localizationa';
 
-                       $folderObjects = ListUtility::resolveSpecialFolderNames($folderObjects);
-                       uksort($folderObjects, 'strnatcasecmp');
+                       $folders = ListUtility::resolveSpecialFolderNames($folders);
 
+                       $iOut = '';
                        // Directories are added
-                       $iOut = $this->formatDirList($folderObjects);
+                       $this->eCounter = $this->firstElementNumber;
+                       list($flag, $code) = $this->fwd_rwd_nav();
+                       $iOut .= $code;
+
+                       $iOut .= $this->formatDirList($folders);
                        // Files are added
-                       $iOut .= $this->formatFileList($files, $titleCol);
+                       $iOut .= $this->formatFileList($files);
+
+                       $this->eCounter = $this->firstElementNumber + $this->iLimit <= $this->totalItems
+                               ? $this->firstElementNumber + $this->iLimit
+                               : $this->totalItems;
+                       list($flag, $code) = $this->fwd_rwd_nav();
+                       $iOut .= $code;
+
                        // Header line is drawn
                        $theData = array();
                        foreach ($this->fieldArray as $v) {
@@ -500,66 +493,60 @@ class FileList extends AbstractRecordList {
                                $displayName = htmlspecialchars($folderName);
                        }
 
-                       list($flag, $code) = $this->fwd_rwd_nav();
-                       $out .= $code;
-                       if ($flag) {
-                               $isLocked = $folderObject instanceof InaccessibleFolder;
-                               $isWritable = $folderObject->checkActionPermission('write');
+                       $isLocked = $folderObject instanceof InaccessibleFolder;
+                       $isWritable = $folderObject->checkActionPermission('write');
 
-                               // Initialization
-                               $this->counter++;
+                       // Initialization
+                       $this->counter++;
 
-                               // The icon with link
-                               $theIcon = IconUtility::getSpriteIconForResource($folderObject, array('title' => $folderName));
-                               if (!$isLocked && $this->clickMenus) {
-                                       $theIcon = $GLOBALS['SOBE']->doc->wrapClickMenuOnIcon($theIcon, $folderObject->getCombinedIdentifier());
-                               }
+                       // The icon with link
+                       $theIcon = IconUtility::getSpriteIconForResource($folderObject, array('title' => $folderName));
+                       if (!$isLocked && $this->clickMenus) {
+                               $theIcon = $GLOBALS['SOBE']->doc->wrapClickMenuOnIcon($theIcon, $folderObject->getCombinedIdentifier());
+                       }
 
-                               // Preparing and getting the data-array
-                               $theData = array();
-                               if ($isLocked) {
-                                       foreach ($this->fieldArray as $field) {
-                                               $theData[$field] = '';
-                                       }
-                                       $theData['file'] = $displayName;
-                               } else {
-                                       foreach ($this->fieldArray as $field) {
-                                               switch ($field) {
-                                                       case 'size':
-                                                               $numFiles = $folderObject->getFileCount();
-                                                               $theData[$field] = $numFiles . ' ' . $this->getLanguageService()->getLL(($numFiles === 1 ? 'file' : 'files'), TRUE);
-                                                               break;
-                                                       case 'rw':
-                                                               $theData[$field] = '<strong class="text-danger">' . $this->getLanguageService()->getLL('read', TRUE) . '</strong>' . (!$isWritable ? '' : '<strong class="text-danger">' . $this->getLanguageService()->getLL('write', TRUE) . '</strong>');
-                                                               break;
-                                                       case 'fileext':
-                                                               $theData[$field] = $this->getLanguageService()->getLL('folder', TRUE);
-                                                               break;
-                                                       case 'tstamp':
-                                                               // @todo: FAL: how to get the mtime info -- $theData[$field] = \TYPO3\CMS\Backend\Utility\BackendUtility::date($theFile['tstamp']);
-                                                               $theData[$field] = '-';
-                                                               break;
-                                                       case 'file':
-                                                               $theData[$field] = $this->linkWrapDir($displayName, $folderObject);
-                                                               break;
-                                                       case '_CONTROL_':
-                                                               $theData[$field] = $this->makeEdit($folderObject);
-                                                               break;
-                                                       case '_CLIPBOARD_':
-                                                               $theData[$field] = $this->makeClip($folderObject);
-                                                               break;
-                                                       case '_REF_':
-                                                               $theData[$field] = $this->makeRef($folderObject);
-                                                               break;
-                                                       default:
-                                                               $theData[$field] = GeneralUtility::fixed_lgd_cs($theData[$field], $this->fixedL);
-                                               }
+                       // Preparing and getting the data-array
+                       $theData = array();
+                       if ($isLocked) {
+                               foreach ($this->fieldArray as $field) {
+                                       $theData[$field] = '';
+                               }
+                               $theData['file'] = $displayName;
+                       } else {
+                               foreach ($this->fieldArray as $field) {
+                                       switch ($field) {
+                                               case 'size':
+                                                       $numFiles = $folderObject->getFileCount();
+                                                       $theData[$field] = $numFiles . ' ' . $this->getLanguageService()->getLL(($numFiles === 1 ? 'file' : 'files'), TRUE);
+                                                       break;
+                                               case 'rw':
+                                                       $theData[$field] = '<strong class="text-danger">' . $this->getLanguageService()->getLL('read', TRUE) . '</strong>' . (!$isWritable ? '' : '<strong class="text-danger">' . $this->getLanguageService()->getLL('write', TRUE) . '</strong>');
+                                                       break;
+                                               case 'fileext':
+                                                       $theData[$field] = $this->getLanguageService()->getLL('folder', TRUE);
+                                                       break;
+                                               case 'tstamp':
+                                                       // @todo: FAL: how to get the mtime info -- $theData[$field] = \TYPO3\CMS\Backend\Utility\BackendUtility::date($theFile['tstamp']);
+                                                       $theData[$field] = '-';
+                                                       break;
+                                               case 'file':
+                                                       $theData[$field] = $this->linkWrapDir($displayName, $folderObject);
+                                                       break;
+                                               case '_CONTROL_':
+                                                       $theData[$field] = $this->makeEdit($folderObject);
+                                                       break;
+                                               case '_CLIPBOARD_':
+                                                       $theData[$field] = $this->makeClip($folderObject);
+                                                       break;
+                                               case '_REF_':
+                                                       $theData[$field] = $this->makeRef($folderObject);
+                                                       break;
+                                               default:
+                                                       $theData[$field] = GeneralUtility::fixed_lgd_cs($theData[$field], $this->fixedL);
                                        }
                                }
-                               $out .= $this->addelement(1, $theIcon, $theData);
                        }
-                       $this->eCounter++;
-                       $this->dirCounter = $this->eCounter;
+                       $out .= $this->addelement(1, $theIcon, $theData);
                }
                return $out;
        }
@@ -639,116 +626,111 @@ class FileList extends AbstractRecordList {
                });
 
                foreach ($files as $fileObject) {
-                       list($flag, $code) = $this->fwd_rwd_nav();
-                       $out .= $code;
-                       if ($flag) {
-                               // Initialization
-                               $this->counter++;
-                               $this->totalbytes += $fileObject->getSize();
-                               $ext = $fileObject->getExtension();
-                               $fileName = trim($fileObject->getName());
-                               // The icon with link
-                               $theIcon = IconUtility::getSpriteIconForResource($fileObject, array('title' => $fileName . ' [' . (int)$fileObject->getUid() . ']'));
-                               if ($this->clickMenus) {
-                                       $theIcon = $GLOBALS['SOBE']->doc->wrapClickMenuOnIcon($theIcon, $fileObject->getCombinedIdentifier());
-                               }
-                               // Preparing and getting the data-array
-                               $theData = array();
-                               foreach ($this->fieldArray as $field) {
-                                       switch ($field) {
-                                               case 'size':
-                                                       $theData[$field] = GeneralUtility::formatSize($fileObject->getSize(), $this->getLanguageService()->getLL('byteSizeUnits', TRUE));
-                                                       break;
-                                               case 'rw':
-                                                       $theData[$field] = '' . (!$fileObject->checkActionPermission('read') ? ' ' : '<strong class="text-danger">' . $this->getLanguageService()->getLL('read', TRUE) . '</strong>') . (!$fileObject->checkActionPermission('write') ? '' : '<strong class="text-danger">' . $this->getLanguageService()->getLL('write', TRUE) . '</strong>');
-                                                       break;
-                                               case 'fileext':
-                                                       $theData[$field] = strtoupper($ext);
-                                                       break;
-                                               case 'tstamp':
-                                                       $theData[$field] = BackendUtility::date($fileObject->getModificationTime());
-                                                       break;
-                                               case '_CONTROL_':
-                                                       $theData[$field] = $this->makeEdit($fileObject);
-                                                       break;
-                                               case '_CLIPBOARD_':
-                                                       $theData[$field] = $this->makeClip($fileObject);
-                                                       break;
-                                               case '_LOCALIZATION_':
-                                                       if (!empty($systemLanguages) && $fileObject->isIndexed() && $fileObject->checkActionPermission('write') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
-                                                               $metaDataRecord = $fileObject->_getMetaData();
-                                                               $translations = $this->getTranslationsForMetaData($metaDataRecord);
-                                                               $languageCode = '';
-
-                                                               foreach ($systemLanguages as $language) {
-                                                                       $languageId = $language['uid'];
-                                                                       $flagIcon = $language['flagIcon'];
-                                                                       if (array_key_exists($languageId, $translations)) {
-                                                                               $flagButtonIcon = IconUtility::getSpriteIcon(
-                                                                                       'actions-document-open',
-                                                                                       array('title' => sprintf($GLOBALS['LANG']->getLL('editMetadataForLanguage'), $language['title'])),
-                                                                                       array($flagIcon . '-overlay' => array()));
-                                                                               $data = array(
-                                                                                       'sys_file_metadata' => array($translations[$languageId]['uid'] => 'edit')
-                                                                               );
-                                                                               $editOnClick = BackendUtility::editOnClick(GeneralUtility::implodeArrayForUrl('edit', $data), '', $this->listUrl());
-                                                                               $languageCode .= '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($editOnClick) . '">' . $flagButtonIcon . '</a>';
-                                                                       } else {
-                                                                               $href = $GLOBALS['SOBE']->doc->issueCommand(
-                                                                                       '&cmd[sys_file_metadata][' . $metaDataRecord['uid'] . '][localize]=' . $languageId,
-                                                                                       BackendUtility::getModuleUrl('record_edit', array('justLocalized' => 'sys_file_metadata:' . $metaDataRecord['uid'] . ':' . $languageId, 'returnUrl' => $this->listURL())) . BackendUtility::getUrlToken('editRecord')
-                                                                               );
-                                                                               $flagButtonIcon = IconUtility::getSpriteIcon(
-                                                                                       $flagIcon,
-                                                                                       array('title' => sprintf($GLOBALS['LANG']->getLL('createMetadataForLanguage'), $language['title'])),
-                                                                                       array($flagIcon . '-overlay' => array())
-                                                                               );
-                                                                               $languageCode .= '<a href="' . htmlspecialchars($href) . '" class="btn btn-default">' . $flagButtonIcon . '</a> ';
-                                                                       }
-                                                               }
-
-                                                               // Hide flag button bar when not translated yet
-                                                               $theData[$field] = ' <div class="localisationData btn-group" data-fileid="' . $fileObject->getUid() . '"' .
-                                                                       (empty($translations) ? ' style="display: none;"' : '') . '>' . $languageCode . '</div>';
-                                                               $theData[$field] .= '<a class="btn btn-default filelist-translationToggler" data-fileid="' . $fileObject->getUid() . '">' .
-                                                                       IconUtility::getSpriteIcon(
-                                                                               'mimetypes-x-content-page-language-overlay',
-                                                                               array(
-                                                                                       'title' => $GLOBALS['LANG']->getLL('translateMetadata')
-                                                                               )
-                                                                       ) . '</a>';
-                                                       }
-                                                       break;
-                                               case '_REF_':
-                                                       $theData[$field] = $this->makeRef($fileObject);
-                                                       break;
-                                               case 'file':
-                                                       // Edit metadata of file
-                                                       $theData[$field] = $this->linkWrapFile(htmlspecialchars($fileName), $fileObject);
-
-                                                       if ($fileObject->isMissing()) {
-                                                               $flashMessage = \TYPO3\CMS\Core\Resource\Utility\BackendUtility::getFlashMessageForMissingFile($fileObject);
-                                                               $theData[$field] .= $flashMessage->render();
-                                                               // Thumbnails?
-                                                       } elseif ($this->thumbs && $this->isImage($ext)) {
-                                                               $processedFile = $fileObject->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, array());
-                                                               if ($processedFile) {
-                                                                       $thumbUrl = $processedFile->getPublicUrl(TRUE);
-                                                                       $theData[$field] .= '<br /><img src="' . $thumbUrl . '" title="' . htmlspecialchars($fileName) . '" alt="" />';
+                       // Initialization
+                       $this->counter++;
+                       $this->totalbytes += $fileObject->getSize();
+                       $ext = $fileObject->getExtension();
+                       $fileName = trim($fileObject->getName());
+                       // The icon with link
+                       $theIcon = IconUtility::getSpriteIconForResource($fileObject, array('title' => $fileName . ' [' . (int)$fileObject->getUid() . ']'));
+                       if ($this->clickMenus) {
+                               $theIcon = $GLOBALS['SOBE']->doc->wrapClickMenuOnIcon($theIcon, $fileObject->getCombinedIdentifier());
+                       }
+                       // Preparing and getting the data-array
+                       $theData = array();
+                       foreach ($this->fieldArray as $field) {
+                               switch ($field) {
+                                       case 'size':
+                                               $theData[$field] = GeneralUtility::formatSize($fileObject->getSize(), $this->getLanguageService()->getLL('byteSizeUnits', TRUE));
+                                               break;
+                                       case 'rw':
+                                               $theData[$field] = '' . (!$fileObject->checkActionPermission('read') ? ' ' : '<strong class="text-danger">' . $this->getLanguageService()->getLL('read', TRUE) . '</strong>') . (!$fileObject->checkActionPermission('write') ? '' : '<strong class="text-danger">' . $this->getLanguageService()->getLL('write', TRUE) . '</strong>');
+                                               break;
+                                       case 'fileext':
+                                               $theData[$field] = strtoupper($ext);
+                                               break;
+                                       case 'tstamp':
+                                               $theData[$field] = BackendUtility::date($fileObject->getModificationTime());
+                                               break;
+                                       case '_CONTROL_':
+                                               $theData[$field] = $this->makeEdit($fileObject);
+                                               break;
+                                       case '_CLIPBOARD_':
+                                               $theData[$field] = $this->makeClip($fileObject);
+                                               break;
+                                       case '_LOCALIZATION_':
+                                               if (!empty($systemLanguages) && $fileObject->isIndexed() && $fileObject->checkActionPermission('write') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
+                                                       $metaDataRecord = $fileObject->_getMetaData();
+                                                       $translations = $this->getTranslationsForMetaData($metaDataRecord);
+                                                       $languageCode = '';
+
+                                                       foreach ($systemLanguages as $language) {
+                                                               $languageId = $language['uid'];
+                                                               $flagIcon = $language['flagIcon'];
+                                                               if (array_key_exists($languageId, $translations)) {
+                                                                       $flagButtonIcon = IconUtility::getSpriteIcon(
+                                                                               'actions-document-open',
+                                                                               array('title' => sprintf($GLOBALS['LANG']->getLL('editMetadataForLanguage'), $language['title'])),
+                                                                               array($flagIcon . '-overlay' => array()));
+                                                                       $data = array(
+                                                                               'sys_file_metadata' => array($translations[$languageId]['uid'] => 'edit')
+                                                                       );
+                                                                       $editOnClick = BackendUtility::editOnClick(GeneralUtility::implodeArrayForUrl('edit', $data), $GLOBALS['BACK_PATH'], $this->listUrl());
+                                                                       $languageCode .= '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($editOnClick) . '">' . $flagButtonIcon . '</a>';
+                                                               } else {
+                                                                       $href = $GLOBALS['SOBE']->doc->issueCommand(
+                                                                               '&cmd[sys_file_metadata][' . $metaDataRecord['uid'] . '][localize]=' . $languageId,
+                                                                               $this->backPath . 'alt_doc.php?justLocalized=' . rawurlencode(('sys_file_metadata:' . $metaDataRecord['uid'] . ':' . $languageId)) .
+                                                                               '&returnUrl=' . rawurlencode($this->listURL()) . BackendUtility::getUrlToken('editRecord')
+                                                                       );
+                                                                       $flagButtonIcon = IconUtility::getSpriteIcon(
+                                                                               $flagIcon,
+                                                                               array('title' => sprintf($GLOBALS['LANG']->getLL('createMetadataForLanguage'), $language['title'])),
+                                                                               array($flagIcon . '-overlay' => array())
+                                                                       );
+                                                                       $languageCode .= '<a href="' . htmlspecialchars($href) . '" class="btn btn-default">' . $flagButtonIcon . '</a> ';
                                                                }
                                                        }
-                                                       break;
-                                               default:
-                                                       $theData[$field] = '';
-                                                       if ($fileObject->hasProperty($field)) {
-                                                               $theData[$field] = htmlspecialchars(GeneralUtility::fixed_lgd_cs($fileObject->getProperty($field), $this->fixedL));
+
+                                                       // Hide flag button bar when not translated yet
+                                                       $theData[$field] = ' <div class="localisationData btn-group" data-fileid="' . $fileObject->getUid() . '"' .
+                                                               (empty($translations) ? ' style="display: none;"' : '') . '>' . $languageCode . '</div>';
+                                                       $theData[$field] .= '<a class="btn btn-default filelist-translationToggler" data-fileid="' . $fileObject->getUid() . '">' .
+                                                               IconUtility::getSpriteIcon(
+                                                                       'mimetypes-x-content-page-language-overlay',
+                                                                       array(
+                                                                               'title' => $GLOBALS['LANG']->getLL('translateMetadata')
+                                                                       )
+                                                               ) . '</a>';
+                                               }
+                                               break;
+                                       case '_REF_':
+                                               $theData[$field] = $this->makeRef($fileObject);
+                                               break;
+                                       case 'file':
+                                               // Edit metadata of file
+                                               $theData[$field] = $this->linkWrapFile(htmlspecialchars($fileName), $fileObject);
+
+                                               if ($fileObject->isMissing()) {
+                                                       $flashMessage = \TYPO3\CMS\Core\Resource\Utility\BackendUtility::getFlashMessageForMissingFile($fileObject);
+                                                       $theData[$field] .= $flashMessage->render();
+                                                       // Thumbnails?
+                                               } elseif ($this->thumbs && $this->isImage($ext)) {
+                                                       $processedFile = $fileObject->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, array());
+                                                       if ($processedFile) {
+                                                               $thumbUrl = $processedFile->getPublicUrl(TRUE);
+                                                               $theData[$field] .= '<br /><img src="' . $thumbUrl . '" title="' . htmlspecialchars($fileName) . '" alt="" />';
                                                        }
-                                       }
+                                               }
+                                               break;
+                                       default:
+                                               $theData[$field] = '';
+                                               if ($fileObject->hasProperty($field)) {
+                                                       $theData[$field] = htmlspecialchars(GeneralUtility::fixed_lgd_cs($fileObject->getProperty($field), $this->fixedL));
+                                               }
                                }
-                               $out .= $this->addelement(1, $theIcon, $theData);
-
                        }
-                       $this->eCounter++;
+                       $out .= $this->addelement(1, $theIcon, $theData);
                }
                return $out;
        }
@@ -982,4 +964,4 @@ class FileList extends AbstractRecordList {
                return $GLOBALS['BE_USER'];
        }
 
-}
+}
\ No newline at end of file