[BUGFIX] Re-introduce read-only file mounts 85/30685/19
authorLorenz Ulrich <lorenz.ulrich@visol.ch>
Mon, 23 Jun 2014 13:41:17 +0000 (15:41 +0200)
committerHelmut Hummel <helmut.hummel@typo3.org>
Tue, 8 Jul 2014 09:31:40 +0000 (11:31 +0200)
This re-introduces the read-only file mounts that were silently dropped
in TYPO3 6.0.

File mounts can be added by User TSconfig the same way they were added
in TYPO3 4.x. But since FAL added Storages, the storage needs to be
configurable.

options.folderTree.altElementBrowserMountPoints = 3:/test

This adds the folder "test" of storage 3 as read-only mount in the File
List and the Element Browser. The old syntax is still supported:

options.folderTree.altElementBrowserMountPoints = documents

If no storage is configured, it is assumed that the folder is in the
default storage.

Resolves: #49391
Resolves: #57979
Documentation: #59648
Releases: 6.3, 6.2
Change-Id: I648c8ae15d4add98cd55e2f9c27d89d8e49d6152
Reviewed-on: https://review.typo3.org/30685
Reviewed-by: Markus Klein
Tested-by: Markus Klein
Reviewed-by: Helmut Hummel
Tested-by: Helmut Hummel
typo3/sysext/backend/Classes/Utility/IconUtility.php
typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
typo3/sysext/core/Classes/Resource/FolderInterface.php
typo3/sysext/core/Classes/Resource/ResourceStorage.php
typo3/sysext/extbase/Classes/Domain/Model/FileMount.php
typo3/sysext/recordlist/Classes/Controller/ElementBrowserController.php

index 7012cb2..2ec6f03 100644 (file)
@@ -747,37 +747,54 @@ class IconUtility {
        static public function getSpriteIconForResource(\TYPO3\CMS\Core\Resource\ResourceInterface $resource, array $options = array(), array $overlays = array()) {
                // Folder
                if ($resource instanceof \TYPO3\CMS\Core\Resource\FolderInterface) {
+                       $iconName = NULL;
+                       $role = $resource->getRole();
                        // non browsable storage
                        if ($resource->getStorage()->isBrowsable() === FALSE && !empty($options['mount-root'])) {
                                $iconName = 'apps-filetree-folder-locked';
-                       // storage root
-                       } elseif ($resource->getStorage()->getRootLevelFolder()->getIdentifier() === $resource->getIdentifier()) {
-                               $iconName = 'apps-filetree-root';
-                       // user/group mount root
-                       } elseif (!empty($options['mount-root'])) {
-                               $iconName = 'apps-filetree-mount';
                        } else {
+                               // storage root
+                               if ($resource->getStorage()->getRootLevelFolder()->getIdentifier() === $resource->getIdentifier()) {
+                                       $iconName = 'apps-filetree-root';
+                               }
 
-                               // in folder tree view $options['folder-open'] can define a open folder icon
-                               if (!empty($options['folder-open'])) {
-                                       $iconName = 'apps-filetree-folder-opened';
-                               } else {
-                                       $iconName = 'apps-filetree-folder-default';
+
+                               // user/group mount root
+                               if (!empty($options['mount-root'])) {
+                                       $iconName = 'apps-filetree-mount';
+                                       if ($role === \TYPO3\CMS\Core\Resource\FolderInterface::ROLE_READONLY_MOUNT) {
+                                               $overlays['status-overlay-locked'] = array();
+                                       } elseif ($role === \TYPO3\CMS\Core\Resource\FolderInterface::ROLE_USER_MOUNT) {
+                                               $overlays['status-overlay-access-restricted'] = array();
+                                       }
                                }
 
-                               $role = $resource->getRole();
-                               if ($role === \TYPO3\CMS\Core\Resource\FolderInterface::ROLE_TEMPORARY) {
-                                       $iconName = 'apps-filetree-folder-temp';
-                               } elseif ($role === \TYPO3\CMS\Core\Resource\FolderInterface::ROLE_RECYCLER) {
-                                       $iconName = 'apps-filetree-folder-recycler';
+                               if ($iconName === NULL) {
+                                       // in folder tree view $options['folder-open'] can define a open folder icon
+                                       if (!empty($options['folder-open'])) {
+                                               $iconName = 'apps-filetree-folder-opened';
+                                       } else {
+                                               $iconName = 'apps-filetree-folder-default';
+                                       }
+
+                                       if ($role === \TYPO3\CMS\Core\Resource\FolderInterface::ROLE_TEMPORARY) {
+                                               $iconName = 'apps-filetree-folder-temp';
+                                       } elseif ($role === \TYPO3\CMS\Core\Resource\FolderInterface::ROLE_RECYCLER) {
+                                               $iconName = 'apps-filetree-folder-recycler';
+                                       }
                                }
 
                                // if locked add overlay
-                               if ($resource instanceof \TYPO3\CMS\Core\Resource\InaccessibleFolder) {
+                               if ($resource instanceof \TYPO3\CMS\Core\Resource\InaccessibleFolder ||
+                                       !$resource->getStorage()->checkFolderActionPermission('add', $resource)
+                               ) {
                                        $overlays['status-overlay-locked'] = array();
                                }
                        }
-               // File
+
+
+
+                       // File
                } else {
                        $iconName = self::mapFileExtensionToSpriteIconName($resource->getExtension());
 
index e6efc4a..575fd22 100644 (file)
@@ -1607,74 +1607,122 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
         * @internal
         */
        public function getFileMountRecords() {
-               static $fileMountRecords = array();
+               static $fileMountRecordCache = array();
 
-               if (empty($fileMountRecords)) {
-                       // Processing filemounts (both from the user and the groups)
-                       $fileMounts = array_unique(GeneralUtility::intExplode(',', $this->dataLists['filemount_list'], TRUE));
+               if (!empty($fileMountRecordCache)) {
+                       return $fileMountRecordCache;
+               }
+
+               // Processing file mounts (both from the user and the groups)
+               $fileMounts = array_unique(GeneralUtility::intExplode(',', $this->dataLists['filemount_list'], TRUE));
 
-                       // Limit filemounts if set in workspace record
-                       if ($this->workspace > 0 && !empty($this->workspaceRec['file_mountpoints'])) {
-                               $workspaceFileMounts = GeneralUtility::intExplode(',', $this->workspaceRec['file_mountpoints'], TRUE);
-                               $fileMounts = array_intersect($fileMounts, $workspaceFileMounts);
+               // Limit file mounts if set in workspace record
+               if ($this->workspace > 0 && !empty($this->workspaceRec['file_mountpoints'])) {
+                       $workspaceFileMounts = GeneralUtility::intExplode(',', $this->workspaceRec['file_mountpoints'], TRUE);
+                       $fileMounts = array_intersect($fileMounts, $workspaceFileMounts);
+               }
+
+               if (!empty($fileMounts)) {
+                       $orderBy = isset($GLOBALS['TCA']['sys_filemounts']['ctrl']['default_sortby'])
+                               ? $this->db->stripOrderBy($GLOBALS['TCA']['sys_filemounts']['ctrl']['default_sortby'])
+                               : 'sorting';
+                       $fileMountRecords = $this->db->exec_SELECTgetRows(
+                               // Select read_only as (int)0, as there is no real database field for this.
+                               // Don't select as false as this is not supported by DBAL!
+                               '*,0 as read_only',
+                               'sys_filemounts',
+                               'deleted=0 AND hidden=0 AND pid=0 AND uid IN (' . implode(',', $fileMounts) . ')',
+                               '',
+                               $orderBy
+                       );
+                       foreach ($fileMountRecords as $fileMount) {
+                               $fileMountRecordCache[$fileMount['base'] . $fileMount['path']] = $fileMount;
                        }
+               }
 
-                       if (!empty($fileMounts)) {
-                               $orderBy = isset($GLOBALS['TCA']['sys_filemounts']['ctrl']['default_sortby'])
-                                       ? $this->db->stripOrderBy($GLOBALS['TCA']['sys_filemounts']['ctrl']['default_sortby'])
-                                       : 'sorting';
-                               $fileMountRecords = $this->db->exec_SELECTgetRows(
-                                       '*',
-                                       'sys_filemounts',
-                                       'deleted=0 AND hidden=0 AND pid=0 AND uid IN (' . implode(',', $fileMounts) . ')',
-                                       '',
-                                       $orderBy
+               // Read-only file mounts
+               $readOnlyMountPoints = trim($GLOBALS['BE_USER']->getTSConfigVal('options.folderTree.altElementBrowserMountPoints'));
+               if ($readOnlyMountPoints) {
+                       // We cannot use the API here but need to fetch the default storage record directly
+                       // to not instantiate it (which directly applies mount points) before all mount points are resolved!
+                       $whereClause = 'is_default=1 ' . BackendUtility::BEenableFields('sys_file_storage') . BackendUtility::deleteClause('sys_file_storage');
+                       $defaultStorageRow = $this->db->exec_SELECTgetSingleRow('uid', 'sys_file_storage', $whereClause);
+                       $readOnlyMountPointArray = GeneralUtility::trimExplode(',', $readOnlyMountPoints);
+                       foreach ($readOnlyMountPointArray as $readOnlyMountPoint) {
+                               $readOnlyMountPointConfiguration = GeneralUtility::trimExplode(':', $readOnlyMountPoint);
+                               if (count($readOnlyMountPointConfiguration) === 2) {
+                                       // A storage is passed in the configuration
+                                       $storageUid = (int)$readOnlyMountPointConfiguration[0];
+                                       $path = $readOnlyMountPointConfiguration[1];
+                               } else {
+                                       if (empty($defaultStorageRow)) {
+                                               throw new \RuntimeException('Read only mount points have been defined in User TsConfig without specific storage, but a default storage could not be resolved.', 1404472382);
+                                       }
+                                       // Backwards compatibility: If no storage is passed, we use the default storage
+                                       $storageUid = $defaultStorageRow['uid'];
+                                       $path = $readOnlyMountPointConfiguration[0];
+                               }
+                               $fileMountRecordCache[$storageUid . $path] = array(
+                                       'base' => $storageUid,
+                                       'title' => $path,
+                                       'path' => $path,
+                                       'read_only' => TRUE
                                );
                        }
+               }
 
-                       // Personal or Group filemounts are not accessible if file mount list is set in workspace record
-                       if ($this->workspace <= 0 || empty($this->workspaceRec['file_mountpoints'])) {
-                               // If userHomePath is set, we attempt to mount it
-                               if ($GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath']) {
-                                       list($userHomeStorageUid, $userHomeFilter) = explode(':', $GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath'], 2);
-                                       $userHomeStorageUid = (int)$userHomeStorageUid;
-                                       $userHomeFilter = '/' . ltrim($userHomeFilter, '/');
-                                       if ($userHomeStorageUid > 0) {
-                                               // Try and mount with [uid]_[username]
-                                               $fileMountRecords[] = array(
-                                                       'base' => $userHomeStorageUid,
-                                                       'title' => $this->user['username'],
-                                                       'path' => $userHomeFilter . $this->user['uid'] . '_' . $this->user['username'] . $GLOBALS['TYPO3_CONF_VARS']['BE']['userUploadDir']
-                                               );
-                                               // Try and mount with only [uid]
-                                               $fileMountRecords[] = array(
-                                                       'base' => $userHomeStorageUid,
-                                                       'title' => $this->user['username'],
-                                                       'path' => $userHomeFilter . $this->user['uid'] . $GLOBALS['TYPO3_CONF_VARS']['BE']['userUploadDir']
-                                               );
-                                       }
+
+               // Personal or Group filemounts are not accessible if file mount list is set in workspace record
+               if ($this->workspace <= 0 || empty($this->workspaceRec['file_mountpoints'])) {
+                       // If userHomePath is set, we attempt to mount it
+                       if ($GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath']) {
+                               list($userHomeStorageUid, $userHomeFilter) = explode(':', $GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath'], 2);
+                               $userHomeStorageUid = (int)$userHomeStorageUid;
+                               $userHomeFilter = '/' . ltrim($userHomeFilter, '/');
+                               if ($userHomeStorageUid > 0) {
+                                       // Try and mount with [uid]_[username]
+                                       $path = $userHomeFilter . $this->user['uid'] . '_' . $this->user['username'] . $GLOBALS['TYPO3_CONF_VARS']['BE']['userUploadDir'];
+                                       $fileMountRecordCache[$userHomeStorageUid . $path] = array(
+                                               'base' => $userHomeStorageUid,
+                                               'title' => $this->user['username'],
+                                               'path' => $path,
+                                               'read_only' => FALSE,
+                                               'user_mount' => TRUE
+                                       );
+                                       // Try and mount with only [uid]
+                                       $path = $userHomeFilter . $this->user['uid'] . $GLOBALS['TYPO3_CONF_VARS']['BE']['userUploadDir'];
+                                       $fileMountRecordCache[$userHomeStorageUid . $path] = array(
+                                               'base' => $userHomeStorageUid,
+                                               'title' => $this->user['username'],
+                                               'path' => $path,
+                                               'read_only' => FALSE,
+                                               'user_mount' => TRUE
+                                       );
                                }
+                       }
 
-                               // Mount group home-dirs
-                               if ((is_array($this->user) && $this->user['options'] & 2) == 2 && $GLOBALS['TYPO3_CONF_VARS']['BE']['groupHomePath'] != '') {
-                                       // If groupHomePath is set, we attempt to mount it
-                                       list($groupHomeStorageUid, $groupHomeFilter) = explode(':', $GLOBALS['TYPO3_CONF_VARS']['BE']['groupHomePath'], 2);
-                                       $groupHomeStorageUid = (int)$groupHomeStorageUid;
-                                       $groupHomeFilter = '/' . ltrim($groupHomeFilter, '/');
-                                       if ($groupHomeStorageUid > 0) {
-                                               foreach ($this->userGroups as $groupData) {
-                                                       $fileMountRecords[] = array(
-                                                               'base' => $groupHomeStorageUid,
-                                                               'title' => $groupData['title'],
-                                                               'path' => $groupHomeFilter . $groupData['uid']
-                                                       );
-                                               }
+                       // Mount group home-dirs
+                       if ((is_array($this->user) && $this->user['options'] & 2) == 2 && $GLOBALS['TYPO3_CONF_VARS']['BE']['groupHomePath'] != '') {
+                               // If groupHomePath is set, we attempt to mount it
+                               list($groupHomeStorageUid, $groupHomeFilter) = explode(':', $GLOBALS['TYPO3_CONF_VARS']['BE']['groupHomePath'], 2);
+                               $groupHomeStorageUid = (int)$groupHomeStorageUid;
+                               $groupHomeFilter = '/' . ltrim($groupHomeFilter, '/');
+                               if ($groupHomeStorageUid > 0) {
+                                       foreach ($this->userGroups as $groupData) {
+                                               $path = $groupHomeFilter . $groupData['uid'];
+                                               $fileMountRecordCache[$groupHomeStorageUid . $path] = array(
+                                                       'base' => $groupHomeStorageUid,
+                                                       'title' => $groupData['title'],
+                                                       'path' => $path,
+                                                       'read_only' => FALSE,
+                                                       'user_mount' => TRUE
+                                               );
                                        }
                                }
                        }
                }
 
-               return $fileMountRecords;
+               return $fileMountRecordCache;
        }
 
        /**
index 452756d..e3af41c 100644 (file)
@@ -28,6 +28,9 @@ interface FolderInterface extends ResourceInterface
        const ROLE_PROCESSING = 'processing';
        const ROLE_TEMPORARY = 'temporary';
        const ROLE_USERUPLOAD = 'userupload';
+       const ROLE_MOUNT = 'mount';
+       const ROLE_READONLY_MOUNT = 'readonly-mount';
+       const ROLE_USER_MOUNT = 'user-mount';
 
        /**
         * Returns a list of all subfolders
index 51f5165..7e406e8 100644 (file)
@@ -427,6 +427,16 @@ class ResourceStorage implements ResourceStorageInterface {
                }
                $data = $this->driver->getFolderInfoByIdentifier($folderIdentifier);
                $folderObject = ResourceFactory::getInstance()->createFolderObject($this, $data['identifier'], $data['name']);
+               // Use the canonical identifier instead of the user provided one!
+               $folderIdentifier = $folderObject->getIdentifier();
+               if (
+                       !empty($this->fileMounts[$folderIdentifier])
+                       && empty($this->fileMounts[$folderIdentifier]['read_only'])
+                       && !empty($additionalData['read_only'])
+               ) {
+                       // Do not overwrite a regular mount with a read only mount
+                       return;
+               }
                if (empty($additionalData)) {
                        $additionalData = array(
                                'path' => $folderIdentifier,
@@ -572,16 +582,28 @@ class ResourceStorage implements ResourceStorageInterface {
                        $isWriteCheck = TRUE;
                }
 
+               // Check 4: "Read-only" filemount
+               if ($isWriteCheck) {
+                       foreach ($this->fileMounts as $fileMount) {
+                               if ($this->driver->isWithin($fileMount['folder']->getIdentifier(), $file->getIdentifier())) {
+                                       if (!empty($fileMount['read_only'])) {
+                                               return FALSE;
+                                       }
+                                       break;
+                               }
+                       }
+               }
+
                $isMissing = FALSE;
                if (!$isProcessedFile && $file instanceof File) {
                        $isMissing = $file->isMissing();
                }
 
-               // Check 4: Check the capabilities of the storage (and the driver)
+               // Check 5: Check the capabilities of the storage (and the driver)
                if ($isWriteCheck && ($isMissing || !$this->isWritable())) {
                        return FALSE;
                }
-               // Check 5: "File permissions" of the driver (only when file isn't marked as missing)
+               // Check 6: "File permissions" of the driver (only when file isn't marked as missing)
                if (!$isMissing) {
                        $filePermissions = $this->driver->getPermissions($file->getIdentifier());
                        if ($isReadCheck && !$filePermissions['r']) {
@@ -635,7 +657,20 @@ class ResourceStorage implements ResourceStorageInterface {
                if ($isWriteCheck && !$this->isWritable()) {
                        return FALSE;
                }
-               // Check 4: "Folder permissions" of the driver
+
+               // Check 4: "Read-only" filemount
+               if ($isWriteCheck) {
+                       foreach ($this->fileMounts as $fileMount) {
+                               if ($this->driver->isWithin($fileMount['folder']->getIdentifier(), $folder->getIdentifier())) {
+                                       if (!empty($fileMount['read_only'])) {
+                                               return FALSE;
+                                       }
+                                       break;
+                               }
+                       }
+               }
+
+               // Check 5: "Folder permissions" of the driver
                $folderPermissions = $this->driver->getPermissions($folder->getIdentifier());
                if ($isReadCheck && !$folderPermissions['r']) {
                        return FALSE;
@@ -2458,12 +2493,22 @@ class ResourceStorage implements ResourceStorageInterface {
         */
        public function getRole(FolderInterface $folder) {
                $folderRole = FolderInterface::ROLE_DEFAULT;
-
+               $identifier = $folder->getIdentifier();
                if (method_exists($this->driver, 'getRole')) {
                        $folderRole = $this->driver->getRole($folder->getIdentifier());
                }
+               if (isset($this->fileMounts[$identifier])) {
+                       $folderRole = FolderInterface::ROLE_MOUNT;
+
+                       if (!empty($this->fileMounts[$identifier]['read_only'])) {
+                               $folderRole = FolderInterface::ROLE_READONLY_MOUNT;
+                       }
+                       if ($this->fileMounts[$identifier]['user_mount']) {
+                               $folderRole = FolderInterface::ROLE_USER_MOUNT;
+                       }
+               }
 
-               if ($folder->getIdentifier() === $this->getProcessingFolder()->getIdentifier()) {
+               if ($identifier === $this->getProcessingFolder()->getIdentifier()) {
                        $folderRole = FolderInterface::ROLE_PROCESSING;
                }
 
index 19c60ac..3dfb4fe 100644 (file)
@@ -103,4 +103,13 @@ class FileMount extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {
        public function setIsAbsolutePath($value) {
                $this->isAbsolutePath = $value;
        }
+
+       /**
+        * Getter for the virtual field read_only
+        *
+        * @return bool
+        */
+       public function isReadOnly() {
+               return FALSE;
+       }
 }
index 01a6f9b..6b59225 100644 (file)
@@ -115,18 +115,6 @@ class ElementBrowserController {
                                        $GLOBALS['BE_USER']->groupData['webmounts'] = implode(',', array_unique(\TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $altMountPoints)));
                                        $GLOBALS['WEBMOUNTS'] = $GLOBALS['BE_USER']->returnWebmounts();
                                }
-                       case 'file':
-                       case 'filedrag':
-                       case 'folder':
-                               // Setting additional read-only browsing file mounts
-                               $altMountPoints = trim($GLOBALS['BE_USER']->getTSConfigVal('options.folderTree.altElementBrowserMountPoints'));
-                               if ($altMountPoints) {
-                                       $altMountPoints = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $altMountPoints);
-                                       foreach ($altMountPoints as $filePathRelativeToFileadmindir) {
-                                               // @todo: add this feature for FAL and TYPO3 6.2
-                                       }
-                               }
-                               break;
                }
                // Render type by user func
                $browserRendered = FALSE;