From 2dc2d908f7e6dd63916a5acdf8ad8667b47ab331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Gro=C3=9Fberndt?= Date: Thu, 15 Jun 2023 10:44:40 +0200 Subject: [PATCH] Compatibility to TYPO3 v12 (#210) * [DOCS] Update README.md * Raise compatibility to TYPO3 v12 * Use executeQuery() or executeStatement() instead of execute() * Use use-statements in Configuration/ * Add .editorconfig * Code cleanup * Remove dynamicReturnTypeMeta.json as the plugin is outdated and neither usable nor necessary. * Remove BE-mode check from ext_localconf.php. This is not needed and breaks in v12 * "use" statements in ext_localconf.php * Add TODO for EventDispatcher for EXT:solrfal for v12 * Update Aspects * parameter types for properties and method arguments * copyright * declare(strict_types=1) * order declare/copyright/namespaces * remove PHPDoc if only type hints or method name * Update ExtensionConfiguration * Declare correct type for Context\UserAspect::$user * Update ItemProvider * documentation of constructor arguments for v11 * parameter type for properties * PHPDoc * Update BePublicUrlController * BePublicUrlController::$resourceFactory only provided by Dependency Injection * BePublicUrlController::$processedFileRepository provided by Dependency Injection * Make BePublicUrlController::dumpFile() more resilient * catch FileDoesNotExistException for 'f'-file and respond with 404 * respond with 404 for deleted or missing 'f'-file like changed for 'p' in #102 * catch \RuntimeException for 'p'-file and respond with 404. This prevents null pointer for $orgFile->getStorage() * Update FileTreeController * FileTreeController::$resourceFactory only provided by Dependency Injection * Error message instead of empty response for missing storage * Update FileTreeStateController * add @throws declarations * Constructor arguments of FileTreeStateController only provided by Dependency Injection * Remove unused class Domain/Repository/ProcessedFileRepository * Update FolderChangedEventListener * added registration information and added @noinspection PHPUnused * Add FolderChangedEventListener::updateFolderPermissions() * Extracted duplicate code to new method * Update GeneratePublicUrlForResourceEventListener * added registration information and @noinspection PHPUnused * Update ModifyIconForResourcePropertiesEventListener * change list() to short array syntax * Update Events * added @noinspection PHPUnused * Update DownloadStatistics * added @throws * Update AbstractBeButtons and DocHeaderButtonsHook * parameter types for properties and methods * Update CmsLayout * CmsLayout::$resourceFactory only provided by Dependency Injection * Update FileDumpHook * catch UnknownLinkHandlerException and add error message * Update KeSearchFilesHook * Update ProcessDatamapHook * parameter types for methods * Update EidFrontendAuthentication * Remove DocHeaderButtonsHook::addFolderPermissionsButton(). Used in TYPO3 <= v7 and usage removed in a6f079bc Updated extension to fully support TYPO3 8.7 and removed old below TYPO3 7.6 support. * Update CheckPermissions * switch from Folder to FolderInterface as not necessary * replace PHP8 str_contains() with strpos() * Update LeafStateService * LeafStateService::$resourceFactory only provided by Dependency Injection * Update UserFileMountService * Update Utility * Update ViewHelpers * add PHPDoc type hints * Rename TypoScript files from *.txt to *.typoscript * Fix typos in locallang_be.xlf * Formatting of HTML files, fluid namespace declaration * Update CollapsibleFolderTree.js * Fix typo * Fix formatting * Reuse jQuery selector for folders * Update SVG files * Update .editorconfig and format accordingly * Remove ext_icon.png. Available in Resources/Public/Icons/Extension.png https://docs.typo3.org/c/typo3/cms-core/11.5/en-us/Changelog/8.3/Feature-77349-AdditionalLocationsForExtensionIcons.html?highlight=ext_icon * Move icon registration to ext_localconf.php. Should always have been there: https://docs.typo3.org/c/typo3/cms-core/12.4/en-us/Changelog/7.5/Feature-68741-IntroduceNewIconFactoryAsBaseForReplaceTheIconSkinningAPI.html * Update ext_tables.sql. Database management fields are generated automatically: https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/9.3/Feature-85160-AutoCreateManagementDBFieldsFromTCACtrl.html?highlight=85160 * [BUGFIX] Catch FolderDoesNotExistException. $file->getParentFolder() may throw a FolderDoesNotExistException which currently is not part of PHPDoc in TYPO3 core. This may lead to this exception being thrown in the backend in case of an offline protected storage instead of displaying the file tree. This change makes sure the file tree is shown by catching the exception. * Context menu using ES6 module in TYPO3 v12. Adding a context menu using RequireJS does only work in TYPO3 v11 but no longer in v12, even if the documentation hints it should: https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/ApiOverview/Backend/JavaScript/ES6/Index.html#migration-from-requirejs * Remove ['TYPO3_CONF_VARS']['EXTCONF'] usage from FileDumpHook. Using this is removed since TYPO3 v10 * Migrate Hooks\FileDumpHook to EventListener\ModifyFileDumpEventListener https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/Events/Core/Resource/ModifyFileDumpEvent.html * Remove comparison to EXT:naw_securedl from documentation. EXT:naw_securedl is outdated and not relevant anymore * IconFactoryAspect uses constructor and SingletonInterface * Use UserFileMountService of core. Instead of using an outdated copy of the UserFileMountService from core simply override the config and use the core service. The UserFileMountService is deprecated in v12 and will be removed in v13, but currently selection of a storage root folder is not possible from the element browser, therefore migration to type=folder is not an option currently. https://forge.typo3.org/issues/100789 * Remove unnecessary option to select storage. As displayCond already makes sure a storage is selected, the dropdown item in the folder selection is not needed. * Add typoscript option to disable javascript include * Rename event variables and remove type hints * Remove unused method CheckPermissions::getBackendUser() * Pass request to UserAuthentication. This is required for TYPO3 v12 * Avoid TCA migrations in TYPO3 v12 * Replace strpos() with str_contains(). As TYPO3 v11 uses a polyfill this is possible also with PHP 7.4 * Use FileInterface for ModifyFileDumpEventListener::dumpFileContents(). The object to be dumped may also be a ProcessedFile. --- .editorconfig | 55 +++++ Classes/Aspects/IconFactoryAspect.php | 74 ++++--- Classes/Aspects/PublicUrlAspect.php | 59 +++--- Classes/Aspects/SolrFalAspect.php | 28 +-- .../Configuration/ExtensionConfiguration.php | 55 ++--- Classes/Context/UserAspect.php | 15 +- Classes/ContextMenu/ItemProvider.php | 42 ++-- Classes/Controller/BePublicUrlController.php | 67 +++--- Classes/Controller/FileTreeController.php | 28 +-- .../Controller/FileTreeStateController.php | 39 ++-- .../Repository/ProcessedFileRepository.php | 67 ------ .../FolderChangedEventListener.php | 88 ++++---- ...eratePublicUrlForResourceEventListener.php | 27 ++- .../ModifyFileDumpEventListener.php} | 195 ++++++++---------- ...IconForResourcePropertiesEventListener.php | 15 +- Classes/Events/AddCustomGroupsEvent.php | 11 +- Classes/Events/BeforeFileDumpEvent.php | 26 ++- Classes/Events/BeforeRedirectsEvent.php | 28 ++- Classes/FormEngine/DownloadStatistics.php | 70 ++++--- Classes/Hooks/AbstractBeButtons.php | 86 ++++---- Classes/Hooks/CmsLayout.php | 56 +++-- Classes/Hooks/DocHeaderButtonsHook.php | 48 ++--- Classes/Hooks/KeSearchFilesHook.php | 51 ++--- Classes/Hooks/ProcessDatamapHook.php | 34 +-- .../Middleware/EidFrontendAuthentication.php | 29 +-- Classes/Security/CheckPermissions.php | 154 ++++++-------- Classes/Service/LeafStateService.php | 34 ++- Classes/Service/UserFileMountService.php | 79 ++----- Classes/Service/Utility.php | 109 ++++++---- .../ViewHelpers/DownloadLinkViewHelper.php | 13 +- Classes/ViewHelpers/LeaveStateViewHelper.php | 18 +- .../Security/AssetAccessViewHelper.php | 33 ++- Configuration/Backend/AjaxRoutes.php | 5 +- Configuration/FlexForms/FileTree.xml | 114 +++++----- Configuration/JavaScriptModules.php | 14 ++ Configuration/RequestMiddlewares.php | 4 +- Configuration/Services.yaml | 5 + Configuration/TCA/Overrides/fe_users.php | 10 +- .../TCA/Overrides/sys_file_metadata.php | 23 ++- Configuration/TCA/Overrides/sys_template.php | 5 +- Configuration/TCA/Overrides/tt_content.php | 8 +- .../TCA/tx_falsecuredownload_folder.php | 22 +- Configuration/TypoScript/constants.txt | 11 - Configuration/TypoScript/constants.typoscript | 15 ++ Configuration/TypoScript/setup.txt | 11 - Configuration/TypoScript/setup.typoscript | 12 ++ Documentation/Misc/Index.rst | 143 +++++++------ Documentation/Settings.cfg | 20 +- README.md | 18 +- Resources/Private/Language/locallang.xlf | 5 +- Resources/Private/Language/locallang_be.xlf | 14 +- Resources/Private/Language/locallang_db.xlf | 5 +- Resources/Private/Layouts/Default.html | 20 +- Resources/Private/Partials/FileTree/Leaf.html | 35 ++-- .../Private/Templates/FileTree/Tree.html | 32 +-- Resources/Public/Icons/folder.svg | 77 +++---- .../Icons/overlay-inherited-permissions.svg | 112 +++++----- .../JavaScript/CollapsibleFolderTree.js | 45 ++-- .../Public/JavaScript/ContextMenuActions.js | 74 +++---- .../Public/JavaScript/context-menu-actions.js | 32 +++ composer.json | 83 ++++---- dynamicReturnTypeMeta.json | 47 ----- ext_emconf.php | 2 +- ext_icon.png | Bin 720 -> 0 bytes ext_localconf.php | 123 ++++++----- ext_tables.php | 19 -- ext_tables.sql | 34 +-- 67 files changed, 1438 insertions(+), 1494 deletions(-) create mode 100644 .editorconfig delete mode 100644 Classes/Domain/Repository/ProcessedFileRepository.php rename Classes/{Hooks/FileDumpHook.php => EventListener/ModifyFileDumpEventListener.php} (73%) create mode 100644 Configuration/JavaScriptModules.php delete mode 100644 Configuration/TypoScript/constants.txt create mode 100644 Configuration/TypoScript/constants.typoscript delete mode 100644 Configuration/TypoScript/setup.txt create mode 100644 Configuration/TypoScript/setup.typoscript create mode 100644 Resources/Public/JavaScript/context-menu-actions.js delete mode 100644 dynamicReturnTypeMeta.json delete mode 100644 ext_icon.png delete mode 100644 ext_tables.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a3a3176 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,55 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +# TS/JS-Files +[*.{ts,js}] +indent_size = 2 + +# JSON-Files +[*.json] +indent_style = tab + +# ReST-Files +[*.{rst,rst.txt}] +indent_size = 4 +max_line_length = 80 + +# Markdown-Files +[*.md] +max_line_length = 80 + +# YAML-Files +[*.{yaml,yml}] +indent_size = 2 + +# JSON-Files +[composer.json] +indent_style = space + +# TypoScript +[*.{typoscript,tsconfig}] +indent_size = 2 + +# XLF-Files +[*.xlf] +indent_style = tab + +# SQL-Files +[*.sql] +indent_style = tab +indent_size = 2 + +# .htaccess +[{_.htaccess,.htaccess}] +indent_style = tab diff --git a/Classes/Aspects/IconFactoryAspect.php b/Classes/Aspects/IconFactoryAspect.php index b18f4e0..bf2146a 100644 --- a/Classes/Aspects/IconFactoryAspect.php +++ b/Classes/Aspects/IconFactoryAspect.php @@ -1,7 +1,8 @@ @@ -22,57 +23,62 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\Aspects; use BeechIt\FalSecuredownload\Security\CheckPermissions; +use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException; use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\Folder; use TYPO3\CMS\Core\Resource\ResourceInterface; +use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; -/** - * Class IconFactoryAspect - */ -class IconFactoryAspect +class IconFactoryAspect implements SingletonInterface { - /** - * @param ResourceInterface $resource - * @param string $size - * @param array $options - * @param string $iconIdentifier - * @param string $overlayIdentifier - * @return array - */ + private CheckPermissions $checkPermissions; + + public function __construct() + { + $this->checkPermissions = GeneralUtility::makeInstance(CheckPermissions::class);; + } + public function buildIconForResource( ResourceInterface $resource, - $size, + string $size, array $options, - $iconIdentifier, - $overlayIdentifier - ) { - if (!$resource->getStorage()->isPublic()) { - /** @var $checkPermissionsService CheckPermissions */ - $checkPermissionsService = GeneralUtility::makeInstance(CheckPermissions::class); + string $iconIdentifier, + ?string $overlayIdentifier + ): array + { + $storage = $resource->getStorage(); + if (!$storage->isPublic()) { + + $currentPermissionsCheck = $storage->getEvaluatePermissions(); + $storage->setEvaluatePermissions(false); - $currentPermissionsCheck = $resource->getStorage()->getEvaluatePermissions(); - $resource->getStorage()->setEvaluatePermissions(false); + try { + $folder = $resource instanceof Folder ? $resource : $resource->getParentFolder(); - $folder = $resource instanceof Folder ? $resource : $resource->getParentFolder(); + if ($resource instanceof File && $resource->getProperty('fe_groups')) { + $overlayIdentifier = 'overlay-restricted'; - if ($resource instanceof File && $resource->getProperty('fe_groups')) { - $overlayIdentifier = 'overlay-restricted'; + // check if there are permissions set on this specific folder + } elseif ($folder === $resource && $this->checkPermissions->getFolderPermissions($folder) !== false) { + $overlayIdentifier = 'overlay-restricted'; - // check if there are permissions set on this specific folder - } elseif ($folder === $resource && $checkPermissionsService->getFolderPermissions($folder) !== false) { - $overlayIdentifier = 'overlay-restricted'; + // check if there are access restrictions in the root line of this folder + } elseif (!$this->checkPermissions->checkFolderRootLineAccess($folder, false)) { + $overlayIdentifier = 'overlay-inherited-permissions'; + } - // check if there are access restrictions in the root line of this folder - } elseif (!$checkPermissionsService->checkFolderRootLineAccess($folder, false)) { - $overlayIdentifier = 'overlay-inherited-permissions'; + } catch (FolderDoesNotExistException $e) { + // $resource->getParentFolder() may throw a FolderDoesNotExistException which currently is not documented in PHPDoc } - $resource->getStorage()->setEvaluatePermissions($currentPermissionsCheck); + $storage->setEvaluatePermissions($currentPermissionsCheck); } return [$resource, $size, $options, $iconIdentifier, $overlayIdentifier]; } diff --git a/Classes/Aspects/PublicUrlAspect.php b/Classes/Aspects/PublicUrlAspect.php index 4380867..f0a02e1 100644 --- a/Classes/Aspects/PublicUrlAspect.php +++ b/Classes/Aspects/PublicUrlAspect.php @@ -1,7 +1,8 @@ @@ -22,47 +23,35 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ -use TYPO3\CMS\Core\Resource\ResourceStorage; + */ + +namespace BeechIt\FalSecuredownload\Aspects; + +use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException; +use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Core\Resource\Driver\DriverInterface; -use TYPO3\CMS\Core\Resource\ResourceInterface; -use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\File; +use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\ProcessedFile; -use TYPO3\CMS\Backend\Routing\UriBuilder; -use TYPO3\CMS\Core\Resource; +use TYPO3\CMS\Core\Resource\ResourceInterface; +use TYPO3\CMS\Core\Resource\ResourceStorage; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; -/** - * Class PublicUrlAspect - */ class PublicUrlAspect implements SingletonInterface { /** * Flag to en-/disable rendering of BE user link instead of FE link - * - * @var bool */ - protected $enabled = true; + protected bool $enabled = true; - /** - * Get enabled - * - * @return bool - */ - public function getEnabled() + public function getEnabled(): bool { return $this->enabled; } - /** - * Set enabled - * - * @param bool $enabled - */ - public function setEnabled($enabled) + public function setEnabled(bool $enabled): void { $this->enabled = $enabled; } @@ -70,11 +59,12 @@ public function setEnabled($enabled) /** * Generate public url for file * - * @param Resource\ResourceStorage $storage - * @param Resource\Driver\DriverInterface $driver - * @param Resource\ResourceInterface $resourceObject + * @param ResourceStorage $storage + * @param DriverInterface $driver + * @param ResourceInterface $resourceObject * @param mixed $relativeToCurrentScript Deprecated. Will be removed in a future version * @param array $urlData + * @throws RouteNotFoundException */ public function generatePublicUrl( ResourceStorage $storage, @@ -82,7 +72,8 @@ public function generatePublicUrl( ResourceInterface $resourceObject, $relativeToCurrentScript, array $urlData - ) { + ): void + { // We only render special links for non-public files if ($this->enabled && $resourceObject instanceof FileInterface && !$storage->isPublic()) { @@ -99,8 +90,14 @@ public function generatePublicUrl( 'BeResourceStorageDumpFile' ); - // $urlData['publicUrl'] is passed by reference, so we can change that here and the value will be taken into account + /** @var UriBuilder $uriBuilder */ $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); + + /** + * $urlData['publicUrl'] is passed by reference, so we can change that here and the value will be taken into account + * @noinspection PhpArrayWriteIsNotUsedInspection + * @noinspection PhpArrayUsedOnlyForWriteInspection + */ $urlData['publicUrl'] = (string)$uriBuilder->buildUriFromRoute( 'ajax_dump_file', $queryParameterArray, diff --git a/Classes/Aspects/SolrFalAspect.php b/Classes/Aspects/SolrFalAspect.php index 44f4b36..c7a07a0 100644 --- a/Classes/Aspects/SolrFalAspect.php +++ b/Classes/Aspects/SolrFalAspect.php @@ -1,39 +1,29 @@ checkPermissionsService = GeneralUtility::makeInstance(CheckPermissions::class); @@ -44,9 +34,9 @@ public function __construct() * Add correct fe_group info and public_url * * @param Item $item - * @param \ArrayObject $metadata + * @param ArrayObject $metadata */ - public function fileMetaDataRetrieved(Item $item, \ArrayObject $metadata) + public function fileMetaDataRetrieved(Item $item, ArrayObject $metadata): void { if ($item->getFile() instanceof File && !$item->getFile()->getStorage()->isPublic()) { $resourcePermissions = $this->checkPermissionsService->getPermissions($item->getFile()); diff --git a/Classes/Configuration/ExtensionConfiguration.php b/Classes/Configuration/ExtensionConfiguration.php index 2492237..0bc77e9 100644 --- a/Classes/Configuration/ExtensionConfiguration.php +++ b/Classes/Configuration/ExtensionConfiguration.php @@ -1,6 +1,8 @@ @@ -21,7 +23,7 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ namespace BeechIt\FalSecuredownload\Configuration; @@ -33,15 +35,15 @@ */ class ExtensionConfiguration { - private static $isInitialized = false; - private static $loginRedirectUrl = ''; - private static $noAccessRedirectUrl = ''; - private static $forceDownload = false; - private static $forceDownloadForExt = ''; - private static $trackDownloads = false; - private static $resumableDownload = true; + private static bool $isInitialized = false; + private static string $loginRedirectUrl = ''; + private static string $noAccessRedirectUrl = ''; + private static bool $forceDownload = false; + private static string $forceDownloadForExt = ''; + private static bool $trackDownloads = false; + private static bool $resumableDownload = true; - private static function init() + private static function init(): void { if (!self::$isInitialized) { self::$isInitialized = true; @@ -51,41 +53,29 @@ private static function init() self::$forceDownload = (bool)$extensionConfig['force_download']; self::$forceDownloadForExt = $extensionConfig['force_download_for_ext']; self::$trackDownloads = (bool)$extensionConfig['track_downloads']; - self::$resumableDownload = (isset($extensionConfig['resumable_download']) ? (bool)$extensionConfig['resumable_download'] : false); + self::$resumableDownload = isset($extensionConfig['resumable_download']) && $extensionConfig['resumable_download']; } } - /** - * @return string - */ - public static function loginRedirectUrl() + public static function loginRedirectUrl(): string { self::init(); return self::$loginRedirectUrl; } - /** - * @return string - */ - public static function noAccessRedirectUrl() + public static function noAccessRedirectUrl(): string { self::init(); return self::$noAccessRedirectUrl; } - /** - * @return bool - */ - public static function forceDownload() + public static function forceDownload(): bool { self::init(); return self::$forceDownload; } - /** - * @return string - */ - public static function forceDownloadForExt() + public static function forceDownloadForExt(): string { self::init(); return self::$forceDownloadForExt; @@ -93,21 +83,16 @@ public static function forceDownloadForExt() /** * Track user downloads - * - * @return bool */ - public static function trackDownloads() + public static function trackDownloads(): bool { self::init(); return self::$trackDownloads; } - /** - * @return bool - */ - public static function resumableDownload() + public static function resumableDownload(): bool { self::init(); return self::$resumableDownload; } -} \ No newline at end of file +} diff --git a/Classes/Context/UserAspect.php b/Classes/Context/UserAspect.php index 8df9ab9..e6698ac 100644 --- a/Classes/Context/UserAspect.php +++ b/Classes/Context/UserAspect.php @@ -1,6 +1,6 @@ user = []; return $user; } @@ -55,14 +55,13 @@ private function createPseudoUser(): object * Fetch common information about the user * * @param string $name - * @return int|bool|string|array + * @return AbstractUserAuthentication|stdClass * @throws AspectPropertyNotFoundException */ public function get(string $name) { - switch ($name) { - case 'user': - return $this->user; + if ($name === 'user') { + return $this->user; } throw new AspectPropertyNotFoundException('Property "' . $name . '" not found in Aspect "' . __CLASS__ . '".', 1597220199); } diff --git a/Classes/ContextMenu/ItemProvider.php b/Classes/ContextMenu/ItemProvider.php index d96611c..8278a3b 100644 --- a/Classes/ContextMenu/ItemProvider.php +++ b/Classes/ContextMenu/ItemProvider.php @@ -1,6 +1,6 @@ resourceFactory = $resourceFactory ?? GeneralUtility::makeInstance(ResourceFactory::class); parent::__construct($table, $identifier, $context); } - /** - * @var Folder - */ - protected $folder; - public function getPriority(): int { return 90; @@ -49,8 +51,10 @@ public function canHandle(): bool /** * Initialize file object + * + * @throws ResourceDoesNotExistException */ - protected function initialize() + protected function initialize(): void { parent::initialize(); $resource = $this->resourceFactory @@ -70,6 +74,8 @@ protected function initialize() /** * Adds the folder permission menu item for folder of a non-public storage + * + * @throws ResourceDoesNotExistException */ public function addItems(array $items): array { @@ -96,8 +102,16 @@ protected function getAdditionalAttributes(string $itemName): array $utility = GeneralUtility::makeInstance(Utility::class); $folderRecord = $utility->getFolderRecord($this->folder); + $typo3Version = new Typo3Version(); + if ($typo3Version->getMajorVersion() > 11) { + $dataCallbackModule = '@beechit/fal-securedownload/context-menu-actions'; + } else { + // keep RequireJs for TYPO3 below v12.0 + $dataCallbackModule = 'TYPO3/CMS/FalSecuredownload/ContextMenuActions'; + } + return [ - 'data-callback-module' => 'TYPO3/CMS/FalSecuredownload/ContextMenuActions', + 'data-callback-module' => $dataCallbackModule, 'data-folder-record-uid' => $folderRecord['uid'] ?? 0, 'data-storage' => $this->folder->getStorage()->getUid(), 'data-folder' => $this->folder->getIdentifier(), diff --git a/Classes/Controller/BePublicUrlController.php b/Classes/Controller/BePublicUrlController.php index 8528a83..d1f24c4 100644 --- a/Classes/Controller/BePublicUrlController.php +++ b/Classes/Controller/BePublicUrlController.php @@ -1,50 +1,49 @@ resourceFactory = $resourceFactory ?? GeneralUtility::makeInstance(ResourceFactory::class); + $this->resourceFactory = $resourceFactory; $this->responseFactory = $responseFactory; + $this->processedFileRepository = $processedFileRepository; } /** * Dump file content - * @return void */ - public function dumpFile() + public function dumpFile(): ResponseInterface { $parameters = ['eID' => 'dumpFile']; if (GeneralUtility::_GP('t')) { @@ -57,20 +56,28 @@ public function dumpFile() $parameters['p'] = (int)GeneralUtility::_GP('p'); } - if (GeneralUtility::hmac( - implode('|', $parameters), - 'BeResourceStorageDumpFile' - ) === GeneralUtility::_GP('fal_token') + if ( + GeneralUtility::hmac( + implode('|', $parameters), 'BeResourceStorageDumpFile' + ) === GeneralUtility::_GP('fal_token') ) { if (isset($parameters['f'])) { - $file = $this->resourceFactory->getFileObject($parameters['f']); + try { + $file = $this->resourceFactory->getFileObject($parameters['f']); + } catch (FileDoesNotExistException $e) { + return $this->responseFactory->createResponse(404); + } if ($file->isDeleted() || $file->isMissing()) { - $file = null; + return $this->responseFactory->createResponse(404); } $orgFile = $file; } else { - /** @var ProcessedFile $file */ - $file = GeneralUtility::makeInstance(ProcessedFileRepository::class)->findByUid($parameters['p']); + try { + /** @var ProcessedFile $file */ + $file = $this->processedFileRepository->findByUid($parameters['p']); + } catch (RuntimeException $e) { + return $this->responseFactory->createResponse(404); + } if ($file->isDeleted()) { return $this->responseFactory->createResponse(404); } @@ -82,10 +89,6 @@ public function dumpFile() return $this->responseFactory->createResponse(403); } - if ($file === null) { - return $this->responseFactory->createResponse(404);; - } - ob_start(); $response = $file->getStorage()->streamFile($file); diff --git a/Classes/Controller/FileTreeController.php b/Classes/Controller/FileTreeController.php index 0731f86..e3d50a4 100644 --- a/Classes/Controller/FileTreeController.php +++ b/Classes/Controller/FileTreeController.php @@ -1,7 +1,8 @@ @@ -22,26 +23,23 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\Controller; + use Psr\Http\Message\ResponseInterface; use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException; use TYPO3\CMS\Core\Resource\ResourceFactory; -use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; -/** - * FileTreeController - */ class FileTreeController extends ActionController { - /** - * @var ResourceFactory - */ - protected $resourceFactory; - public function __construct(ResourceFactory $resourceFactory = null) + protected ResourceFactory $resourceFactory; + + public function __construct(ResourceFactory $resourceFactory) { - $this->resourceFactory = $resourceFactory ?? GeneralUtility::makeInstance(ResourceFactory::class); + $this->resourceFactory = $resourceFactory; } /** @@ -50,8 +48,9 @@ public function __construct(ResourceFactory $resourceFactory = null) public function treeAction(): ResponseInterface { if ($this->settings['storage'] === '') { - return $this->htmlResponse(null); + return $this->htmlResponse('Storage is not configured'); } + try { $folder = $this->resourceFactory->getFolderObjectFromCombinedIdentifier($this->settings['storage'] . ':' . $this->settings['folder']); } catch (FolderDoesNotExistException $exception) { @@ -60,6 +59,7 @@ public function treeAction(): ResponseInterface } $this->view->assign('folder', $folder); + return $this->htmlResponse(); } } diff --git a/Classes/Controller/FileTreeStateController.php b/Classes/Controller/FileTreeStateController.php index 1ff6237..554d398 100644 --- a/Classes/Controller/FileTreeStateController.php +++ b/Classes/Controller/FileTreeStateController.php @@ -1,8 +1,8 @@ @@ -23,46 +23,45 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\Controller; use BeechIt\FalSecuredownload\Service\LeafStateService; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use TYPO3\CMS\Core\Context\Context; +use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException; +use TYPO3\CMS\Core\Context\Exception\AspectPropertyNotFoundException; use TYPO3\CMS\Core\Exception; use TYPO3\CMS\Core\Http\JsonResponse; use TYPO3\CMS\Core\Http\Response; use TYPO3\CMS\Core\Utility\GeneralUtility; -/** - * FileTreeStateController - */ class FileTreeStateController { - /** - * @var object|Context - */ - protected $context; - /** - * @var LeafStateService|object - */ - protected $leafStateService; + protected Context $context; + protected LeafStateService $leafStateService; - public function __construct(Context $context = null, LeafStateService $leafStateService = null) + public function __construct(Context $context, LeafStateService $leafStateService) { - $this->context = $context ?? GeneralUtility::makeInstance(Context::class); - $this->leafStateService = $leafStateService ?? GeneralUtility::makeInstance(LeafStateService::class); + $this->context = $context; + $this->leafStateService = $leafStateService; } /** * Saves the current Leaf state of a user * + * defined as eID=FalSecuredownloadFileTreeState in ext_localconf.php + * * @param ServerRequestInterface $request - * @param RequestHandlerInterface $handler * @return ResponseInterface - * @throws Exception + * @throws AspectNotFoundException + * @throws AspectPropertyNotFoundException + * @see EidFrontendAuthentication::process() + * @noinspection PhpUnused */ public function saveLeafState(ServerRequestInterface $request): ResponseInterface { @@ -70,9 +69,11 @@ public function saveLeafState(ServerRequestInterface $request): ResponseInterfac if (empty($folder)) { return (new Response())->withStatus(404); } + $open = (bool)($request->getParsedBody()['open'] ?? $request->getQueryParams()['open']); $userAspect = $this->context->getAspect('beechit.user'); $this->leafStateService->saveLeafStateForUser($userAspect->get('user'), $folder, $open); + return new JsonResponse([]); } } diff --git a/Classes/Domain/Repository/ProcessedFileRepository.php b/Classes/Domain/Repository/ProcessedFileRepository.php deleted file mode 100644 index e02c93b..0000000 --- a/Classes/Domain/Repository/ProcessedFileRepository.php +++ /dev/null @@ -1,67 +0,0 @@ - - * All rights reserved - * - * This script is part of the TYPO3 project. The TYPO3 project is - * free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * The GNU General Public License can be found at - * http://www.gnu.org/copyleft/gpl.html. - * - * This script is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ -use TYPO3\CMS\Core\Resource\ProcessedFile; -use TYPO3\CMS\Core\Database\ConnectionPool; -use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Core\Utility\MathUtility; - -/** - * ProcessedFileRepository - */ -class ProcessedFileRepository extends \TYPO3\CMS\Core\Resource\ProcessedFileRepository -{ - - /** - * Find ProcessedFile by Uid - * - * @param int $uid - * @return object|ProcessedFile - * @throws \RuntimeException - * @throws \InvalidArgumentException - */ - public function findByUid($uid) - { - if (!MathUtility::canBeInterpretedAsInteger($uid)) { - throw new \InvalidArgumentException('uid has to be integer.', 1316779798); - } - - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table); - $row = $queryBuilder - ->select('*') - ->from($this->table) - ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter((int)$uid, \PDO::PARAM_INT))) - ->execute() - ->fetchAssociative(); - - if (empty($row) || !is_array($row)) { - throw new \RuntimeException( - 'Could not find row with uid "' . $uid . '" in table ' . $this->table, - 1314354065 - ); - } - return $this->createDomainObject($row); - } -} diff --git a/Classes/EventListener/FolderChangedEventListener.php b/Classes/EventListener/FolderChangedEventListener.php index 20cca2e..249bcf3 100644 --- a/Classes/EventListener/FolderChangedEventListener.php +++ b/Classes/EventListener/FolderChangedEventListener.php @@ -1,7 +1,8 @@ @@ -22,7 +23,9 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\EventListener; use BeechIt\FalSecuredownload\Service\Utility; use TYPO3\CMS\Core\Resource\Event\AfterFolderDeletedEvent; @@ -37,19 +40,16 @@ /** * Slots that pick up signals after (re)moving folders to update folder record + * + * Events of class are registered in Services.yaml + * + * @noinspection PhpUnused */ class FolderChangedEventListener implements SingletonInterface { - protected $folderMapping = []; + protected array $folderMapping = []; + protected Utility $utilityService; - /** - * @var Utility - */ - protected $utilityService; - - /** - * __construct - */ public function __construct() { $this->utilityService = GeneralUtility::makeInstance(Utility::class); @@ -58,63 +58,43 @@ public function __construct() /** * Get sub folder structure of folder before is gets moved * Is needed to update folder records when move was successful + * + * @noinspection PhpUnused */ - public function preFolderMove(BeforeFolderMovedEvent $event) + public function preFolderMove(BeforeFolderMovedEvent $event): void { $this->folderMapping[$event->getFolder()->getCombinedIdentifier()] = $this->getSubFolderIdentifiers($event->getFolder()); } /** * Update folder permissions records when folder is moved + * + * @noinspection PhpUnused */ - public function postFolderMove(AfterFolderMovedEvent $event) + public function postFolderMove(AfterFolderMovedEvent $event): void { $folder = $event->getFolder(); $newFolder = $event->getTargetFolder(); - $oldStorageUid = $folder->getStorage()->getUid(); - $newStorageUid = $newFolder->getStorage()->getUid(); - - $this->utilityService->updateFolderRecord( - $oldStorageUid, - $folder->getHashedIdentifier(), - $folder->getIdentifier(), - [ - 'storage' => $newStorageUid, - 'folder_hash' => $newFolder->getHashedIdentifier(), - 'folder' => $newFolder->getIdentifier() - ] - ); - - if (!empty($this->folderMapping[$folder->getCombinedIdentifier()])) { - $newMapping = $this->getSubFolderIdentifiers($newFolder); - foreach ($this->folderMapping[$folder->getCombinedIdentifier()] as $key => $folderInfo) { - $this->utilityService->updateFolderRecord( - $oldStorageUid, - $folderInfo[0], - $folderInfo[1], - [ - 'storage' => $newStorageUid, - 'folder_hash' => $newMapping[$key][0], - 'folder' => $newMapping[$key][1] - ] - ); - } - } + $this->updateFolderPermissions($folder, $newFolder); } /** * Get sub folder structure of folder before is gets deleted * Is needed to update folder records when delete was successful + * + * @noinspection PhpUnused */ - public function preFolderDelete(BeforeFolderDeletedEvent $event) + public function preFolderDelete(BeforeFolderDeletedEvent $event): void { $this->folderMapping[$event->getFolder()->getCombinedIdentifier()] = $this->getSubFolderIdentifiers($event->getFolder()); } /** * Update folder permissions records when folder is deleted + * + * @noinspection PhpUnused */ - public function postFolderDelete(AfterFolderDeletedEvent $event) + public function postFolderDelete(AfterFolderDeletedEvent $event): void { $folder = $event->getFolder(); $storageUid = $folder->getStorage()->getUid(); @@ -131,19 +111,28 @@ public function postFolderDelete(AfterFolderDeletedEvent $event) /** * Get sub folder structure of folder before is gets renamed * Is needed to update folder records when renaming was successful + * + * @noinspection PhpUnused */ - public function preFolderRename(BeforeFolderRenamedEvent $event) + public function preFolderRename(BeforeFolderRenamedEvent $event): void { $this->folderMapping[$event->getFolder()->getCombinedIdentifier()] = $this->getSubFolderIdentifiers($event->getFolder()); } /** * Update folder permissions records when a folder is renamed + * + * @noinspection PhpUnused */ - public function postFolderRename(AfterFolderRenamedEvent $event) + public function postFolderRename(AfterFolderRenamedEvent $event): void { $folder = $event->getSourceFolder(); $newFolder = $event->getFolder(); + $this->updateFolderPermissions($folder, $newFolder); + } + + private function updateFolderPermissions(Folder $folder, Folder $newFolder): void + { $oldStorageUid = $folder->getStorage()->getUid(); $newStorageUid = $newFolder->getStorage()->getUid(); @@ -177,11 +166,8 @@ public function postFolderRename(AfterFolderRenamedEvent $event) /** * Get folder - * - * @param Folder $folder - * @return array */ - protected function getSubFolderIdentifiers(Folder $folder) + protected function getSubFolderIdentifiers(Folder $folder): array { $folderIdentifiers = []; foreach ($folder->getSubfolders() as $subFolder) { diff --git a/Classes/EventListener/GeneratePublicUrlForResourceEventListener.php b/Classes/EventListener/GeneratePublicUrlForResourceEventListener.php index 8eb7389..319b512 100644 --- a/Classes/EventListener/GeneratePublicUrlForResourceEventListener.php +++ b/Classes/EventListener/GeneratePublicUrlForResourceEventListener.php @@ -1,8 +1,8 @@ @@ -23,20 +23,37 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\EventListener; use BeechIt\FalSecuredownload\Aspects\PublicUrlAspect; +use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Resource\Event\GeneratePublicUrlForResourceEvent; use TYPO3\CMS\Core\Utility\GeneralUtility; +/** + * EventListener is registered in Services.yaml + * + * @noinspection PhpUnused + */ class GeneratePublicUrlForResourceEventListener { + /** + * @throws RouteNotFoundException + */ public function __invoke(GeneratePublicUrlForResourceEvent $event): void { - if (!(Environment::isCli())) { + if (!Environment::isCli()) { $publicUrlAspect = GeneralUtility::makeInstance(PublicUrlAspect::class); - $publicUrlAspect->generatePublicUrl($event->getStorage(), $event->getDriver(), $event->getResource(), false, ['publicUrl' => $event->getPublicUrl()]); + $publicUrlAspect->generatePublicUrl( + $event->getStorage(), + $event->getDriver(), + $event->getResource(), + false, + ['publicUrl' => $event->getPublicUrl()] + ); } } } diff --git a/Classes/Hooks/FileDumpHook.php b/Classes/EventListener/ModifyFileDumpEventListener.php similarity index 73% rename from Classes/Hooks/FileDumpHook.php rename to Classes/EventListener/ModifyFileDumpEventListener.php index db9599b..5588c3c 100644 --- a/Classes/Hooks/FileDumpHook.php +++ b/Classes/EventListener/ModifyFileDumpEventListener.php @@ -1,8 +1,8 @@ @@ -23,7 +23,9 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\EventListener; use BeechIt\FalSecuredownload\Configuration\ExtensionConfiguration; use BeechIt\FalSecuredownload\Context\UserAspect; @@ -33,80 +35,38 @@ use InvalidArgumentException; use Psr\EventDispatcher\EventDispatcherInterface; use TYPO3\CMS\Core\Context\Context; +use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException; +use TYPO3\CMS\Core\Context\Exception\AspectPropertyNotFoundException; +use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; -use TYPO3\CMS\Core\Http\AbstractApplication; +use TYPO3\CMS\Core\LinkHandling\Exception\UnknownLinkHandlerException; use TYPO3\CMS\Core\LinkHandling\LinkService; -use TYPO3\CMS\Core\Resource\File; +use TYPO3\CMS\Core\Resource\Event\ModifyFileDumpEvent; +use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException; use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\FileReference; -use TYPO3\CMS\Core\Resource\Hook\FileDumpEIDHookInterface; use TYPO3\CMS\Core\Resource\ResourceInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Extbase\SignalSlot\Dispatcher; use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; -use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; -/** - * FileDumpHook - */ -class FileDumpHook extends AbstractApplication implements FileDumpEIDHookInterface +class ModifyFileDumpEventListener { - /** - * @var FrontendUserAuthentication - */ - protected $feUser; - - /** - * @var File - */ - protected $originalFile; - - /** - * @var string - */ - protected $loginRedirectUrl; - - /** - * @var string - */ - protected $noAccessRedirectUrl; - - /** - * @var bool - */ - protected $forceDownload = false; - - /** - * @var string - */ - protected $forceDownloadForExt = ''; - - /** - * @var bool - */ - protected $resumableDownload = false; - - protected $context; - - /** - * @var EventDispatcherInterface - */ - private $eventDispatcher; + protected ?FrontendUserAuthentication $feUser = null; + protected FileInterface $originalFile; + protected string $loginRedirectUrl = ''; + protected string $noAccessRedirectUrl = ''; + protected bool $forceDownload = false; + protected string $forceDownloadForExt = ''; + protected bool $resumableDownload = false; + protected Context $context; + private EventDispatcherInterface $eventDispatcher; + private ModifyFileDumpEvent $event; - /** - * Constructor - */ public function __construct(EventDispatcherInterface $eventDispatcher) { $this->context = GeneralUtility::makeInstance(Context::class); - if (!empty($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['fal_securedownload']['login_redirect_url'])) { - $this->loginRedirectUrl = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['fal_securedownload']['login_redirect_url']; - } - if (!empty($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['fal_securedownload']['no_access_redirect_url'])) { - $this->noAccessRedirectUrl = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['fal_securedownload']['no_access_redirect_url']; - } if (ExtensionConfiguration::loginRedirectUrl()) { $this->loginRedirectUrl = ExtensionConfiguration::loginRedirectUrl(); @@ -122,12 +82,17 @@ public function __construct(EventDispatcherInterface $eventDispatcher) $this->eventDispatcher = $eventDispatcher; } + public function __invoke(ModifyFileDumpEvent $event): void + { + $this->event = $event; + $this->checkFileAccess($event->getFile()); + } + /** - * Get feUser - * - * @return FrontendUserAuthentication + * @see https://github.com/beechit/fal_securedownload/issues/37 + * @noinspection PhpUnused */ - public function getFeUser() + public function getFeUser(): FrontendUserAuthentication { return $this->feUser; } @@ -139,7 +104,7 @@ public function getFeUser() * * @param ResourceInterface $file */ - public function checkFileAccess(ResourceInterface $file) + private function checkFileAccess(ResourceInterface $file) { if (!$file instanceof FileInterface) { throw new \RuntimeException('Given $file is not a file.', 1469019515); @@ -153,20 +118,19 @@ public function checkFileAccess(ResourceInterface $file) $loginRedirectUrl = $this->loginRedirectUrl; $noAccessRedirectUrl = $this->noAccessRedirectUrl; - /** @var BeforeRedirectsEvent $event */ - $event = $this->eventDispatcher->dispatch(new BeforeRedirectsEvent($loginRedirectUrl, $noAccessRedirectUrl, $file, $this)); - $loginRedirectUrl = $event->getLoginRedirectUrl(); - $noAccessRedirectUrl = $event->getNoAccessRedirectUrl(); + $beforeRedirectsEvent = $this->eventDispatcher->dispatch(new BeforeRedirectsEvent($loginRedirectUrl, $noAccessRedirectUrl, $file, $this)); + $loginRedirectUrl = $beforeRedirectsEvent->getLoginRedirectUrl(); + $noAccessRedirectUrl = $beforeRedirectsEvent->getNoAccessRedirectUrl(); if (!$this->checkPermissions()) { if (!$this->isLoggedIn()) { - if ($loginRedirectUrl !== null) { + if (!empty($loginRedirectUrl)) { $this->redirectToUrl($loginRedirectUrl); } else { $this->exitScript('Authentication required!'); } } else { - if ($noAccessRedirectUrl !== null) { + if (!empty($noAccessRedirectUrl)) { $this->redirectToUrl($noAccessRedirectUrl); } else { $this->exitScript('No access!'); @@ -188,7 +152,7 @@ public function checkFileAccess(ResourceInterface $file) ->insert( 'tx_falsecuredownload_download', $columns, - [\PDO::PARAM_INT, \PDO::PARAM_INT, \PDO::PARAM_INT, \PDO::PARAM_INT] + [Connection::PARAM_INT, Connection::PARAM_INT, Connection::PARAM_INT, Connection::PARAM_INT] ); } @@ -205,14 +169,13 @@ public function checkFileAccess(ResourceInterface $file) /** * Dump file contents * - * @todo: find a nicer way to force the download. Other hooks are blocked by this. - * @todo: Try to get the resumable option part of TYPO3 core itself + * TODO: Try to get the resumable option part of TYPO3 core itself find a nicer way to force the download. Other hooks are blocked by this. * - * @param File $file + * @param FileInterface $file * @param bool $asDownload * @param bool $resumableDownload */ - protected function dumpFileContents($file, $asDownload, $resumableDownload) + protected function dumpFileContents(FileInterface $file, bool $asDownload, bool $resumableDownload) { $downloadName = $file->hasProperty('download_name') && $file->getProperty('download_name') ? $file->getProperty('download_name') : $file->getName(); @@ -224,7 +187,7 @@ protected function dumpFileContents($file, $asDownload, $resumableDownload) if (!$resumableDownload) { $response = $file->getStorage()->streamFile($file, $asDownload, $downloadName); - $this->sendResponse($response); + $this->event->setResponse($response); exit; } @@ -250,7 +213,7 @@ protected function dumpFileContents($file, $asDownload, $resumableDownload) } $dumpSize = $fileSize; - list($begin, $end) = $range; + [$begin, $end] = $range; if ($begin !== 0 || $end !== $fileSize - 1) { header('HTTP/1.1 206 Partial Content'); header('Content-Range: bytes ' . $begin . '-' . $end . '/' . $fileSize); @@ -288,11 +251,8 @@ protected function dumpFileContents($file, $asDownload, $resumableDownload) /** * Determine if we want to force a file download - * - * @param string $fileExtension - * @return bool */ - protected function forceDownload($fileExtension) + protected function forceDownload(string $fileExtension): bool { $forceDownload = false; if ($this->forceDownload) { @@ -308,23 +268,27 @@ protected function forceDownload($fileExtension) /** * Check if user is logged in - * - * @return bool */ - protected function isLoggedIn() + protected function isLoggedIn(): bool { - $this->initializeUserAuthentication(); - return is_array($this->feUser->user) && $this->feUser->user['uid'] ? true : false; + try { + $this->initializeUserAuthentication(); + return is_array($this->feUser->user) && $this->feUser->user['uid']; + } catch (AspectNotFoundException|AspectPropertyNotFoundException $e) { + return false; + } } /** * Check if current user has enough permissions to view file - * - * @return bool */ - protected function checkPermissions() + protected function checkPermissions(): bool { - $this->initializeUserAuthentication(); + try { + $this->initializeUserAuthentication(); + } catch (AspectNotFoundException|AspectPropertyNotFoundException $e) { + return false; + } /** @var $checkPermissionsService CheckPermissions */ $checkPermissionsService = GeneralUtility::makeInstance(CheckPermissions::class); @@ -335,11 +299,17 @@ protected function checkPermissions() $userFeGroups = !$this->feUser->user ? false : $this->feUser->groupData['uid']; - return $checkPermissionsService->checkFileAccess($this->originalFile, $userFeGroups); + try { + return $checkPermissionsService->checkFileAccess($this->originalFile, $userFeGroups); + } catch (FolderDoesNotExistException $e) { + return false; + } } /** - * Initialise feUser + * Initialize feUser + * @throws AspectNotFoundException + * @throws AspectPropertyNotFoundException */ protected function initializeUserAuthentication() { @@ -347,28 +317,23 @@ protected function initializeUserAuthentication() /** @var UserAspect $userAspect */ $userAspect = $this->context->getAspect('beechit.user'); $this->feUser = $userAspect->get('user'); - $this->feUser->fetchGroupData(); + $this->feUser->fetchGroupData($this->event->getRequest()); } } /** - * Exit with a error message - * - * @param string $message - * @param int $httpCode + * Exit with an error message */ - protected function exitScript($message, $httpCode = 403) + protected function exitScript(string $message, int $httpCode = 403) { - header('HTTP/1.1 ' . (int)$httpCode . ' Forbidden'); + header('HTTP/1.1 ' . $httpCode . ' Forbidden'); exit($message); } /** * Redirect to url - * - * @param $url */ - protected function redirectToUrl($url) + protected function redirectToUrl(string $url) { $url = str_replace( '###REQUEST_URI###', @@ -386,12 +351,17 @@ protected function redirectToUrl($url) /** * Resolve the URL (currently only page and external URL are supported) - * - * @param string $url */ - protected function resolveUrl($url): string + protected function resolveUrl(string $url): string { - $urlParameters = GeneralUtility::makeInstance(LinkService::class)->resolve($url); + try { + $urlParameters = GeneralUtility::makeInstance(LinkService::class)->resolve($url); + } catch (UnknownLinkHandlerException $e) { + throw new InvalidArgumentException( + 'Redirects URL can only handle TYPO3 urls of types "page" or "url".', + 1686123053 + ); + } if ($urlParameters['type'] !== LinkService::TYPE_PAGE && $urlParameters['type'] !== LinkService::TYPE_URL) { throw new InvalidArgumentException( @@ -403,6 +373,7 @@ protected function resolveUrl($url): string if ($urlParameters['type'] === LinkService::TYPE_URL) { $uri = $urlParameters['url']; } else { + /** @var ContentObjectRenderer $contentObject */ $contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class, null); $contentObject->start([], ''); @@ -426,9 +397,9 @@ protected function resolveUrl($url): string * @param int $fileSize the size of the file * @return array the range (begin, end), or empty array if the range request is invalid. */ - protected function getHttpRange($fileSize) + protected function getHttpRange(int $fileSize): array { - $range = isset($_SERVER['HTTP_RANGE']) ? $_SERVER['HTTP_RANGE'] : false; + $range = $_SERVER['HTTP_RANGE'] ?? false; if (!$range || $range === '-') { return [0, $fileSize - 1]; } @@ -449,7 +420,7 @@ protected function getHttpRange($fileSize) $end = $fileSize - 1; } if ($start < 0 || $start > $end) { - return false; + return []; } return [$start, $end]; } diff --git a/Classes/EventListener/ModifyIconForResourcePropertiesEventListener.php b/Classes/EventListener/ModifyIconForResourcePropertiesEventListener.php index 60aa1d3..7c2f1b1 100644 --- a/Classes/EventListener/ModifyIconForResourcePropertiesEventListener.php +++ b/Classes/EventListener/ModifyIconForResourcePropertiesEventListener.php @@ -1,8 +1,8 @@ @@ -23,19 +23,26 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\EventListener; use BeechIt\FalSecuredownload\Aspects\IconFactoryAspect; use TYPO3\CMS\Core\Imaging\Event\ModifyIconForResourcePropertiesEvent; use TYPO3\CMS\Core\Utility\GeneralUtility; +/** + * EventListener is registered in Services.yaml + * + * @noinspection PhpUnused + */ class ModifyIconForResourcePropertiesEventListener { public function __invoke(ModifyIconForResourcePropertiesEvent $event): void { $iconFactoryAspect = GeneralUtility::makeInstance(IconFactoryAspect::class); - List($resource, $size, $options, $iconIdentifier, $overlayIdentifier) = + [, , , $iconIdentifier, $overlayIdentifier] = $iconFactoryAspect->buildIconForResource( $event->getResource(), $event->getSize(), diff --git a/Classes/Events/AddCustomGroupsEvent.php b/Classes/Events/AddCustomGroupsEvent.php index e71c89b..1ae1bde 100644 --- a/Classes/Events/AddCustomGroupsEvent.php +++ b/Classes/Events/AddCustomGroupsEvent.php @@ -1,8 +1,8 @@ @@ -23,7 +23,9 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\Events; final class AddCustomGroupsEvent { @@ -39,8 +41,9 @@ public function getCustomUserGroups(): array return $this->customUserGroups; } + /** @noinspection PhpUnused */ public function setCustomUserGroups(array $customUserGroups): void { $this->customUserGroups = $customUserGroups; } -} \ No newline at end of file +} diff --git a/Classes/Events/BeforeFileDumpEvent.php b/Classes/Events/BeforeFileDumpEvent.php index 298e412..3346bdc 100644 --- a/Classes/Events/BeforeFileDumpEvent.php +++ b/Classes/Events/BeforeFileDumpEvent.php @@ -1,8 +1,8 @@ @@ -23,42 +23,46 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\Events; -use BeechIt\FalSecuredownload\Hooks\FileDumpHook; +use BeechIt\FalSecuredownload\EventListener\ModifyFileDumpEventListener; use TYPO3\CMS\Core\Resource\ResourceInterface; final class BeforeFileDumpEvent { private ResourceInterface $file; - private FileDumpHook $caller; + private ModifyFileDumpEventListener $caller; - public function __construct(ResourceInterface $file, FileDumpHook $caller) + public function __construct(ResourceInterface $file, ModifyFileDumpEventListener $caller) { $this->file = $file; $this->caller = $caller; } + /** @noinspection PhpUnused */ public function getFile(): ResourceInterface { return $this->file; } + /** @noinspection PhpUnused */ public function setFile(ResourceInterface $file): void { $this->file = $file; } - public function getCaller(): FileDumpHook + /** @noinspection PhpUnused */ + public function getCaller(): ModifyFileDumpEventListener { return $this->caller; } - public function setCaller(FileDumpHook $caller): void + /** @noinspection PhpUnused */ + public function setCaller(ModifyFileDumpEventListener $caller): void { $this->caller = $caller; } - - -} \ No newline at end of file +} diff --git a/Classes/Events/BeforeRedirectsEvent.php b/Classes/Events/BeforeRedirectsEvent.php index 426ed60..cbad460 100644 --- a/Classes/Events/BeforeRedirectsEvent.php +++ b/Classes/Events/BeforeRedirectsEvent.php @@ -1,8 +1,8 @@ @@ -23,9 +23,11 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\Events; -use BeechIt\FalSecuredownload\Hooks\FileDumpHook; +use BeechIt\FalSecuredownload\EventListener\ModifyFileDumpEventListener; use TYPO3\CMS\Core\Resource\ResourceInterface; final class BeforeRedirectsEvent @@ -33,9 +35,9 @@ final class BeforeRedirectsEvent private ?string $loginRedirectUrl; private ?string $noAccessRedirectUrl; private ResourceInterface $file; - private FileDumpHook $caller; + private ModifyFileDumpEventListener $caller; - public function __construct(?string $loginRedirectUrl, ?string $noAccessRedirectUrl, ResourceInterface $file, FileDumpHook $caller) + public function __construct(?string $loginRedirectUrl, ?string $noAccessRedirectUrl, ResourceInterface $file, ModifyFileDumpEventListener $caller) { $this->loginRedirectUrl = $loginRedirectUrl; $this->noAccessRedirectUrl = $noAccessRedirectUrl; @@ -48,6 +50,7 @@ public function getLoginRedirectUrl(): ?string return $this->loginRedirectUrl; } + /** @noinspection PhpUnused */ public function setLoginRedirectUrl(?string $loginRedirectUrl): void { $this->loginRedirectUrl = $loginRedirectUrl; @@ -58,31 +61,34 @@ public function getNoAccessRedirectUrl(): ?string return $this->noAccessRedirectUrl; } + /** @noinspection PhpUnused */ public function setNoAccessRedirectUrl(?string $noAccessRedirectUrl): void { $this->noAccessRedirectUrl = $noAccessRedirectUrl; } + /** @noinspection PhpUnused */ public function getFile(): ResourceInterface { return $this->file; } + /** @noinspection PhpUnused */ public function setFile(ResourceInterface $file): void { $this->file = $file; } - public function getCaller(): FileDumpHook + /** @noinspection PhpUnused */ + public function getCaller(): ModifyFileDumpEventListener { return $this->caller; } - public function setCaller(FileDumpHook $caller): void + /** @noinspection PhpUnused */ + public function setCaller(ModifyFileDumpEventListener $caller): void { $this->caller = $caller; } - - -} \ No newline at end of file +} diff --git a/Classes/FormEngine/DownloadStatistics.php b/Classes/FormEngine/DownloadStatistics.php index b20f49e..490678b 100644 --- a/Classes/FormEngine/DownloadStatistics.php +++ b/Classes/FormEngine/DownloadStatistics.php @@ -1,6 +1,8 @@ @@ -21,10 +23,12 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ namespace BeechIt\FalSecuredownload\FormEngine; +use Doctrine\DBAL\Exception; +use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Backend\Form\AbstractNode; use TYPO3\CMS\Core\Database\ConnectionPool; @@ -33,15 +37,9 @@ class DownloadStatistics extends AbstractNode { - /** - * @var array - */ - protected $resultArray = []; + protected array $resultArray = []; - /** - * @return array - */ - public function render() + public function render(): array { $this->resultArray = $this->initializeResultArray(); $row = $this->data['databaseRow']; @@ -51,25 +49,34 @@ public function render() } $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file'); - $statistics = $queryBuilder - ->selectLiteral( - $queryBuilder->getConnection()->getDatabasePlatform()->getCountExpression( - $queryBuilder->quoteIdentifier('tx_falsecuredownload_download.file') - ) . ' AS ' . $queryBuilder->quoteIdentifier('cnt') - ) - ->addSelect('sys_file.name') - ->from('sys_file') - ->join( - 'sys_file', - 'tx_falsecuredownload_download', - 'tx_falsecuredownload_download', - $queryBuilder->expr()->eq('tx_falsecuredownload_download.file', $queryBuilder->quoteIdentifier('sys_file.uid')) - ) - ->where($queryBuilder->expr()->eq('tx_falsecuredownload_download.feuser', $queryBuilder->createNamedParameter((int)$row['uid'], \PDO::PARAM_INT))) - ->groupBy('sys_file.name') - ->orderBy('sys_file.name') - ->execute() - ->fetchAll(); + try { + $statistics = $queryBuilder + ->selectLiteral( + $queryBuilder->getConnection()->getDatabasePlatform()->getCountExpression( + $queryBuilder->quoteIdentifier('tx_falsecuredownload_download.file') + ) . ' AS ' . $queryBuilder->quoteIdentifier('cnt') + ) + ->addSelect('sys_file.name') + ->from('sys_file') + ->join( + 'sys_file', + 'tx_falsecuredownload_download', + 'tx_falsecuredownload_download', + $queryBuilder->expr()->eq('tx_falsecuredownload_download.file', $queryBuilder->quoteIdentifier('sys_file.uid')) + ) + ->where( + $queryBuilder->expr()->eq( + 'tx_falsecuredownload_download.feuser', + $queryBuilder->createNamedParameter((int)$row['uid'], Connection::PARAM_INT) + ) + ) + ->groupBy('sys_file.name') + ->orderBy('sys_file.name') + ->executeQuery() + ->fetchAllAssociative(); + } catch (Exception $e) { + $statistics = null; + } $lang = $this->getLanguageService(); $markup = []; @@ -95,10 +102,7 @@ public function render() return $this->resultArray; } - /** - * @return LanguageService - */ - protected function getLanguageService() + protected function getLanguageService(): LanguageService { return $GLOBALS['LANG']; } diff --git a/Classes/Hooks/AbstractBeButtons.php b/Classes/Hooks/AbstractBeButtons.php index 54e5d1f..1d7b56b 100644 --- a/Classes/Hooks/AbstractBeButtons.php +++ b/Classes/Hooks/AbstractBeButtons.php @@ -1,7 +1,8 @@ @@ -22,7 +23,12 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\Hooks; + +use Psr\Http\Message\UriInterface; +use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException; use TYPO3\CMS\Core\Localization\LanguageService; use BeechIt\FalSecuredownload\Service\Utility; use TYPO3\CMS\Backend\Routing\UriBuilder; @@ -35,16 +41,12 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; /** - * Abstract utility class for classes that want to add BE buttons - * to edit folder permissions + * Abstract utility class for classes that want to add BE buttons to edit folder permissions */ abstract class AbstractBeButtons { - /** - * @var ResourceFactory - */ - protected $resourceFactory; + protected ResourceFactory $resourceFactory; public function __construct(ResourceFactory $resourceFactory = null) { @@ -54,29 +56,24 @@ public function __construct(ResourceFactory $resourceFactory = null) /** * Generate album add/edit buttons for click menu or toolbar * - * @param string $combinedIdentifier - * @return array + * @throws RouteNotFoundException */ - protected function generateButtons($combinedIdentifier) + protected function generateButtons(string $combinedIdentifier): array { $buttons = []; - if (!$GLOBALS['BE_USER']->user) - { + if (!$GLOBALS['BE_USER']->user) { return $buttons; } - // In some folder copy/move actions in file list a invalid id is passed + // In some folder copy/move actions in file list an invalid id is passed try { - /** @var $file \TYPO3\CMS\Core\Resource\Folder */ $folder = $this->resourceFactory->retrieveFileOrFolderObject($combinedIdentifier); - } catch (ResourceDoesNotExistException $exception) { - $folder = null; - } catch (InsufficientFolderAccessPermissionsException $exception) { + } catch (ResourceDoesNotExistException|InsufficientFolderAccessPermissionsException $exception) { $folder = null; } - if ($folder && $folder instanceof Folder + if ($folder instanceof Folder && !$folder->getStorage()->isPublic() && in_array( $folder->getRole(), @@ -87,8 +84,6 @@ protected function generateButtons($combinedIdentifier) $utility = GeneralUtility::makeInstance(Utility::class); $folderRecord = $utility->getFolderRecord($folder); - $menuItems[] = 'spacer'; - if ($folderRecord) { $buttons[] = $this->createLink( $this->sL('clickmenu.folderpermissions'), @@ -105,14 +100,11 @@ protected function generateButtons($combinedIdentifier) ); } } + return $buttons; } - /** - * @param string $name - * @return Icon - */ - protected function getIcon($name) + protected function getIcon(string $name): Icon { $iconFactory = GeneralUtility::makeInstance(IconFactory::class); return $iconFactory->getIcon('action-' . $name, Icon::SIZE_SMALL); @@ -122,9 +114,10 @@ protected function getIcon($name) * Build edit url * * @param int $uid Media album uid - * @return string + * @return UriInterface + * @throws RouteNotFoundException */ - protected function buildEditUrl($uid) + protected function buildEditUrl(int $uid): UriInterface { return $this->buildUrl([ 'edit' => [ @@ -138,10 +131,9 @@ protected function buildEditUrl($uid) /** * Build Add new media album url * - * @param Folder $folder - * @return string + * @throws RouteNotFoundException */ - protected function buildAddUrl(Folder $folder) + protected function buildAddUrl(Folder $folder): UriInterface { return $this->buildUrl([ 'edit' => [ @@ -163,11 +155,14 @@ protected function buildAddUrl(Folder $folder) * Build record edit url * * @param array $parameters URL parameters - * @return string + * @return UriInterface + * @throws RouteNotFoundException */ - protected function buildUrl(array $parameters) + protected function buildUrl(array $parameters): UriInterface { $parameters['returnUrl'] = GeneralUtility::getIndpEnv('REQUEST_URI'); + + /** @var UriBuilder $uriBuilder */ $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); return $uriBuilder->buildUriFromRoute('record_edit', $parameters); @@ -178,33 +173,24 @@ protected function buildUrl(array $parameters) * * @param string $title * @param string $shortTitle - * @param string $icon - * @param string $url + * @param Icon $icon + * @param UriInterface $url * @param bool $addReturnUrl * @return string|array */ - abstract protected function createLink($title, $shortTitle, $icon, $url, $addReturnUrl = true); + abstract protected function createLink(string $title, string $shortTitle, Icon $icon, UriInterface $url, bool $addReturnUrl = true); - /** - * @return LanguageService - */ - protected function getLangService() + protected function getLangService(): LanguageService { return $GLOBALS['LANG']; } /** * Get language string - * - * @param string $key - * @param string $languageFile - * @return string */ - protected function sL( - $key, - $languageFile = 'LLL:EXT:fal_securedownload/Resources/Private/Language/locallang_be.xlf' - ) { - return $this->getLangService()->sL($languageFile . ':' . $key); + protected function sL(string $key): string + { + return $this->getLangService()->sL('LLL:EXT:fal_securedownload/Resources/Private/Language/locallang_be.xlf:' . $key); } } diff --git a/Classes/Hooks/CmsLayout.php b/Classes/Hooks/CmsLayout.php index 38354ed..e170fcc 100644 --- a/Classes/Hooks/CmsLayout.php +++ b/Classes/Hooks/CmsLayout.php @@ -1,7 +1,8 @@ @@ -22,7 +23,11 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\Hooks; + +use Exception; use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -32,30 +37,29 @@ */ class CmsLayout { - /** - * @var ResourceFactory - */ - protected $resourceFactory; - public function __construct(ResourceFactory $resourceFactory = null) + protected ResourceFactory $resourceFactory; + + public function __construct(ResourceFactory $resourceFactory) { - $this->resourceFactory = $resourceFactory ?? GeneralUtility::makeInstance(ResourceFactory::class); + $this->resourceFactory = $resourceFactory; } /** * Flexform information - * - * @var array */ - public $flexformData = []; + public array $flexformData = []; /** * Returns information about this extension's pi1 plugin * + * Registered as "Page module hook" in ext_localconf.php + * * @param array $params Parameters to the hook * @return string Information about pi1 plugin + * @noinspection PhpUnused */ - public function getExtensionSummary(array $params) + public function getExtensionSummary(array $params): string { $tableData = []; $result = '' . $this->sL('plugin.title') . ''; @@ -68,7 +72,7 @@ public function getExtensionSummary(array $params) try { $storageUid = $this->getFieldFromFlexform('settings.storage'); $storageName = $this->resourceFactory->getStorageObject($storageUid)->getName(); - } catch (\Exception $exception) { + } catch (Exception $exception) { } if ($storageName) { @@ -95,11 +99,8 @@ public function getExtensionSummary(array $params) /** * Render the settings as table for Web>Page module * System settings are displayed in mono font - * - * @param array $tableData - * @return string */ - protected function renderSettingsAsTable(array $tableData) + protected function renderSettingsAsTable(array $tableData): string { if (count($tableData) == 0) { return ''; @@ -121,7 +122,7 @@ protected function renderSettingsAsTable(array $tableData) * @param string $sheet name of the sheet * @return string|null if nothing found, value if found */ - protected function getFieldFromFlexform($key, $sheet = 'sDEF') + protected function getFieldFromFlexform(string $key, string $sheet = 'sDEF'): ?string { $flexform = $this->flexformData; if (isset($flexform['data'])) { @@ -137,22 +138,13 @@ protected function getFieldFromFlexform($key, $sheet = 'sDEF') /** * Get language string - * - * @param string $key - * @param string $languageFile - * @return string */ - protected function sL( - $key, - $languageFile = 'LLL:EXT:fal_securedownload/Resources/Private/Language/locallang_be.xlf' - ) { - return $this->getLangService()->sL($languageFile . ':' . $key); + protected function sL(string $key): string + { + return $this->getLangService()->sL('LLL:EXT:fal_securedownload/Resources/Private/Language/locallang_be.xlf:' . $key); } - /** - * @return LanguageService - */ - protected function getLangService() + protected function getLangService(): LanguageService { return $GLOBALS['LANG']; } diff --git a/Classes/Hooks/DocHeaderButtonsHook.php b/Classes/Hooks/DocHeaderButtonsHook.php index 6e51e37..8923fa1 100644 --- a/Classes/Hooks/DocHeaderButtonsHook.php +++ b/Classes/Hooks/DocHeaderButtonsHook.php @@ -1,8 +1,8 @@ @@ -23,9 +23,14 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\Hooks; +use Psr\Http\Message\UriInterface; +use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException; use TYPO3\CMS\Backend\Template\Components\ButtonBar; +use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\Utility\GeneralUtility; /** @@ -33,48 +38,25 @@ */ class DocHeaderButtonsHook extends AbstractBeButtons { - /** - * Add folder permissions button to top bar of file list - * - * @param array $params ['buttons' => $buttons, 'markers' => &$markers, 'pObj' => &$this] - */ - public function addFolderPermissionsButton(array $params) - { - // only add button to file list module - if ($params['pObj']->scriptID === 'ext/filelist/mod1/index.php') { - $extraButtons = $this->generateButtons(GeneralUtility::_GP('id')); - if (count($extraButtons)) { - $params['markers']['BUTTONLIST_LEFT'] = - preg_replace( - '`$`', - implode('', $extraButtons) . '', - $params['markers']['BUTTONLIST_LEFT'] - ); - } - } - } /** * Create button - * - * @param string $title - * @param string $shortTitle - * @param string $icon - * @param string $url - * @param bool $addReturnUrl */ - protected function createLink($title, $shortTitle, $icon, $url, $addReturnUrl = true) + protected function createLink(string $title, string $shortTitle, Icon $icon, UriInterface $url, bool $addReturnUrl = true): array { - $link = [ + return [ 'title' => $title, 'icon' => $icon, 'url' => $url . ($addReturnUrl ? '&returnUrl=' . rawurlencode($_SERVER['REQUEST_URI']) : '') ]; - return $link; } /** * Get buttons + * + * Registered in ext_localconf.php as ['Backend\Template\Components\ButtonBar']['getButtonsHook'] + * + * @throws RouteNotFoundException */ public function getButtons(array $params, ButtonBar $buttonBar): array { @@ -90,4 +72,4 @@ public function getButtons(array $params, ButtonBar $buttonBar): array return $buttons; } -} \ No newline at end of file +} diff --git a/Classes/Hooks/KeSearchFilesHook.php b/Classes/Hooks/KeSearchFilesHook.php index 6ae9487..7ff7e23 100644 --- a/Classes/Hooks/KeSearchFilesHook.php +++ b/Classes/Hooks/KeSearchFilesHook.php @@ -1,7 +1,8 @@ @@ -22,26 +23,21 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\Hooks; + use BeechIt\FalSecuredownload\Security\CheckPermissions; use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; -/** - * Class KeSearchFilesHook - */ class KeSearchFilesHook implements SingletonInterface { - /** - * @var CheckPermissions - */ - protected $checkPermissionsService; - /** - * Constructor - */ + protected CheckPermissions $checkPermissionsService; + public function __construct() { $this->checkPermissionsService = GeneralUtility::makeInstance(CheckPermissions::class); @@ -63,16 +59,17 @@ public function __construct() */ public function modifyFileIndexEntryFromContentIndexer( $fileObject, - $content, - $fileIndexerObject, - &$feGroups, - $ttContentRow, - $storagePid, - $title, - $tags, - $abstract, - $additionalFields - ) { + string $content, + \tx_kesearch_indexer_types_file $fileIndexerObject, + string &$feGroups, + array $ttContentRow, + int $storagePid, + string $title, + string $tags, + string $abstract, + array $additionalFields + ): void + { if ($fileObject instanceof File && !$fileObject->getStorage()->isPublic()) { $resourcePermissions = $this->checkPermissionsService->getPermissions($fileObject); // If there are already permissions set, refine these with actual file permissions @@ -96,7 +93,13 @@ public function modifyFileIndexEntryFromContentIndexer( * @param array $indexRecordValues * @param \tx_kesearch_indexer_types_file $indexer */ - public function modifyFileIndexEntry($file, $content, $additionalFields, &$indexRecordValues, $indexer) + public function modifyFileIndexEntry( + $file, + string $content, + array $additionalFields, + array &$indexRecordValues, + \tx_kesearch_indexer_types_file $indexer + ): void { if ($file instanceof File && !$file->getStorage()->isPublic()) { $indexRecordValues['fe_group'] = $this->checkPermissionsService->getPermissions($file); diff --git a/Classes/Hooks/ProcessDatamapHook.php b/Classes/Hooks/ProcessDatamapHook.php index 0b13cca..72e791f 100644 --- a/Classes/Hooks/ProcessDatamapHook.php +++ b/Classes/Hooks/ProcessDatamapHook.php @@ -1,7 +1,8 @@ @@ -23,7 +24,9 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\Hooks; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\DataHandling\DataHandler; @@ -37,19 +40,15 @@ class ProcessDatamapHook /** * Trigger updateFolderTree after change in tx_falsecuredownload_folder * - * @param string $status - * @param string $table - * @param $id - * @param array $fieldArray - * @param DataHandler $dataHandler */ public function processDatamap_afterDatabaseOperations( - $status, - $table, - $id, + string $status, + string $table, + string $id, array $fieldArray, DataHandler $dataHandler - ) { + ): void + { if ($table === 'tx_falsecuredownload_folder') { BackendUtility::setUpdateSignal('updateFolderTree'); } @@ -60,21 +59,22 @@ public function processDatamap_afterDatabaseOperations( * * @param string $command * @param string $table - * @param int $id + * @param string $id * @param mixed $value * @param DataHandler $dataHandler * @param mixed $pasteUpdate * @param array $pasteDatamap */ public function processCmdmap_postProcess( - $command, - $table, - $id, + string $command, + string $table, + string $id, $value, DataHandler $dataHandler, $pasteUpdate, array $pasteDatamap - ) { + ): void + { if ($table === 'tx_falsecuredownload_folder') { BackendUtility::setUpdateSignal('updateFolderTree'); } diff --git a/Classes/Middleware/EidFrontendAuthentication.php b/Classes/Middleware/EidFrontendAuthentication.php index 506ab33..eac4de5 100644 --- a/Classes/Middleware/EidFrontendAuthentication.php +++ b/Classes/Middleware/EidFrontendAuthentication.php @@ -1,4 +1,7 @@ handle($request); } - $GLOBALS['TYPO3_REQUEST'] = $request; - $frontendUser = GeneralUtility::makeInstance(FrontendUserAuthentication::class); // List of page IDs where to look for frontend user records @@ -52,14 +47,14 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface } // Authenticate now - $frontendUser->start(); + $frontendUser->start($request); $frontendUser->unpack_uc(); // Register the frontend user as aspect and within the session $this->setFrontendUserAspect($frontendUser); $backendUserObject = GeneralUtility::makeInstance(FrontendBackendUserAuthentication::class); - $backendUserObject->start(); + $backendUserObject->start($request); $backendUserObject->unpack_uc(); if (!empty($backendUserObject->user['uid'])) { $backendUserObject->fetchGroupData(); @@ -71,20 +66,16 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface /** * Register the frontend user as aspect - * - * @param AbstractUserAuthentication $user */ - protected function setFrontendUserAspect(AbstractUserAuthentication $user) + protected function setFrontendUserAspect(AbstractUserAuthentication $user): void { $this->context->setAspect('beechit.user', GeneralUtility::makeInstance(UserAspect::class, $user)); } /** * Register the backend user as aspect - * - * @param AbstractUserAuthentication $user */ - protected function setBackendUserAspect(AbstractUserAuthentication $user) + protected function setBackendUserAspect(AbstractUserAuthentication $user): void { $this->context->setAspect('beechit.beuser', GeneralUtility::makeInstance(UserAspect::class, $user)); $GLOBALS['BE_USER'] = $user; diff --git a/Classes/Security/CheckPermissions.php b/Classes/Security/CheckPermissions.php index 36049b9..d729c34 100644 --- a/Classes/Security/CheckPermissions.php +++ b/Classes/Security/CheckPermissions.php @@ -1,7 +1,8 @@ @@ -22,25 +23,21 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\Security; use BeechIt\FalSecuredownload\Events\AddCustomGroupsEvent; use BeechIt\FalSecuredownload\Service\Utility; use Psr\EventDispatcher\EventDispatcherInterface; -use TYPO3\CMS\Backend\FrontendBackendUserAuthentication; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; -use TYPO3\CMS\Core\Authentication\Mfa\MfaRequiredException; -use TYPO3\CMS\Core\Context\Context; -use TYPO3\CMS\Core\Context\UserAspect; use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException; -use TYPO3\CMS\Core\Resource\File; -use TYPO3\CMS\Core\Resource\Folder; +use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\FolderInterface; use TYPO3\CMS\Core\Resource\ResourceInterface; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Extbase\SignalSlot\Dispatcher; /** * Utility functions to check permissions @@ -48,24 +45,10 @@ class CheckPermissions implements SingletonInterface { - /** - * @var Utility - */ - protected $utilityService; - - /** - * @var array check folder root-line access cache - */ - protected $checkFolderRootLineAccessCache = []; - - /** - * @var EventDispatcherInterface - */ - protected $eventDispatcher; + protected Utility $utilityService; + protected array $checkFolderRootLineAccessCache = []; + protected EventDispatcherInterface $eventDispatcher; - /** - * Constructor - */ public function __construct(EventDispatcherInterface $eventDispatcher) { $this->utilityService = GeneralUtility::makeInstance(Utility::class); @@ -75,22 +58,24 @@ public function __construct(EventDispatcherInterface $eventDispatcher) /** * Check file access for current FeUser * - * @param File $file - * @return bool + * TODO: check if meant to be public api, otherwise remove + * + * @noinspection PhpUnused */ - public function checkFileAccessForCurrentFeUser($file) + public function checkFileAccessForCurrentFeUser(FileInterface $file): bool { $userFeGroups = !isset($GLOBALS['TSFE']->fe_user->user) ? false : $GLOBALS['TSFE']->fe_user->groupData['uid']; - return $this->checkFileAccess($file, $userFeGroups); + try { + return $this->checkFileAccess($file, $userFeGroups); + } catch (FolderDoesNotExistException $e) { + return false; + } } /** * Check backend user file access - * - * @param File $file - * @return bool */ - public function checkBackendUserFileAccess(File $file): bool + public function checkBackendUserFileAccess(FileInterface $file): bool { $backendUser = $GLOBALS['BE_USER'] ?? null; if (!$backendUser instanceof BackendUserAuthentication || empty($backendUser->user['uid'])) { @@ -102,7 +87,7 @@ public function checkBackendUserFileAccess(File $file): bool $resourceStorage = $file->getStorage(); $resourceStorage->setUserPermissions($GLOBALS['BE_USER']->getFilePermissionsForStorage($resourceStorage)); foreach ($GLOBALS['BE_USER']->getFileMountRecords() as $fileMountRow) { - if ((int)$fileMountRow['base'] === (int)$resourceStorage->getUid()) { + if ((int)$fileMountRow['base'] === $resourceStorage->getUid()) { try { $resourceStorage->addFileMount($fileMountRow['path'], $fileMountRow); } catch (FolderDoesNotExistException $e) { @@ -117,31 +102,15 @@ public function checkBackendUserFileAccess(File $file): bool return $access; } - /** - * Get backend user object - * - * @return FrontendBackendUserAuthentication - * @throws MfaRequiredException - */ - protected function getBackendUser(): FrontendBackendUserAuthentication - { - $backendUserObject = GeneralUtility::makeInstance(FrontendBackendUserAuthentication::class); - $backendUserObject->start(); - $backendUserObject->unpack_uc(); - if (!empty($backendUserObject->user['uid'])) { - $backendUserObject->fetchGroupData(); - } - return $backendUserObject; - } - /** * Check file access for given FeGroups combination * - * @param File $file + * @param FileInterface $file * @param bool|array $userFeGroups FALSE = no login, array() fe groups of user * @return bool + * @throws FolderDoesNotExistException */ - public function checkFileAccess($file, $userFeGroups) + public function checkFileAccess(FileInterface $file, $userFeGroups): bool { // all files in public storage are accessible if ($file->getStorage()->isPublic()) { @@ -149,9 +118,8 @@ public function checkFileAccess($file, $userFeGroups) } $customUserGroups = []; - /** @var AddCustomGroupsEvent $event */ - $event = $this->eventDispatcher->dispatch(new AddCustomGroupsEvent([$customUserGroups])); - $eventArguments = $event->getCustomUserGroups(); + $addCustomGroupsEvent = $this->eventDispatcher->dispatch(new AddCustomGroupsEvent([$customUserGroups])); + $eventArguments = $addCustomGroupsEvent->getCustomUserGroups(); $customUserGroups = array_shift($eventArguments); if (is_array($userFeGroups)) { @@ -161,7 +129,7 @@ public function checkFileAccess($file, $userFeGroups) $userFeGroups = $customUserGroups; } - /** @var Folder $parentFolder */ + // $file->getParentFolder() may throw a FolderDoesNotExistException which currently is not documented in PHPDoc $parentFolder = $file->getParentFolder(); // check folder access if ($this->checkFolderRootLineAccess($parentFolder, $userFeGroups)) { @@ -178,10 +146,11 @@ public function checkFileAccess($file, $userFeGroups) /** * Check if given FeGroups have enough rights to access given folder * + * @param FolderInterface $folder * @param bool|array $userFeGroups FALSE = no login, array() is the groups of the user * @return bool */ - public function checkFolderRootLineAccess(Folder $folder, $userFeGroups) + public function checkFolderRootLineAccess(FolderInterface $folder, $userFeGroups): bool { $cacheIdentifier = sha1( $folder->getHashedIdentifier() . @@ -191,18 +160,22 @@ public function checkFolderRootLineAccess(Folder $folder, $userFeGroups) if (!isset($this->checkFolderRootLineAccessCache[$cacheIdentifier])) { $this->checkFolderRootLineAccessCache[$cacheIdentifier] = true; - // loop through the root line of an folder and check the permissions of every folder - foreach ($this->getFolderRootLine($folder) as $rootlinefolder) { - // fetch folder permissions record - $folderRecord = $this->utilityService->getFolderRecord($rootlinefolder); + // loop through the root line of a folder and check the permissions of every folder + try { + foreach ($this->getFolderRootLine($folder) as $rootlineFolder) { + // fetch folder permissions record + $folderRecord = $this->utilityService->getFolderRecord($rootlineFolder); - // if record found check permissions - if ($folderRecord) { - if (!$this->matchFeGroupsWithFeUser($folderRecord['fe_groups'], $userFeGroups)) { - $this->checkFolderRootLineAccessCache[$cacheIdentifier] = false; - break; + // if record found check permissions + if ($folderRecord) { + if (!$this->matchFeGroupsWithFeUser($folderRecord['fe_groups'], $userFeGroups)) { + $this->checkFolderRootLineAccessCache[$cacheIdentifier] = false; + break; + } } } + } catch (FolderDoesNotExistException $e) { + return false; } } return $this->checkFolderRootLineAccessCache[$cacheIdentifier]; @@ -211,6 +184,7 @@ public function checkFolderRootLineAccess(Folder $folder, $userFeGroups) /** * Get permissions set on folder (no root line check) * + * @param FolderInterface $folder * @return bool|string FALSE or comma separated list of fe_group uids */ public function getFolderPermissions(FolderInterface $folder) @@ -225,32 +199,33 @@ public function getFolderPermissions(FolderInterface $folder) /** * Get FeGroups that are allowed to view a file/folder (checks full rootline) - * - * @return string */ - public function getPermissions(ResourceInterface $resource) + public function getPermissions(ResourceInterface $resource): string { $currentPermissionsCheck = $resource->getStorage()->getEvaluatePermissions(); $resource->getStorage()->setEvaluatePermissions(false); $feGroups = []; - // loop trough the root line of an folder and check the permissions of every folder - foreach ($this->getFolderRootLine($resource->getParentFolder()) as $folder) { - // fetch folder permissions record - $folderRecord = $this->utilityService->getFolderRecord($folder); + // loop through the root line of a folder and check the permissions of every folder + try { + foreach ($this->getFolderRootLine($resource->getParentFolder()) as $folder) { + // fetch folder permissions record + $folderRecord = $this->utilityService->getFolderRecord($folder); - // if record found check permissions - if ($folderRecord) { - if ($feGroups === []) { - $feGroups = GeneralUtility::trimExplode(',', $folderRecord['fe_groups'], true); - } - if ($folderRecord['fe_groups']) { - $feGroups = ArrayUtility::keepItemsInArray($feGroups, $folderRecord['fe_groups']); + // if record found check permissions + if ($folderRecord) { + if ($feGroups === []) { + $feGroups = GeneralUtility::trimExplode(',', $folderRecord['fe_groups'], true); + } + if ($folderRecord['fe_groups']) { + $feGroups = ArrayUtility::keepItemsInArray($feGroups, $folderRecord['fe_groups']); + } + break; } - break; } + } catch (FolderDoesNotExistException $e) { } - if ($resource instanceof File && $resource->getProperty('fe_groups')) { + if ($resource instanceof FileInterface && $resource->getProperty('fe_groups')) { $feGroups = ArrayUtility::keepItemsInArray($feGroups, $resource->getProperty('fe_groups')); } $resource->getStorage()->setEvaluatePermissions($currentPermissionsCheck); @@ -260,12 +235,13 @@ public function getPermissions(ResourceInterface $resource) /** * Get all folders in root line of given folder * - * @return Folder[] + * @return FolderInterface[] + * @throws FolderDoesNotExistException */ - public function getFolderRootLine(FolderInterface $folder) + public function getFolderRootLine(FolderInterface $folder): array { $rootLine = [$folder]; - /** @var $parentFolder \TYPO3\CMS\Core\Resource\Folder */ + // $folder->getParentFolder() may throw a FolderDoesNotExistException which currently is not documented in PHPDoc $parentFolder = $folder->getParentFolder(); $count = 0; while ($parentFolder->getIdentifier() !== $folder->getIdentifier()) { @@ -287,7 +263,7 @@ public function getFolderRootLine(FolderInterface $folder) * @param bool|array $userFeGroups FALSE = no login, array() is the groups of the user * @return bool */ - public function matchFeGroupsWithFeUser($groups, $userFeGroups) + public function matchFeGroupsWithFeUser(string $groups, $userFeGroups): bool { // no groups specified everyone has access diff --git a/Classes/Service/LeafStateService.php b/Classes/Service/LeafStateService.php index e8cb66b..db17e09 100644 --- a/Classes/Service/LeafStateService.php +++ b/Classes/Service/LeafStateService.php @@ -1,7 +1,8 @@ @@ -22,35 +23,29 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\Service; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication; -/** - * Class LeafStateService - */ class LeafStateService implements SingletonInterface { - /** - * @var ResourceFactory - */ - protected $resourceFactory; - public function __construct(ResourceFactory $resourceFactory = null) + protected ResourceFactory $resourceFactory; + + public function __construct(ResourceFactory $resourceFactory) { - $this->resourceFactory = $resourceFactory ?? GeneralUtility::makeInstance(ResourceFactory::class); + $this->resourceFactory = $resourceFactory; } /** * Save new leave state in user session - * - * @param string $folder - * @param bool $open */ - public function saveLeafStateForUser(FrontendUserAuthentication $user, $folder, $open) + public function saveLeafStateForUser(FrontendUserAuthentication $user, string $folder, bool $open): void { // check if folder exists @@ -69,11 +64,8 @@ public function saveLeafStateForUser(FrontendUserAuthentication $user, $folder, /** * Get leaf state from user session - * - * @param string $folder - * @return bool */ - public function getLeafStateForUser(FrontendUserAuthentication $user, $folder) + public function getLeafStateForUser(FrontendUserAuthentication $user, string $folder): bool { $folderStates = $this->getFolderState($user); return !empty($folderStates[$folder]); @@ -99,7 +91,7 @@ protected function getFolderState(FrontendUserAuthentication $user) /** * Save leaf states in user session */ - protected function saveFolderState(FrontendUserAuthentication $user, array $folderState) + protected function saveFolderState(FrontendUserAuthentication $user, array $folderState): void { $user->setKey($user->user['uid'] ? 'user' : 'ses', 'LeafStateService', serialize($folderState)); $user->storeSessionData(); diff --git a/Classes/Service/UserFileMountService.php b/Classes/Service/UserFileMountService.php index 781b928..773e309 100644 --- a/Classes/Service/UserFileMountService.php +++ b/Classes/Service/UserFileMountService.php @@ -1,7 +1,8 @@ @@ -22,75 +23,31 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ -use TYPO3\CMS\Core\Exception; -use TYPO3\CMS\Core\Messaging\FlashMessage; -use TYPO3\CMS\Core\Messaging\FlashMessageService; -use TYPO3\CMS\Core\Resource\StorageRepository; -use TYPO3\CMS\Core\Utility\GeneralUtility; + */ + +namespace BeechIt\FalSecuredownload\Service; + +use TYPO3\CMS\Core\Resource\Service\UserFileMountService as TYPO3UserFileMountService; /** * FlexForm file mount service + * + * Registered in Configuration/FlexForms/FileTree.xml + * + * @noinspection PhpUnused */ -class UserFileMountService extends \TYPO3\CMS\Core\Resource\Service\UserFileMountService +class UserFileMountService extends TYPO3UserFileMountService { /** - * User function for to render a dropdown for selecting a folder - * of a selected storage + * User function for to render a dropdown for selecting a folder of a selected storage * * @param array $PA the array with additional configuration options. - * @throws Exception + * @noinspection PhpUnused */ - public function renderFlexFormSelectDropdown(&$PA) + public function renderFlexFormSelectDropdown(array &$PA): void { - // get storageUid from flexform - $storageUid = $PA['row']['settings.storage'][0]; - - // if storageUid found get folders - if ($storageUid > 0) { - // reset items - $PA['items'] = []; - - /** @var $storageRepository StorageRepository */ - $storageRepository = GeneralUtility::makeInstance(StorageRepository::class); - /** @var $storage \TYPO3\CMS\Core\Resource\ResourceStorage */ - $storage = $storageRepository->findByUid($storageUid); - if ($storage->isBrowsable()) { - if (!empty($storage->getFileMounts())) { - $fileMounts = $storage->getFileMounts(); - $folderItems = []; - foreach ($fileMounts as $fileMount) { - $folderItems[] = $this->getSubfoldersForOptionList($fileMount['folder']); - } - $folderItems = array_merge(...$folderItems); - } else { - $rootLevelFolder = $storage->getRootLevelFolder(); - $folderItems = $this->getSubfoldersForOptionList($rootLevelFolder); - } - foreach ($folderItems as $item) { - $PA['items'][] = [ - $item->getIdentifier(), - $item->getIdentifier() - ]; - } - } else { - /** @var FlashMessageService $flashMessageService */ - $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class); - $queue = $flashMessageService->getMessageQueueByIdentifier(); - $queue->enqueue(new FlashMessage( - 'Storage "' . $storage->getName() . '" is not browsable. No folder is currently selectable.', - '', - FlashMessage::WARNING - )); - - if (!count($PA['items'])) { - $PA['items'][] = [ - '', - '' - ]; - } - } - } + $PA['row']['storage'] = $PA['row']['settings.storage']; + parent::renderTceformsSelectDropdown($PA); } } diff --git a/Classes/Service/Utility.php b/Classes/Service/Utility.php index 60494d8..1389a45 100644 --- a/Classes/Service/Utility.php +++ b/Classes/Service/Utility.php @@ -1,7 +1,8 @@ @@ -22,26 +23,24 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\Service; +use Doctrine\DBAL\Exception; +use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Resource\Folder; +use TYPO3\CMS\Core\Resource\FolderInterface; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; -/** - * Class Utility - */ class Utility implements SingletonInterface { - static protected $folderRecordCache = []; - - /** - * @var ConnectionPool - */ - protected $connectionPool; + static protected array $folderRecordCache = []; + protected ConnectionPool $connectionPool; public function __construct() { @@ -54,19 +53,33 @@ public function __construct() * @param Folder $folder * @return array|false */ - public function getFolderRecord(Folder $folder) + public function getFolderRecord(FolderInterface $folder) { if (!isset(self::$folderRecordCache[$folder->getCombinedIdentifier()]) || !array_key_exists($folder->getCombinedIdentifier(), self::$folderRecordCache) ) { $queryBuilder = $this->getQueryBuilder(); - $record = $queryBuilder - ->select('*') - ->from('tx_falsecuredownload_folder') - ->where($queryBuilder->expr()->eq('storage', $queryBuilder->createNamedParameter((int)$folder->getStorage()->getUid(), \PDO::PARAM_INT))) - ->andWhere($queryBuilder->expr()->eq('folder_hash', $queryBuilder->createNamedParameter($folder->getHashedIdentifier(), \PDO::PARAM_STR))) - ->execute() - ->fetchAssociative(); + try { + $record = $queryBuilder + ->select('*') + ->from('tx_falsecuredownload_folder') + ->where( + $queryBuilder->expr()->eq( + 'storage', + $queryBuilder->createNamedParameter($folder->getStorage()->getUid(), Connection::PARAM_INT) + ) + ) + ->andWhere( + $queryBuilder->expr()->eq( + 'folder_hash', + $queryBuilder->createNamedParameter($folder->getHashedIdentifier()) + ) + ) + ->executeQuery() + ->fetchAssociative(); + } catch (Exception $e) { + $record = false; + } // cache results self::$folderRecordCache[$folder->getCombinedIdentifier()] = $record; @@ -77,13 +90,13 @@ public function getFolderRecord(Folder $folder) /** * Update folder record after move/rename - * - * @param int $oldStorageUid - * @param string $oldIdentifierHash - * @param string $oldIdentifier - * @param array $newRecord */ - public function updateFolderRecord($oldStorageUid, $oldIdentifierHash, $oldIdentifier, $newRecord) + public function updateFolderRecord( + int $oldStorageUid, + string $oldIdentifierHash, + string $oldIdentifier, + array $newRecord + ): void { $allowedFields = ['storage', 'folder', 'folder_hash']; $record = []; @@ -98,12 +111,23 @@ public function updateFolderRecord($oldStorageUid, $oldIdentifierHash, $oldIdent $queryBuilder = $this->getQueryBuilder(); $queryBuilder ->update('tx_falsecuredownload_folder') - ->where($queryBuilder->expr()->eq('storage', $queryBuilder->createNamedParameter((int)$oldStorageUid, \PDO::PARAM_INT))) - ->andWhere($queryBuilder->expr()->eq('folder_hash', $queryBuilder->createNamedParameter($oldIdentifierHash, \PDO::PARAM_STR))); + ->where( + $queryBuilder->expr()->eq( + 'storage', + $queryBuilder->createNamedParameter($oldStorageUid, Connection::PARAM_INT) + ) + ) + ->andWhere( + $queryBuilder->expr()->eq( + 'folder_hash', + $queryBuilder->createNamedParameter($oldIdentifierHash) + ) + ); + foreach ($record as $field => $value) { $queryBuilder->set($field, $value); } - $queryBuilder->execute(); + $queryBuilder->executeStatement(); // clear cache if exists if (isset(self::$folderRecordCache[$oldStorageUid . ':' . $oldIdentifier])) { @@ -114,19 +138,21 @@ public function updateFolderRecord($oldStorageUid, $oldIdentifierHash, $oldIdent /** * Delete folder record when folder is deleted - * - * @param int $storageUid - * @param string $folderHash - * @param string $identifier */ - public function deleteFolderRecord($storageUid, $folderHash, $identifier) + public function deleteFolderRecord(int $storageUid, string $folderHash, string $identifier): void { $queryBuilder = $this->getQueryBuilder(); $queryBuilder ->delete('tx_falsecuredownload_folder') - ->where($queryBuilder->expr()->eq('storage', $queryBuilder->createNamedParameter((int)$storageUid, \PDO::PARAM_INT))) - ->andWhere($queryBuilder->expr()->eq('folder_hash', $queryBuilder->createNamedParameter($folderHash, \PDO::PARAM_STR))) - ->execute(); + ->where( + $queryBuilder->expr()->eq( + 'storage', $queryBuilder->createNamedParameter($storageUid, Connection::PARAM_INT) + ) + ) + ->andWhere( + $queryBuilder->expr()->eq('folder_hash', $queryBuilder->createNamedParameter($folderHash)) + ) + ->executeStatement(); // clear cache if exists if (isset(self::$folderRecordCache[$storageUid . ':' . $identifier])) { @@ -134,14 +160,9 @@ public function deleteFolderRecord($storageUid, $folderHash, $identifier) } } - /** - * Gets a query build - * - * @return QueryBuilder - */ - protected function getQueryBuilder() + protected function getQueryBuilder(): QueryBuilder { return $this->connectionPool->getQueryBuilderForTable('tx_falsecuredownload_folder'); } -} \ No newline at end of file +} diff --git a/Classes/ViewHelpers/DownloadLinkViewHelper.php b/Classes/ViewHelpers/DownloadLinkViewHelper.php index 4765f9b..b5b0333 100644 --- a/Classes/ViewHelpers/DownloadLinkViewHelper.php +++ b/Classes/ViewHelpers/DownloadLinkViewHelper.php @@ -1,7 +1,8 @@ @@ -24,6 +25,8 @@ * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ +namespace BeechIt\FalSecuredownload\ViewHelpers; + use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\ProcessedFile; @@ -32,6 +35,8 @@ /** * Download link view helper. Generates links that force a download action. + * + * @noinspection PhpUnused */ class DownloadLinkViewHelper extends AbstractTagBasedViewHelper { @@ -46,7 +51,7 @@ class DownloadLinkViewHelper extends AbstractTagBasedViewHelper * * @api */ - public function initializeArguments() + public function initializeArguments(): void { parent::initializeArguments(); $this->registerUniversalTagAttributes(); @@ -63,7 +68,7 @@ public function initializeArguments() * * @return string */ - public function render() + public function render(): string { /** @var FileInterface $file */ $file = $this->arguments['file']; diff --git a/Classes/ViewHelpers/LeaveStateViewHelper.php b/Classes/ViewHelpers/LeaveStateViewHelper.php index 35e2e71..ede3aa7 100644 --- a/Classes/ViewHelpers/LeaveStateViewHelper.php +++ b/Classes/ViewHelpers/LeaveStateViewHelper.php @@ -1,7 +1,8 @@ @@ -22,7 +23,9 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ + +namespace BeechIt\FalSecuredownload\ViewHelpers; use BeechIt\FalSecuredownload\Service\LeafStateService; use TYPO3\CMS\Core\Resource\Folder; @@ -30,7 +33,9 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractConditionViewHelper; /** - * Class LeaveStateViewHelper + * Registered as ViewHelper in fluid templates + * + * @noinspection PhpUnused */ class LeaveStateViewHelper extends AbstractConditionViewHelper { @@ -44,11 +49,12 @@ public function initializeArguments() * @param array $arguments * @return bool */ - protected static function evaluateCondition($arguments = null) + protected static function evaluateCondition($arguments = null): bool { /** @var Folder $folder */ $folder = $arguments['folder']; + /** @var LeafStateService $leafStateService */ $leafStateService = GeneralUtility::makeInstance(LeafStateService::class); $feUser = !empty($GLOBALS['TSFE']) ? $GLOBALS['TSFE']->fe_user : false; @@ -60,7 +66,7 @@ protected static function evaluateCondition($arguments = null) * * @return string the rendered string */ - public function render() + public function render(): string { if (static::evaluateCondition($this->arguments)) { return $this->renderThenChild(); diff --git a/Classes/ViewHelpers/Security/AssetAccessViewHelper.php b/Classes/ViewHelpers/Security/AssetAccessViewHelper.php index 04039c0..d7a915e 100644 --- a/Classes/ViewHelpers/Security/AssetAccessViewHelper.php +++ b/Classes/ViewHelpers/Security/AssetAccessViewHelper.php @@ -1,7 +1,8 @@ @@ -22,24 +23,30 @@ * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! - ***************************************************************/ + */ +namespace BeechIt\FalSecuredownload\ViewHelpers\Security; + +use BeechIt\FalSecuredownload\Security\CheckPermissions; use TYPO3\CMS\Core\Context\Context; +use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException; use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\Folder; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractConditionViewHelper; /** - * Asset access ViewHelper + * Registered as ViewHelper in fluid templates + * + * @noinspection PhpUnused */ class AssetAccessViewHelper extends AbstractConditionViewHelper { - public function initializeArguments() + public function initializeArguments(): void { parent::initializeArguments(); $this->registerArgument('folder', 'object', '', true); - $this->registerArgument('file', 'object', '', false, null); + $this->registerArgument('file', 'object', ''); } /** @@ -47,8 +54,9 @@ public function initializeArguments() * otherwise renders child. * * @return string + * @throws AspectNotFoundException */ - public function render() + public function render(): string { return self::evaluateCondition($this->arguments) ? $this->renderThenChild() : $this->renderElseChild(); } @@ -58,16 +66,17 @@ public function render() * * @param array $arguments * @return bool + * @throws AspectNotFoundException */ - protected static function evaluateCondition($arguments = null) + protected static function evaluateCondition($arguments = null): bool { /** @var Folder $folder */ $folder = $arguments['folder']; /** @var File $file */ $file = $arguments['file']; - /** @var $checkPermissionsService \BeechIt\FalSecuredownload\Security\CheckPermissions */ - $checkPermissionsService = GeneralUtility::makeInstance('BeechIt\\FalSecuredownload\\Security\\CheckPermissions'); + /** @var $checkPermissionsService CheckPermissions */ + $checkPermissionsService = GeneralUtility::makeInstance(CheckPermissions::class); $userFeGroups = self::getFeUserGroups(); $access = false; @@ -91,10 +100,12 @@ protected static function evaluateCondition($arguments = null) /** * Determines whether the currently logged in FE user belongs to the specified usergroup * - * @return boolean|array FALSE when not logged in or else frontend.user.groupIds + * @return bool|array FALSE when not logged in or else frontend.user.groupIds + * @throws AspectNotFoundException */ protected static function getFeUserGroups() { + /** @var Context $context */ $context = GeneralUtility::makeInstance(Context::class); if (!$context->getPropertyFromAspect('frontend.user', 'isLoggedIn')) { return false; diff --git a/Configuration/Backend/AjaxRoutes.php b/Configuration/Backend/AjaxRoutes.php index 8e6ec4f..8515366 100644 --- a/Configuration/Backend/AjaxRoutes.php +++ b/Configuration/Backend/AjaxRoutes.php @@ -1,7 +1,10 @@ [ 'path' => '/fal_securedownloads/dump_file', - 'target' => BeechIt\FalSecuredownload\Controller\BePublicUrlController::class . '::dumpFile' + 'target' => BePublicUrlController::class . '::dumpFile' ] ]; diff --git a/Configuration/FlexForms/FileTree.xml b/Configuration/FlexForms/FileTree.xml index cc1f2b7..a932e9e 100644 --- a/Configuration/FlexForms/FileTree.xml +++ b/Configuration/FlexForms/FileTree.xml @@ -1,61 +1,65 @@ - - 1 - - - - + + 1 + + + + - - LLL:EXT:fal_securedownload/Resources/Private/Language/locallang_be.xlf:flexform.sheetTitle - - array - + + + LLL:EXT:fal_securedownload/Resources/Private/Language/locallang_be.xlf:flexform.sheetTitle + + + array + - - - - reload - - select - selectSingle - db - 1 - 1 - 1 - - - LLL:EXT:fal_securedownload/Resources/Private/Language/locallang_be.xlf:flexform.storage.empty - - - - sys_file_storage - ORDER BY sys_file_storage.name - - - + + + + reload + + select + selectSingle + db + 1 + 1 + 1 + + + + LLL:EXT:fal_securedownload/Resources/Private/Language/locallang_be.xlf:flexform.storage.empty + + + + + sys_file_storage + ORDER BY sys_file_storage.name + + + - - - - FIELD:settings.storage:REQ:true - - select - selectSingle - - - LLL:EXT:fal_securedownload/Resources/Private/Language/locallang_be.xlf:flexform.folder.selectStorage - - - - BeechIt\FalSecuredownload\Service\UserFileMountService->renderFlexFormSelectDropdown - - - + + + + FIELD:settings.storage:REQ:true + + select + selectSingle + + BeechIt\FalSecuredownload\Service\UserFileMountService->renderFlexFormSelectDropdown + + + + - + - - - - \ No newline at end of file + + + + diff --git a/Configuration/JavaScriptModules.php b/Configuration/JavaScriptModules.php new file mode 100644 index 0000000..489a59b --- /dev/null +++ b/Configuration/JavaScriptModules.php @@ -0,0 +1,14 @@ + [ + 'backend', + 'core', + ], + 'tags' => [ + 'backend.contextmenu', + ], + 'imports' => [ + '@beechit/fal-securedownload/' => 'EXT:fal_securedownload/Resources/Public/JavaScript/', + ], +]; diff --git a/Configuration/RequestMiddlewares.php b/Configuration/RequestMiddlewares.php index e2940a5..51194ac 100644 --- a/Configuration/RequestMiddlewares.php +++ b/Configuration/RequestMiddlewares.php @@ -1,9 +1,11 @@ [ 'beechit/eid-frontend/authentication' => [ - 'target' => \BeechIt\FalSecuredownload\Middleware\EidFrontendAuthentication::class, + 'target' => EidFrontendAuthentication::class, 'after' => [ 'typo3/cms-core/normalized-params-attribute', ], diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 1fc234b..e67ffd1 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -53,6 +53,11 @@ services: method: 'postFolderRename' event: TYPO3\CMS\Core\Resource\Event\AfterFolderRenamedEvent + BeechIt\FalSecuredownload\EventListener\ModifyFileDumpEventListener: + tags: + - name: event.listener + event: TYPO3\CMS\Core\Resource\Event\ModifyFileDumpEvent + BeechIt\FalSecuredownload\EventListener\ModifyIconForResourcePropertiesEventListener: tags: - name: event.listener diff --git a/Configuration/TCA/Overrides/fe_users.php b/Configuration/TCA/Overrides/fe_users.php index 28cad38..dbcf9c8 100644 --- a/Configuration/TCA/Overrides/fe_users.php +++ b/Configuration/TCA/Overrides/fe_users.php @@ -1,7 +1,11 @@ 20, 'items' => [ [ - 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.any_login', - -2 + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.any_login', + 'value' => -2 ], [ - 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.usergroups', - '--div--' + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.usergroups', + 'value' => '--div--' ] ], 'exclusiveKeys' => '-1,-2', @@ -27,5 +31,12 @@ ] ]; -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns('sys_file_metadata', $additionalColumns); -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes('sys_file_metadata', 'fe_groups'); +$typo3Version = new Typo3Version(); +if ($typo3Version->getMajorVersion() === 11) { + foreach ($additionalColumns['fe_groups']['config']['items'] as &$item) { + $item = array_values($item); + } +} + +ExtensionManagementUtility::addTCAcolumns('sys_file_metadata', $additionalColumns); +ExtensionManagementUtility::addToAllTCAtypes('sys_file_metadata', 'fe_groups'); diff --git a/Configuration/TCA/Overrides/sys_template.php b/Configuration/TCA/Overrides/sys_template.php index 91ae7c2..9e1e7f6 100644 --- a/Configuration/TCA/Overrides/sys_template.php +++ b/Configuration/TCA/Overrides/sys_template.php @@ -1,4 +1,7 @@ [ 'title' => 'LLL:EXT:fal_securedownload/Resources/Private/Language/locallang_db.xlf:tx_falsecuredownload_folder', 'label' => 'folder', @@ -63,12 +66,12 @@ 'maxitems' => 40, 'items' => [ [ - 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.any_login', - -2 + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.any_login', + 'value' => -2 ], [ - 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.usergroups', - '--div--' + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.usergroups', + 'value' => '--div--' ] ], 'exclusiveKeys' => '-1,-2', @@ -78,3 +81,12 @@ ] ] ]; + +$typo3Version = new Typo3Version(); +if ($typo3Version->getMajorVersion() === 11) { + foreach ($tca['columns']['fe_groups']['config']['items'] as &$item) { + $item = array_values($item); + } +} + +return $tca; diff --git a/Configuration/TypoScript/constants.txt b/Configuration/TypoScript/constants.txt deleted file mode 100644 index a745cfb..0000000 --- a/Configuration/TypoScript/constants.txt +++ /dev/null @@ -1,11 +0,0 @@ - -plugin.tx_falsecuredownload { - view { - # cat=plugin.tx_falsecuredownload/file; type=string; label=Path to template overrides (FE) - templateRootPath = - # cat=plugin.tx_falsecuredownload/file; type=string; label=Path to template partial overrides (FE) - partialRootPath = - # cat=plugin.tx_falsecuredownload/file; type=string; label=Path to template layout overrides (FE) - layoutRootPath = - } -} diff --git a/Configuration/TypoScript/constants.typoscript b/Configuration/TypoScript/constants.typoscript new file mode 100644 index 0000000..452cf0f --- /dev/null +++ b/Configuration/TypoScript/constants.typoscript @@ -0,0 +1,15 @@ +plugin.tx_falsecuredownload { + view { + # cat=plugin.tx_falsecuredownload/file; type=string; label=Path to template overrides (FE) + templateRootPath = + # cat=plugin.tx_falsecuredownload/file; type=string; label=Path to template partial overrides (FE) + partialRootPath = + # cat=plugin.tx_falsecuredownload/file; type=string; label=Path to template layout overrides (FE) + layoutRootPath = + } + + settings { + # cat=plugin.tx_falsecuredownload/settings; type=bool; label=Whether to include Javascript to open/collapse folders + includeJavascript = 1 + } +} diff --git a/Configuration/TypoScript/setup.txt b/Configuration/TypoScript/setup.txt deleted file mode 100644 index 9ccd913..0000000 --- a/Configuration/TypoScript/setup.txt +++ /dev/null @@ -1,11 +0,0 @@ - -plugin.tx_falsecuredownload { - view { - templateRootPaths.0 = EXT:fal_securedownload/Resources/Private/Templates/ - templateRootPaths.10 = {$plugin.tx_falsecuredownload.view.templateRootPath} - partialRootPaths.0 = EXT:fal_securedownload/Resources/Private/Partials/ - partialRootPaths.10 = {$plugin.tx_falsecuredownload.view.partialRootPath} - layoutRootPaths.0 = EXT:fal_securedownload/Resources/Private/Layouts/ - layoutRootPaths.10 = {$plugin.tx_falsecuredownload.view.layoutRootPath} - } -} diff --git a/Configuration/TypoScript/setup.typoscript b/Configuration/TypoScript/setup.typoscript new file mode 100644 index 0000000..86141b9 --- /dev/null +++ b/Configuration/TypoScript/setup.typoscript @@ -0,0 +1,12 @@ +plugin.tx_falsecuredownload { + view { + templateRootPaths.0 = EXT:fal_securedownload/Resources/Private/Templates/ + templateRootPaths.10 = {$plugin.tx_falsecuredownload.view.templateRootPath} + partialRootPaths.0 = EXT:fal_securedownload/Resources/Private/Partials/ + partialRootPaths.10 = {$plugin.tx_falsecuredownload.view.partialRootPath} + layoutRootPaths.0 = EXT:fal_securedownload/Resources/Private/Layouts/ + layoutRootPaths.10 = {$plugin.tx_falsecuredownload.view.layoutRootPath} + } + + settings.includeJavascript = {$plugin.tx_falsecuredownload.settings.includeJavascript} +} diff --git a/Documentation/Misc/Index.rst b/Documentation/Misc/Index.rst index ec23d3c..601788d 100644 --- a/Documentation/Misc/Index.rst +++ b/Documentation/Misc/Index.rst @@ -24,7 +24,7 @@ Instead of throwing a "Authentication required!" message you can redirect the us .. code-block:: /login/?redirect_url=###REQUEST_URI### - + # Or a typolink t3://page?uid=5&redirect_url=###REQUEST_URI### @@ -39,7 +39,7 @@ Instead of throwing a "Access denied" message you can redirect the user to a cer .. code-block:: /no-access/?redirect_url=###REQUEST_URI### - + # Or a typolink t3://page?uid=5&redirect_url=###REQUEST_URI### @@ -112,117 +112,128 @@ To have correct urls to indexed files you need to add/adjust following ext:solr *This feature is sponsored by: STIMME DER HOFFNUNG Adventist Media Center* -Signals and slots -================= +EventListeners +============== BeforeRedirects --------------- -This signal will be fired everytime a file is going to download or display. This signal will not be fired, if +This event will be fired everytime a file is going to download or display. This event will not be fired, if access to requested file is restricted for current logged in frontend user. So you can modify some redirect params if needed. -Example of how to register a slot for this signal (in your ext_localconf.php): +Example of how to register a listener for this event in your `EXT:my_extension/Configuration/Services.yaml`: -.. code-block:: php +.. code-block:: yaml - /** Define a redirect page for inaccessible file resources */ - /** @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher */ - $signalSlotDispatcher = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class); - $signalSlotDispatcher->connect( - 'BeechIt\FalSecuredownload\Hooks\FileDumpHook', - 'BeforeRedirects', - 'endor\ExtensionName\Slot\BeforeRedirectsSlot', - 'beforeRedirects' - ); + services: + Vendor\MyExtension\EventListener\BeforeRedirectsEventListener: + tags: + - name: event.listener + identifier: 'myBeforeRedirectsEventListener' + event: BeechIt\FalSecuredownload\Events\BeforeRedirectsEvent + +An example listener `EXT:my_extension/Classes/EventListener/BeforeRedirectsEventListener.php` could look like this: .. code-block:: php $loginRedirectUrl, - 'noAccessRedirectUrl' => $noAccessRedirectUrl, - 'file' => $file, - 'caller' => $caller, - ]; - } - } - -That way you can modify these params if needed 'loginRedirectUrl', 'noAccessRedirectUrl', 'file', 'caller' + public function __invoke(BeforeRedirectsEvent $event): void + { + $event->setLoginRedirectUrl('XXX'); + $event->setNoAccessRedirectUrl('XXX'); + } + +That way you can modify 'loginRedirectUrl', 'noAccessRedirectUrl', 'file', 'caller' if needed. BeforeFileDump -------------- -This signal will be fired everytime a file is going to download or display. This signal will not be fired, if +This event will be fired everytime a file is going to download or display. This event will not be fired, if access to requested file is restricted for current logged in frontend user. BeforeFileDump is useful for e.g. tracking access of downloaded files. -Example of how to register a slot for this signal (in your ext_localconf.php): +Example of how to register a listener for this event in your `EXT:my_extension/Configuration/Services.yaml`: + +.. code-block:: yaml + + services: + Vendor\MyExtension\EventListener\BeforeFileDumpEventListener: + tags: + - name: event.listener + identifier: 'myBeforeFileDumpEventListener' + event: BeechIt\FalSecuredownload\Events\BeforeFileDumpEvent + +An example listener `EXT:my_extension/Classes/EventListener/BeforeFileDumpEventListener.php` could look like this: .. code-block:: php - /** @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher */ - $signalSlotDispatcher = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher'); - $signalSlotDispatcher->connect( - 'BeechIt\FalSecuredownload\Hooks\FileDumpHook', - 'BeforeFileDump', - 'Vendor\ExtensionName\Slot\BeforeFileDumpSlot', - 'logFileDump' - ); + setFile('XXX'); + } + +That way you can modify 'file', 'caller' if needed. AddCustomGroups --------------- -This signal is fired every time, the permissions are checked. It will add new groups to the list of authenticated groups, +This event is fired every time when the permissions are checked. It will add new groups to the list of authenticated groups, which are not detected by the standard group mechanism. An example is, if you are using ip based authentication, where no frontend user is logged in. The slot must return an array which contains the array of the custom usergroups. This array will then be merged with the original array of groups. -.. code-block:: php +Example of how to register a listener for this event in your `EXT:my_extension/Configuration/Services.yaml`: - public function addCustomGroups($customGroups) - { - // add your group ids here - return array($customGroups); - } +.. code-block:: yaml -EXT:fal_securedownload vs EXT:naw_securedl -========================================== + services: + Vendor\MyExtension\EventListener\AddCustomGroupsEventListener: + tags: + - name: event.listener + identifier: 'myAddCustomGroupsEventListener' + event: BeechIt\FalSecuredownload\Events\AddCustomGroupsEvent -* fal_securedownload uses the FAL API to create secure links instead of checking/changing all links found in the HTML output. -* fal_securedownload supports remote storages. -* fal_securedownload requires proper use of the FAL API so extensions that do not use `$file->getPublicUrl()` to create links to your files or not `secured`. But that would also mean remote and non public storages are not supported. -* With fal_securedownload editors can set the permissions for files/folders by fe_group in the BE File list module. -* Links created by fal_securedownload are exchangeable with other users without the risk that people get access to files they are not allowed to access as a FE login is required to get access. +An example listener `EXT:my_extension/Classes/EventListener/AddCustomGroupsEventListener.php` could look like this: - * fal_securedownload 'secured' links don't have a expiration date and are only usable for users with a FE login. - * Links do not change over time. +.. code-block:: php + + setCustomUserGroups($myCustomGroups); + } Known issues ============ -* My FileDumpEID hook isn't executed - The DownloadLinkViewHelper used in the FileTree plugin adds a &download to the asset link. - The hook that is used to check if you have permissions to access the asset will force a download when this parameter is set. - Problem with this is that all other FileDumpEID hooks registered after fal_securedownload will not be executed anymore then. * I got javascript errors after including the provided typoscript template - This is properly because you do not have jQuery available on the FE. You can easily disable the provided javascript be adding this line to you typoscript template + This is properly because you do not have jQuery available on the FE. You can disable the provided javascript by adding this line to your typoscript template .. code-block:: ts - page.jsFooterInline.303030 > + plugin.tx_falsecuredownload.settings.includeJavascript = 0 * Files in my "secure" folder aren't processed by ext:tika If the folder is outside of the document root you need to set `$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']` else ext:tika will not process the files. diff --git a/Documentation/Settings.cfg b/Documentation/Settings.cfg index 502db9e..0edafca 100644 --- a/Documentation/Settings.cfg +++ b/Documentation/Settings.cfg @@ -11,20 +11,20 @@ # ... (required) title (displayed in left sidebar (desktop) or top panel (mobile) # ................................................................................. -project = FAL Secure Download +project = FAL Secure Download # ................................................................................. # ... (recommended) version, displayed next to title (desktop) and in = 2.3) in your file storage root folder or move your storage outside of your webroot +3. Add a .htaccess file with "Require all denied" in your file storage root + folder or move your storage outside your webroot 4. Go to the file list and add access restrictions on file/folder ### Features @@ -24,8 +28,10 @@ The access to assets can be set on folder/file bases by setting access to fe_gro - Keep track of requested downloads (count downloads per user and file) ### Requirements -- TYPO3 11 LTS + +- TYPO3 11 LTS or TYPO3 12 LTS ### Suggestions + - EXT:ke_search v4.3.1 - EXT:solrfal v4.1.0 diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index 427371c..2f1d07e 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -1,6 +1,7 @@ - +
@@ -8,4 +9,4 @@ - \ No newline at end of file + diff --git a/Resources/Private/Language/locallang_be.xlf b/Resources/Private/Language/locallang_be.xlf index 497d3bf..76b4362 100644 --- a/Resources/Private/Language/locallang_be.xlf +++ b/Resources/Private/Language/locallang_be.xlf @@ -1,6 +1,7 @@ - +
@@ -18,19 +19,20 @@ Folder - - - First select a storage - - Folder permissions - Instead of throwing a "Authentication required!" message redirect the user to this url (optional variable = ###REQUEST_URI###) + Instead of throwing an "Authentication required!" message redirect the user to this url + (optional variable = ###REQUEST_URI###) + - Instead of throwing a "Access denied" message redirect the user to this url (optional variable = ###REQUEST_URI###) + Instead of throwing an "Access denied" message redirect the user to this url (optional variable + = ###REQUEST_URI###) + Force download of all files from protected/non-public storages diff --git a/Resources/Private/Language/locallang_db.xlf b/Resources/Private/Language/locallang_db.xlf index 5f91bd2..d8fd88c 100644 --- a/Resources/Private/Language/locallang_db.xlf +++ b/Resources/Private/Language/locallang_db.xlf @@ -1,6 +1,7 @@ - +
@@ -16,4 +17,4 @@ - \ No newline at end of file + diff --git a/Resources/Private/Layouts/Default.html b/Resources/Private/Layouts/Default.html index 449a54c..95615b1 100644 --- a/Resources/Private/Layouts/Default.html +++ b/Resources/Private/Layouts/Default.html @@ -1,9 +1,17 @@ + +
- +
- + + + + + diff --git a/Resources/Private/Partials/FileTree/Leaf.html b/Resources/Private/Partials/FileTree/Leaf.html index 0086db4..5e11bfb 100644 --- a/Resources/Private/Partials/FileTree/Leaf.html +++ b/Resources/Private/Partials/FileTree/Leaf.html @@ -1,21 +1,24 @@ + xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" + xmlns:sd="http://typo3.org/ns/BeechIt/FalSecuredownload/ViewHelpers" + data-namespace-typo3-fluid="true">
    - - -
  • {subFolder.name} - -
  • -
    -
    - - -
  • {file.name}
  • -
    -
    + + +
  • {subFolder.name} + +
  • +
    +
    + + +
  • + {file.name} +
  • +
    +
- \ No newline at end of file + diff --git a/Resources/Private/Templates/FileTree/Tree.html b/Resources/Private/Templates/FileTree/Tree.html index 89cd24f..2d3800f 100644 --- a/Resources/Private/Templates/FileTree/Tree.html +++ b/Resources/Private/Templates/FileTree/Tree.html @@ -1,25 +1,25 @@ + xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" + xmlns:sd="http://typo3.org/ns/BeechIt/FalSecuredownload/ViewHelpers" + data-namespace-typo3-fluid="true"> - + - - -

{folder.name}

- - - -
+ + +

{folder.name}

+ + + +
- - No folder selected - -
+ + No folder selected + +
- \ No newline at end of file + diff --git a/Resources/Public/Icons/folder.svg b/Resources/Public/Icons/folder.svg index f09179d..68f1c1a 100644 --- a/Resources/Public/Icons/folder.svg +++ b/Resources/Public/Icons/folder.svg @@ -1,60 +1,31 @@ - - - - - image/svg+xml - - - - - - - - - - + xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 16 16" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + + + + image/svg+xml + + Folder + + + + + d="m 4.392369,12.471088 -3.86678643,-0.01877 -4e-8,-11.1310884 5.04934727,4e-7 2.046018,1.9897056 7.9025092,-0.018771 0.01877,9.0475294 0,0" + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/> + + + + + + + - - + d="m 11.223611,10.237362 c -0.335,0.362 -0.804,0.6 -1.204,0.6 1.07,-0.022 2.055,-1.4079999 2.154,-2.3999999 v -1.4 c 0,-1 -0.8,-1.8 -1.9,-1.8 H 9.7736112 c -1,0 -1.9,0.8 -1.9,1.8 v 1.4 c 0,0.622 0.387,1.4 0.945,1.8979999 -0.015,0.96 -0.24,1.31 -0.32,1.532 -0.058,-0.01 -0.086,-0.02 -0.075,-0.03 -0.7,0.3 -1.5,0.7 -2.5,1 0,0 -0.9,0.5 -0.9,2.4 H 15.023612 c -0.300001,-2 -1.100001,-2.4 -1.100001,-2.4 -1.03,-0.28 -1.71,-0.65 -2.366,-0.94 -0.057,-0.23 -0.335,-0.65 -0.335,-1.66 z"/> - - diff --git a/Resources/Public/Icons/overlay-inherited-permissions.svg b/Resources/Public/Icons/overlay-inherited-permissions.svg index 06e3c10..ac690f0 100644 --- a/Resources/Public/Icons/overlay-inherited-permissions.svg +++ b/Resources/Public/Icons/overlay-inherited-permissions.svg @@ -1,65 +1,53 @@ - - - - image/svg+xml - - - - - - - - - + xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 11 11" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + id="svg2" + inkscape:version="0.48.4 r9939" + sodipodi:docname="overlay-inherited-permissions.svg"> + + + + image/svg+xml + + User + + + + + + + diff --git a/Resources/Public/JavaScript/CollapsibleFolderTree.js b/Resources/Public/JavaScript/CollapsibleFolderTree.js index 652c3c8..4d9fbcf 100644 --- a/Resources/Public/JavaScript/CollapsibleFolderTree.js +++ b/Resources/Public/JavaScript/CollapsibleFolderTree.js @@ -1,22 +1,25 @@ -$('.fal-securedownload ul li span.icon-folder').on('click', function() { - var $leef = $(this).parent('li'); - if ($('span.icon-folder:first', $leef).hasClass('icon-folder-open')) { - $leef.find('ul:first').slideUp('fast', function(){$('span.icon-folder:first', $leef).removeClass('icon-folder-open')}); - $.get('/index.php?eID=FalSecuredownloadFileTreeState',{ - folder: $('span.icon-folder:first', $leef).data('folder'), - open: '' - }); - } else { - $leef.find('ul:first').slideDown('fast', function(){$('span.icon-folder:first', $leef).addClass('icon-folder-open')}); - $.get('/index.php?eID=FalSecuredownloadFileTreeState',{ - folder: $('span.icon-folder:first', $leef).data('folder'), - open: 1 - }); - } -}); -$('.fal-securedownload ul li span.icon-folder').each(function() { - $(this).next('ul').hide(); - if ($(this).hasClass('icon-folder-open')) { - $(this).next('ul').slideDown(); - } +$('.fal-securedownload ul li span.icon-folder').on('click', function () { + var $leaf = $(this).parent('li'); + if ($('span.icon-folder:first', $leaf).hasClass('icon-folder-open')) { + $leaf.find('ul:first').slideUp('fast', function () { + $('span.icon-folder:first', $leaf).removeClass('icon-folder-open') + }); + $.get('/index.php?eID=FalSecuredownloadFileTreeState', { + folder: $('span.icon-folder:first', $leaf).data('folder'), + open: '' + }); + } else { + $leaf.find('ul:first').slideDown('fast', function () { + $('span.icon-folder:first', $leaf).addClass('icon-folder-open') + }); + $.get('/index.php?eID=FalSecuredownloadFileTreeState', { + folder: $('span.icon-folder:first', $leaf).data('folder'), + open: 1 + }); + } +}).each(function () { + $(this).next('ul').hide(); + if ($(this).hasClass('icon-folder-open')) { + $(this).next('ul').slideDown(); + } }); diff --git a/Resources/Public/JavaScript/ContextMenuActions.js b/Resources/Public/JavaScript/ContextMenuActions.js index 8748fce..0de6f6a 100644 --- a/Resources/Public/JavaScript/ContextMenuActions.js +++ b/Resources/Public/JavaScript/ContextMenuActions.js @@ -2,46 +2,50 @@ * Module: TYPO3/CMS/FalSecuredownload/ContextMenuActions * * JavaScript to handle the click action of the "FalSecuredownload" context menu item + * + * Used in TYPO3 v11 only, v12 uses ES6 modules + * + * @see https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/ApiOverview/Backend/JavaScript/ES6/Index.html#migration-from-requirejs * @exports TYPO3/CMS/FalSecuredownload/ContextMenuActions */ define(function () { - 'use strict'; + 'use strict'; - /** - * @exports TYPO3/CMS/FalSecuredownload/ContextMenuActions - */ - var ContextMenuActions = {}; + /** + * @exports TYPO3/CMS/FalSecuredownload/ContextMenuActions + */ + var ContextMenuActions = {}; - /** - * Open folder permissions edit form - * - * @param {string} table - * @param {string} uid combined folder identifier - */ - ContextMenuActions.folderPermissions = function (table, uid) { - var folderRecordUid = this.data('folderRecordUid') || 0; + /** + * Open folder permissions edit form + * + * @param {string} table + * @param {string} uid combined folder identifier + */ + ContextMenuActions.folderPermissions = function (table, uid) { + var folderRecordUid = this.data('folderRecordUid') || 0; - if (folderRecordUid > 0) { - top.TYPO3.Backend.ContentContainer.setUrl( - top.TYPO3.settings.FormEngine.moduleUrl - + '&edit[tx_falsecuredownload_folder][' + parseInt(folderRecordUid, 10) + ']=edit' - + '&returnUrl=' + ContextMenuActions.getReturnUrl() - ); - } else { - top.TYPO3.Backend.ContentContainer.setUrl( - top.TYPO3.settings.FormEngine.moduleUrl - + '&edit[tx_falsecuredownload_folder][0]=new' - + '&defVals[tx_falsecuredownload_folder][storage]=' + this.data('storage') - + '&defVals[tx_falsecuredownload_folder][folder]=' + this.data('folder') - + '&defVals[tx_falsecuredownload_folder][folder_hash]=' + this.data('folderHash') - + '&returnUrl=' + ContextMenuActions.getReturnUrl() - ); - } - }; + if (folderRecordUid > 0) { + top.TYPO3.Backend.ContentContainer.setUrl( + top.TYPO3.settings.FormEngine.moduleUrl + + '&edit[tx_falsecuredownload_folder][' + parseInt(folderRecordUid, 10) + ']=edit' + + '&returnUrl=' + ContextMenuActions.getReturnUrl() + ); + } else { + top.TYPO3.Backend.ContentContainer.setUrl( + top.TYPO3.settings.FormEngine.moduleUrl + + '&edit[tx_falsecuredownload_folder][0]=new' + + '&defVals[tx_falsecuredownload_folder][storage]=' + this.data('storage') + + '&defVals[tx_falsecuredownload_folder][folder]=' + this.data('folder') + + '&defVals[tx_falsecuredownload_folder][folder_hash]=' + this.data('folderHash') + + '&returnUrl=' + ContextMenuActions.getReturnUrl() + ); + } + }; - ContextMenuActions.getReturnUrl = function () { - return encodeURIComponent(top.list_frame.document.location.pathname + top.list_frame.document.location.search); - }; + ContextMenuActions.getReturnUrl = function () { + return encodeURIComponent(top.list_frame.document.location.pathname + top.list_frame.document.location.search); + }; - return ContextMenuActions; -}); \ No newline at end of file + return ContextMenuActions; +}); diff --git a/Resources/Public/JavaScript/context-menu-actions.js b/Resources/Public/JavaScript/context-menu-actions.js new file mode 100644 index 0000000..3d1dc19 --- /dev/null +++ b/Resources/Public/JavaScript/context-menu-actions.js @@ -0,0 +1,32 @@ +/** + * JavaScript to handle the click action of the "FalSecuredownload" context menu item + * Used in TYPO3 >= v12 + */ +class ContextMenuActions { + static getReturnUrl() { + return encodeURIComponent(top.list_frame.document.location.pathname + top.list_frame.document.location.search) + } + + static folderPermissions() { + var folderRecordUid = this.data('folderRecordUid') || 0; + + if (folderRecordUid > 0) { + top.TYPO3.Backend.ContentContainer.setUrl( + top.TYPO3.settings.FormEngine.moduleUrl + + '&edit[tx_falsecuredownload_folder][' + parseInt(folderRecordUid, 10) + ']=edit' + + '&returnUrl=' + ContextMenuActions.getReturnUrl() + ); + } else { + top.TYPO3.Backend.ContentContainer.setUrl( + top.TYPO3.settings.FormEngine.moduleUrl + + '&edit[tx_falsecuredownload_folder][0]=new' + + '&defVals[tx_falsecuredownload_folder][storage]=' + this.data('storage') + + '&defVals[tx_falsecuredownload_folder][folder]=' + this.data('folder') + + '&defVals[tx_falsecuredownload_folder][folder_hash]=' + this.data('folderHash') + + '&returnUrl=' + ContextMenuActions.getReturnUrl() + ); + } + } +} + +export default ContextMenuActions; diff --git a/composer.json b/composer.json index a5c8618..d172611 100644 --- a/composer.json +++ b/composer.json @@ -1,45 +1,46 @@ { - "name": "beechit/fal-securedownload", - "description": "Secure download of assets. Makes it possible to secure FE use of assets/files by setting permissions to folders/files for fe_groups.", - "type": "typo3-cms-extension", - "keywords": [ - "typo3", - "TYPO3 CMS", - "FAL", - "secure download" - ], - "license": "GPL-2.0-or-later", - "authors": [ - { - "name": "Frans Saris", - "email": "t3ext@beech.it", - "homepage": "http://www.beech.it", - "role": "Lead Developer" - } - ], - "homepage": "https://github.com/beechit/fal_securedownload/", - "support": { - "email": "t3ext@beech.it", - "issues": "https://github.com/beechit/fal_securedownload/issues", - "source": "https://github.com/beechit/fal_securedownload" - }, - "require": { - "typo3/cms-core": "^11.5" - }, - "replace": { - "typo3-ter/fal-securedownload": "self.version" - }, - "autoload": { - "psr-4": { - "BeechIt\\FalSecuredownload\\": "Classes/" - } - }, - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" + "name": "beechit/fal-securedownload", + "description": "Secure download of assets. Makes it possible to secure FE use of assets/files by setting permissions to folders/files for fe_groups.", + "type": "typo3-cms-extension", + "keywords": [ + "typo3", + "TYPO3 CMS", + "FAL", + "secure download" + ], + "license": "GPL-2.0-or-later", + "authors": [ + { + "name": "Frans Saris", + "email": "t3ext@beech.it", + "homepage": "https://www.beech.it/", + "role": "Lead Developer" + } + ], + "homepage": "https://github.com/beechit/fal_securedownload/", + "support": { + "email": "t3ext@beech.it", + "issues": "https://github.com/beechit/fal_securedownload/issues", + "source": "https://github.com/beechit/fal_securedownload" + }, + "require": { + "typo3/cms-core": "^11.5 || ^12.4", + "php": ">= 7.4 < 8.3" + }, + "replace": { + "typo3-ter/fal-securedownload": "self.version" + }, + "autoload": { + "psr-4": { + "BeechIt\\FalSecuredownload\\": "Classes/" + } }, - "typo3/cms": { - "extension-key": "fal_securedownload" + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, + "typo3/cms": { + "extension-key": "fal_securedownload" + } } - } } diff --git a/dynamicReturnTypeMeta.json b/dynamicReturnTypeMeta.json deleted file mode 100644 index 5976c82..0000000 --- a/dynamicReturnTypeMeta.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - // configuration file for PHPStorm Plugin: http://plugins.jetbrains.com/plugin/7251 - "methodCalls": [ - { - "class": "\\TYPO3\\CMS\\Core\\Utility\\GeneralUtility", - "method": "makeInstance", - "position": 0 - }, - { - "class": "\\TYPO3\\CMS\\Extbase\\Object\\ObjectManagerInterface", - "method": "get", - "position": 0 - }, - { - "class": "\\PHPUnit_Framework_TestCase", - "method": "prophesize", - "position": 0, - "mask": "%s|\\Prophecy\\Prophecy\\ObjectProphecy" - }, - { - "class": "\\PHPUnit_Framework_TestCase", - "method": "getMock", - "position": 0, - "mask": "%s|\\PHPUnit_Framework_MockObject_MockObject" - }, - { - "class": "\\PHPUnit_Framework_TestCase", - "method": "createMock", - "position": 0, - "mask": "%s|\\PHPUnit_Framework_MockObject_MockObject" - }, - { - "class": "\\TYPO3\\CMS\\Components\\TestingFramework\\Core\\BaseTestCase", - "method": "getAccessibleMock", - "position": 0, - "mask": "%s|\\PHPUnit_Framework_MockObject_MockObject|\\TYPO3\\CMS\\Components\\TestingFramework\\Core\\AccessibleObjectInterface" - }, - { - "class": "\\TYPO3\\CMS\\Components\\TestingFramework\\Core\\BaseTestCase", - "method": "getAccessibleMockForAbstractClass", - "position": 0, - "mask": "%s|\\PHPUnit_Framework_MockObject_MockObject|\\TYPO3\\CMS\\Components\\TestingFramework\\Core\\AccessibleObjectInterface" - } - ], - "functionCalls": [ - ] -} diff --git a/ext_emconf.php b/ext_emconf.php index 96debbc..1971ea2 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -16,7 +16,7 @@ 'version' => '4.0.3', 'constraints' => [ 'depends' => [ - 'typo3' => '11.5.0 - 11.5.99', + 'typo3' => '11.5.0 - 12.4.99', ], 'conflicts' => [], 'suggests' => [ diff --git a/ext_icon.png b/ext_icon.png deleted file mode 100644 index fb9b4c2bbc2c94a4f777e5c3d953dea265f7b846..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 720 zcmV;>0x$iEP)Q5eU6=iGPZUQ_QKgBcNt&>&x>Nl_%pLP-{EP)t*^8f(^47Ru^ckc9;ncG5Hn z$wpF4h8c|SG30W;@4WAM&pD699qz1jPo2)`>HL4s|2+Mbh|tP9Dmzt|fpo_ES>$xI?G%C|#pH zn}?`QbvjO|i96e#XR_G*pNXHuZEC>1g@^^m2zk1LbU5d@t~JhF6b7+`hZK{t7K-?yV@KHLV-Bua}n zIMa@JjjMfT=Er@!`GHDJkxF+n{-BQP>|t@&D5Olg4rKYi0iz`2?F^<4VrnFnGGXrt zy0)$bpl!h*TLzl5QNet0I%onCO)#&A`Rn=t4F%tdx$F`wuIO+;;LkporSoW zoq$9{T!gQWHXgeQVvG2sfVdLa;%vQ)xEe?d$()Q3BjPPS`Pt~?N{B7a-V3vx%l`l( z7UH=8BcgDK{XHuzHf_5SVk=Ftj~d^!a{yv|!Pb9uB-Qp1A8L^{@FB<|#KnL?bmLUo z;i5>y__zs(;tTIDAL>Kciz57QrUg)s4PLJQE&Txw*NhpH^5>@j0000 'tree', + FileTreeController::class => 'tree', ], // non-cacheable actions [ - BeechIt\FalSecuredownload\Controller\FileTreeController::class => 'tree', + FileTreeController::class => 'tree', ] ); // FE FileTree leaf open/close state dispatcher -$GLOBALS['TYPO3_CONF_VARS']['FE']['eID_include']['FalSecuredownloadFileTreeState'] = - \BeechIt\FalSecuredownload\Controller\FileTreeStateController::class . '::saveLeafState'; - -// FileDumpEID hook -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['FileDumpEID.php']['checkFileAccess']['FalSecuredownload'] = - \BeechIt\FalSecuredownload\Hooks\FileDumpHook::class; - -if (TYPO3_MODE === 'BE') { +$GLOBALS['TYPO3_CONF_VARS']['FE']['eID_include']['FalSecuredownloadFileTreeState'] = FileTreeStateController::class . '::saveLeafState'; - // Page module hook - $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info']['falsecuredownload_filetree']['fal_securedownload'] = - \BeechIt\FalSecuredownload\Hooks\CmsLayout::class . '->getExtensionSummary'; +// Page module hook +$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info']['falsecuredownload_filetree']['fal_securedownload'] = + CmsLayout::class . '->getExtensionSummary'; - // Add FolderPermission button to docheader of filelist - $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['Backend\Template\Components\ButtonBar']['getButtonsHook']['FalSecuredownload'] = - \BeechIt\FalSecuredownload\Hooks\DocHeaderButtonsHook::class . '->getButtons'; +// Add FolderPermission button to docheader of filelist +$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['Backend\Template\Components\ButtonBar']['getButtonsHook']['FalSecuredownload'] = + DocHeaderButtonsHook::class . '->getButtons'; - // Context menu - $GLOBALS['TYPO3_CONF_VARS']['BE']['ContextMenu']['ItemProviders'][1547242135] - = \BeechIt\FalSecuredownload\ContextMenu\ItemProvider::class; +// Context menu +// Only needed for TYPO3 v11 +// https://docs.typo3.org/c/typo3/cms-core/12.4/en-us/Changelog/12.0/Breaking-96333-AutoConfigurationOfContextMenuItemProviders.html +$GLOBALS['TYPO3_CONF_VARS']['BE']['ContextMenu']['ItemProviders'][1547242135] = ItemProvider::class; - // refresh file tree after change in tx_falsecuredownload_folder record - $GLOBALS ['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] = - \BeechIt\FalSecuredownload\Hooks\ProcessDatamapHook::class; - $GLOBALS ['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][] = - \BeechIt\FalSecuredownload\Hooks\ProcessDatamapHook::class; +// refresh file tree after change in tx_falsecuredownload_folder record +$GLOBALS ['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] = ProcessDatamapHook::class; +$GLOBALS ['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][] = ProcessDatamapHook::class; - // ext:ke_search custom indexer hook - $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['ke_search']['modifyFileIndexEntryFromContentIndexer'][] = \BeechIt\FalSecuredownload\Hooks\KeSearchFilesHook::class; - $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['ke_search']['modifyFileIndexEntry'][] = \BeechIt\FalSecuredownload\Hooks\KeSearchFilesHook::class; +// ext:ke_search custom indexer hook +$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['ke_search']['modifyFileIndexEntryFromContentIndexer'][] = KeSearchFilesHook::class; +$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['ke_search']['modifyFileIndexEntry'][] = KeSearchFilesHook::class; - if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('solrfal')) { - /** @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher */ - $signalSlotDispatcher = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class); +if (ExtensionManagementUtility::isLoaded('solrfal')) { + // TODO Must be made compatible to TYPO3 v12 as there is no TYPO3\CMS\Extbase\SignalSlot\Dispatcher anymore + // https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/10.4/Deprecation-90625-ExtbaseSignalSlotDispatcher.html + // TODO to do this the EventDispatcher implementation of EXT:solrfal must be known + // https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/EventDispatcher/Index.html#eventdispatcher + /** @var Dispatcher $signalSlotDispatcher */ + $signalSlotDispatcher = GeneralUtility::makeInstance(Dispatcher::class); - // @Todo convert this to event listener - // ext:solrfal enrich metadata and generate correct public url slot - $signalSlotDispatcher->connect( - \ApacheSolrForTypo3\Solrfal\Indexing\DocumentFactory::class, - 'fileMetaDataRetrieved', - \BeechIt\FalSecuredownload\Aspects\SolrFalAspect::class, - 'fileMetaDataRetrieved' - ); - } + // @Todo convert this to event listener + // ext:solrfal enrich metadata and generate correct public url slot + $signalSlotDispatcher->connect( + DocumentFactory::class, + 'fileMetaDataRetrieved', + SolrFalAspect::class, + 'fileMetaDataRetrieved' + ); +} - if (\BeechIt\FalSecuredownload\Configuration\ExtensionConfiguration::trackDownloads()) { - // register FormEngine node for rendering download statistics in fe_users - $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1470920616] = [ - 'nodeName' => 'falSecureDownloadStats', - 'priority' => 40, - 'class' => \BeechIt\FalSecuredownload\FormEngine\DownloadStatistics::class, - ]; - } +if (ExtensionConfiguration::trackDownloads()) { + // register FormEngine node for rendering download statistics in fe_users + $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1470920616] = [ + 'nodeName' => 'falSecureDownloadStats', + 'priority' => 40, + 'class' => DownloadStatistics::class, + ]; } + +/** @var IconRegistry $iconRegistry */ +$iconRegistry = GeneralUtility::makeInstance(IconRegistry::class); +$iconRegistry->registerIcon( + 'action-folder', + SvgIconProvider::class, + ['source' => 'EXT:fal_securedownload/Resources/Public/Icons/folder.svg'] +); +$iconRegistry->registerIcon( + 'overlay-inherited-permissions', + SvgIconProvider::class, + ['source' => 'EXT:fal_securedownload/Resources/Public/Icons/overlay-inherited-permissions.svg'] +); diff --git a/ext_tables.php b/ext_tables.php deleted file mode 100644 index 9411fbe..0000000 --- a/ext_tables.php +++ /dev/null @@ -1,19 +0,0 @@ -registerIcon( - 'action-folder', - \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, - [ - 'source' => 'EXT:fal_securedownload/Resources/Public/Icons/folder.svg', - ] -); -$iconRegistry->registerIcon( - 'overlay-inherited-permissions', - \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, - [ - 'source' => 'EXT:fal_securedownload/Resources/Public/Icons/overlay-inherited-permissions.svg', - ] -); diff --git a/ext_tables.sql b/ext_tables.sql index 71aeeb8..a98a286 100644 --- a/ext_tables.sql +++ b/ext_tables.sql @@ -1,13 +1,4 @@ -# -# Tabel structure for table 'tx_falsecuredownload_folder' -# CREATE TABLE tx_falsecuredownload_folder ( - uid int(11) NOT NULL auto_increment, - pid int(11) DEFAULT '0' NOT NULL, - - tstamp int(11) DEFAULT '0' NOT NULL, - crdate int(11) DEFAULT '0' NOT NULL, - # file info data storage int(11) DEFAULT '0' NOT NULL, folder text, @@ -16,32 +7,23 @@ CREATE TABLE tx_falsecuredownload_folder ( # FE permissions fe_groups tinytext, - PRIMARY KEY (uid), - KEY folder (storage,folder_hash) -); - -# -# Table structure for table 'sys_file_metadata' -# -CREATE TABLE sys_file_metadata ( - # FE permissions - fe_groups tinytext + KEY folder (storage, folder_hash) ); CREATE TABLE tx_falsecuredownload_download ( - uid int(11) NOT NULL auto_increment, - pid int(11) DEFAULT '0' NOT NULL, - - tstamp int(11) DEFAULT '0' NOT NULL, - crdate int(11) DEFAULT '0' NOT NULL, - feuser int(11) DEFAULT '0' NOT NULL, file int(11) DEFAULT '0' NOT NULL, - PRIMARY KEY (uid), KEY user (feuser) ); +# Additional structure definitions + +CREATE TABLE sys_file_metadata ( + # FE permissions + fe_groups tinytext +); + CREATE TABLE fe_users ( downloads int(11) DEFAULT '0' NOT NULL );