From 17cb5b9d097ea361d070beff02c6412b5c1c5602 Mon Sep 17 00:00:00 2001 From: medofrh Date: Wed, 1 Jul 2026 12:38:19 +0200 Subject: [PATCH 01/25] Add .DS_Store to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 055339d..c28b5b4 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ public public/* index.html composer.lock +.DS_Store # User-specific stuff: From 00ecd4ba2517f112a1f5e33fb621e07981f73d05 Mon Sep 17 00:00:00 2001 From: medofrh Date: Wed, 1 Jul 2026 22:27:01 +0200 Subject: [PATCH 02/25] replace old utility with strategy pattern for Scalability --- Classes/Controller/ProtectionController.php | 4 ++ Classes/Domain/Model/Protection.php | 44 +--------------- Classes/Middleware/AccessMiddleware.php | 5 +- Classes/Service/AccessService.php | 34 +++++++++++++ .../Utility/Access/AccessUtilityInterface.php | 13 +++++ .../Utility/Access/BeLoginAccessUtility.php | 43 ++++++++++++++++ .../Utility/Access/FeLoginAccessUtility.php | 46 +++++++++++++++++ Configuration/Services.yaml | 16 ++++++ ...pfileprotector_domain_model_protection.php | 6 +-- Resources/Private/Partials/Access/Be.html | 3 ++ Resources/Private/Partials/Access/Fe.html | 51 +++++++++++++++++++ .../Partials/Protection/FormFields.html | 50 ++---------------- 12 files changed, 220 insertions(+), 95 deletions(-) create mode 100644 Classes/Service/AccessService.php create mode 100644 Classes/Utility/Access/AccessUtilityInterface.php create mode 100644 Classes/Utility/Access/BeLoginAccessUtility.php create mode 100644 Classes/Utility/Access/FeLoginAccessUtility.php create mode 100644 Resources/Private/Partials/Access/Be.html create mode 100644 Resources/Private/Partials/Access/Fe.html diff --git a/Classes/Controller/ProtectionController.php b/Classes/Controller/ProtectionController.php index e4ea5d7..7a4518f 100644 --- a/Classes/Controller/ProtectionController.php +++ b/Classes/Controller/ProtectionController.php @@ -9,6 +9,7 @@ use Fixpunkt\FpFileprotector\Domain\Repository\FrontendUserGroupRepository; use Fixpunkt\FpFileprotector\Domain\Repository\FrontendUserRepository; use Fixpunkt\FpFileprotector\Domain\Repository\ProtectionRepository; +use Fixpunkt\FpFileprotector\Service\AccessService; use Psr\Http\Message\ResponseInterface; use TYPO3\CMS\Backend\Attribute\AsController; use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; @@ -28,6 +29,7 @@ public function __construct( protected readonly FrontendUserGroupRepository $userGroupRepository, protected readonly FrontendUserRepository $userRepository, protected readonly FolderRepository $folderRepository, + protected readonly AccessService $accessService, ) { $querySettings = GeneralUtility::makeInstance(Typo3QuerySettings::class); $querySettings->setRespectStoragePage(false); @@ -50,6 +52,7 @@ public function newAction(string $combinedIdentifier): ResponseInterface 'folder' => $this->folderRepository->findOneByCombinedIdentifier($combinedIdentifier), 'userGroups' => $this->userGroupRepository->findAll(), 'users' => $this->userRepository->findAll(), + 'accessPartials' => $this->accessService->getPartials(), ]); return $moduleTemplate->renderResponse('Protection/New'); @@ -87,6 +90,7 @@ public function editAction(Protection $protection): ResponseInterface 'folder' => $protection->getFolderObject(), 'userGroups' => $this->userGroupRepository->findAll(), 'users' => $this->userRepository->findAll(), + 'accessPartials' => $this->accessService->getPartials(), ]); return $moduleTemplate->renderResponse('Protection/Edit'); diff --git a/Classes/Domain/Model/Protection.php b/Classes/Domain/Model/Protection.php index 069073e..57b8818 100644 --- a/Classes/Domain/Model/Protection.php +++ b/Classes/Domain/Model/Protection.php @@ -6,8 +6,6 @@ use Fixpunkt\FpFileprotector\Domain\Repository\FolderRepository; use Fixpunkt\FpFileprotector\Resource\Folder; -use Fixpunkt\FpFileprotector\Utility\FrontendUserUtility; -use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; use TYPO3\CMS\Extbase\Persistence\ObjectStorage; @@ -177,50 +175,12 @@ public function removeUser(FrontendUser $user): void } /** - * Checks whether the current user is allowed to access the resource. - * - * @return bool - */ - public function isGranted(): bool - { - if ($this->isFeLogin()) { - $frontendUserUtility = GeneralUtility::makeInstance(FrontendUserUtility::class); - $feUser = $frontendUserUtility->getCurrentFrontendUser(); - if ($feUser && $feUser->isLoggedIn()) { - if ($this->getUserGroups()->count() === 0 && $this->getUsers()->count() === 0) { - return true; - } - - if (in_array($feUser->get('id'), $this->getUsersUids(), true)) { - return true; - } - - foreach ($feUser->get('groupIds') as $userGroupId) { - if (in_array($userGroupId, $this->getUserGroupsUids(), true)) { - return true; - } - } - } - } - - $context = GeneralUtility::makeInstance(Context::class); - if ( - $this->isBeLogin() - && (bool)$context->getPropertyFromAspect('backend.user', 'isLoggedIn') - ) { - return true; - } - - return false; - } - - /** - * Returns whether the folder protection applies any restrictions. + * Returns whether the folder protection applies any FE restrictions. * * @return bool */ public function isProtected(): bool { - return $this->isFeLogin() || $this->isBeLogin(); + return $this->isFeLogin(); } } diff --git a/Classes/Middleware/AccessMiddleware.php b/Classes/Middleware/AccessMiddleware.php index 5bfe457..05bf017 100644 --- a/Classes/Middleware/AccessMiddleware.php +++ b/Classes/Middleware/AccessMiddleware.php @@ -5,6 +5,7 @@ namespace Fixpunkt\FpFileprotector\Middleware; use Fixpunkt\FpFileprotector\Domain\Repository\ProtectionRepository; +use Fixpunkt\FpFileprotector\Service\AccessService; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; @@ -20,6 +21,8 @@ class AccessMiddleware implements MiddlewareInterface { + public function __construct(private readonly AccessService $accessService) {} + public function process( ServerRequestInterface $request, RequestHandlerInterface $handler @@ -82,7 +85,7 @@ public function process( } $protection = ProtectionRepository::getProtectionStatic($folder); - if ((!$protection && !$protectedByDefault) || ($protection && $protection->isGranted())) { + if ((!$protection && !$protectedByDefault) || ($protection && $this->accessService->isGranted($protection))) { return $this->releaseFile($storage, $filePath); } return $this->createError(LocalizationUtility::translate('sys_file_storage.errors.access_denied', 'FpFileprotector'), 500); diff --git a/Classes/Service/AccessService.php b/Classes/Service/AccessService.php new file mode 100644 index 0000000..6736b4e --- /dev/null +++ b/Classes/Service/AccessService.php @@ -0,0 +1,34 @@ + $accessUtilities */ + public function __construct(private readonly iterable $accessUtilities) {} + + public function isGranted(Protection $protection): bool + { + foreach ($this->accessUtilities as $utility) { + if ($utility->isGranted($protection)) { + return true; + } + } + return false; + } + + /** @return string[] */ + public function getPartials(): array + { + $partials = []; + foreach ($this->accessUtilities as $utility) { + $partials[] = $utility->getPartial(); + } + return $partials; + } +} diff --git a/Classes/Utility/Access/AccessUtilityInterface.php b/Classes/Utility/Access/AccessUtilityInterface.php new file mode 100644 index 0000000..4c007d3 --- /dev/null +++ b/Classes/Utility/Access/AccessUtilityInterface.php @@ -0,0 +1,13 @@ +isLoggedIn()) { + return false; + } + + if ($beUser->isAdmin()) { + return true; + } + + foreach ($beUser->getFileStorages() as $storage) { + if ($storage->getUid() !== $protection->getStorage()) { + continue; + } + try { + $folder = $storage->getFolder($protection->getFolder()); + return $storage->isWithinFileMountBoundaries($folder, false); + } catch (\Exception) { + return false; + } + } + + return false; + } + + public function getPartial(): string + { + return 'Access/Be'; + } +} diff --git a/Classes/Utility/Access/FeLoginAccessUtility.php b/Classes/Utility/Access/FeLoginAccessUtility.php new file mode 100644 index 0000000..25ee9d4 --- /dev/null +++ b/Classes/Utility/Access/FeLoginAccessUtility.php @@ -0,0 +1,46 @@ +isFeLogin()) { + return false; + } + + $feUser = $this->frontendUserUtility->getCurrentFrontendUser(); + if (!$feUser || !$feUser->isLoggedIn()) { + return false; + } + + if ($protection->getUserGroups()->count() === 0 && $protection->getUsers()->count() === 0) { + return true; + } + + if (in_array($feUser->get('id'), $protection->getUsersUids(), true)) { + return true; + } + + foreach ($feUser->get('groupIds') as $userGroupId) { + if (in_array($userGroupId, $protection->getUserGroupsUids(), true)) { + return true; + } + } + + return false; + } + + public function getPartial(): string + { + return 'Access/Fe'; + } +} diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index de9f1ed..c614b37 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -5,6 +5,11 @@ services: autoconfigure: true public: true + _instanceof: + Fixpunkt\FpFileprotector\Utility\Access\AccessUtilityInterface: + tags: + - { name: 'fp_fileprotector.access' } + Fixpunkt\FpFileprotector\: resource: '../Classes/*' exclude: @@ -12,6 +17,17 @@ services: - '../Classes/Resource/*' - '../Classes/Utility/*' + Fixpunkt\FpFileprotector\Utility\Access\: + resource: '../Classes/Utility/Access/*' + + # Utilities needed for DI + Fixpunkt\FpFileprotector\Utility\FrontendUserUtility: ~ + + # Inject all tagged access utilities into AccessService + Fixpunkt\FpFileprotector\Service\AccessService: + arguments: + $accessUtilities: !tagged_iterator 'fp_fileprotector.access' + Fixpunkt\FpFileprotector\EventListener\ModifyIconForResourcePropertiesListener: tags: - name: event.listener diff --git a/Configuration/TCA/tx_fpfileprotector_domain_model_protection.php b/Configuration/TCA/tx_fpfileprotector_domain_model_protection.php index 83fc312..6831d2b 100644 --- a/Configuration/TCA/tx_fpfileprotector_domain_model_protection.php +++ b/Configuration/TCA/tx_fpfileprotector_domain_model_protection.php @@ -21,13 +21,9 @@ 'label' => 'LLL:EXT:fp_fileprotector/Resources/Private/Language/locallang.xlf:tx_fpfileprotector_domain_model_protection.palette.fe', 'showitem' => 'fe_login,--linebreak--,user_groups,--linebreak--,users', ], - 'be' => [ - 'label' => 'LLL:EXT:fp_fileprotector/Resources/Private/Language/locallang.xlf:tx_fpfileprotector_domain_model_protection.palette.be', - 'showitem' => 'be_login', - ], ], 'types' => [ - 0 => ['showitem' => '--palette--;;folder,--palette--;;fe,--palette--;;be'], + 0 => ['showitem' => '--palette--;;folder,--palette--;;fe'], ], 'columns' => [ 'storage' => [ diff --git a/Resources/Private/Partials/Access/Be.html b/Resources/Private/Partials/Access/Be.html new file mode 100644 index 0000000..3396fdf --- /dev/null +++ b/Resources/Private/Partials/Access/Be.html @@ -0,0 +1,3 @@ +

+ +

\ No newline at end of file diff --git a/Resources/Private/Partials/Access/Fe.html b/Resources/Private/Partials/Access/Fe.html new file mode 100644 index 0000000..c6555d5 --- /dev/null +++ b/Resources/Private/Partials/Access/Fe.html @@ -0,0 +1,51 @@ +

+ +

+
+ +
+
+
+
+ + +
+
+ +
+ +
+ +
+
+
+
+
+
+
+ + +
+
+ +
+ +
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/Resources/Private/Partials/Protection/FormFields.html b/Resources/Private/Partials/Protection/FormFields.html index 613e821..4eba5c9 100644 --- a/Resources/Private/Partials/Protection/FormFields.html +++ b/Resources/Private/Partials/Protection/FormFields.html @@ -1,47 +1,3 @@ -

- -

-
- -
-
-
-
- - -
-
- -
-
-
-
-
-
-
- - -
-
- -
-
-
-
-
-
- -

- -

-
- -
\ No newline at end of file + + + \ No newline at end of file From e4173471ad756fb7ef8b6d615274df2b8ba0c054 Mon Sep 17 00:00:00 2001 From: medofrh Date: Thu, 2 Jul 2026 15:30:59 +0200 Subject: [PATCH 03/25] Fix login check in BeLoginAccessUtility to use user UID --- Classes/Utility/Access/BeLoginAccessUtility.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/Utility/Access/BeLoginAccessUtility.php b/Classes/Utility/Access/BeLoginAccessUtility.php index 98e388f..ce41e3a 100644 --- a/Classes/Utility/Access/BeLoginAccessUtility.php +++ b/Classes/Utility/Access/BeLoginAccessUtility.php @@ -13,7 +13,7 @@ public function isGranted(Protection $protection): bool { /** @var BackendUserAuthentication|null $beUser */ $beUser = $GLOBALS['BE_USER'] ?? null; - if (!$beUser || !$beUser->isLoggedIn()) { + if (!$beUser || empty($beUser->user['uid'])) { return false; } From 8bb5901557952b8d97fa7dac063bcf2014ed360b Mon Sep 17 00:00:00 2001 From: medofrh Date: Thu, 2 Jul 2026 15:41:07 +0200 Subject: [PATCH 04/25] Refactor error handling in AccessMiddleware to use a dedicated translate method for localization --- Classes/Middleware/AccessMiddleware.php | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Classes/Middleware/AccessMiddleware.php b/Classes/Middleware/AccessMiddleware.php index 05bf017..4059011 100644 --- a/Classes/Middleware/AccessMiddleware.php +++ b/Classes/Middleware/AccessMiddleware.php @@ -47,7 +47,7 @@ public function process( $storage = $this->getStorage($storageIdentifier); if (!$storage) { - return $this->createError(LocalizationUtility::translate('sys_file_storage.errors.storage_not_found', 'FpFileprotector')); + return $this->createError($this->translate('sys_file_storage.errors.storage_not_found')); } $protected = $storage->getStorageRecord()['protected']; $protectedByDefault = $storage->getStorageRecord()['protected_by_default']; @@ -68,7 +68,7 @@ public function process( ) ) ) { - return $this->createError(LocalizationUtility::translate('sys_file_storage.errors.file_not_found', 'FpFileprotector')); + return $this->createError($this->translate('sys_file_storage.errors.file_not_found')); } $originalFile = $file; if ($originalFile instanceof ProcessedFile) { @@ -81,14 +81,14 @@ public function process( $folder = $originalFile->getParentFolder(); if (!$folder) { - return $this->createError(LocalizationUtility::translate('sys_file_storage.errors.folder_not_found', 'FpFileprotector')); + return $this->createError($this->translate('sys_file_storage.errors.folder_not_found')); } $protection = ProtectionRepository::getProtectionStatic($folder); if ((!$protection && !$protectedByDefault) || ($protection && $this->accessService->isGranted($protection))) { return $this->releaseFile($storage, $filePath); } - return $this->createError(LocalizationUtility::translate('sys_file_storage.errors.access_denied', 'FpFileprotector'), 500); + return $this->createError($this->translate('sys_file_storage.errors.access_denied'), 500); } /** @@ -110,6 +110,15 @@ private function getStorage(string $identifier): ?ResourceStorage return null; } + private function translate(string $key): string + { + try { + return LocalizationUtility::translate($key, 'FpFileprotector') ?? $key; + } catch (\Throwable) { + return $key; + } + } + /** * Returns an error response. * @@ -137,7 +146,7 @@ private function releaseFile(ResourceStorage $storage, string $fileIdentifier): { $file = $storage->getFile($fileIdentifier); if (!$file) { - return $this->createError(LocalizationUtility::translate('sys_file_storage.errors.file_release_not_found', 'FpFileprotector')); + return $this->createError($this->translate('sys_file_storage.errors.file_release_not_found')); } $body = new Stream('php://temp', 'rw'); From b8c8384dc1ad62ede51c1e79c723b61a23db182e Mon Sep 17 00:00:00 2001 From: medofrh Date: Thu, 2 Jul 2026 15:47:54 +0200 Subject: [PATCH 05/25] Fix type casting for protected fields in FileStorageRepository and update form fields for proper checkbox handling --- Classes/Domain/Repository/FileStorageRepository.php | 4 ++-- Resources/Private/Partials/FileStorage/FormFields.html | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Classes/Domain/Repository/FileStorageRepository.php b/Classes/Domain/Repository/FileStorageRepository.php index 17e6b3f..a2bce89 100644 --- a/Classes/Domain/Repository/FileStorageRepository.php +++ b/Classes/Domain/Repository/FileStorageRepository.php @@ -60,8 +60,8 @@ public function update(ResourceStorage $fileStorage): void $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_file_storage')->createQueryBuilder(); $queryBuilder ->update('sys_file_storage') - ->set('protected', $fileStorage->isProtected()) - ->set('protected_by_default', $fileStorage->isProtectedByDefault() ?: 0) + ->set('protected', (int)$fileStorage->isProtected()) + ->set('protected_by_default', (int)$fileStorage->isProtectedByDefault()) ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($fileStorage->getUid()))) ->executeStatement(); } diff --git a/Resources/Private/Partials/FileStorage/FormFields.html b/Resources/Private/Partials/FileStorage/FormFields.html index 7495198..996b109 100644 --- a/Resources/Private/Partials/FileStorage/FormFields.html +++ b/Resources/Private/Partials/FileStorage/FormFields.html @@ -1,10 +1,12 @@
\ No newline at end of file From ab66f9c5bc17ceded64473efd94d696cea6883dc Mon Sep 17 00:00:00 2001 From: medofrh Date: Tue, 7 Jul 2026 12:24:28 +0200 Subject: [PATCH 06/25] Add folder identifier to edit and htaccess URIs in FolderController --- Classes/Controller/FolderController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Classes/Controller/FolderController.php b/Classes/Controller/FolderController.php index 7060aab..a17cef6 100644 --- a/Classes/Controller/FolderController.php +++ b/Classes/Controller/FolderController.php @@ -95,9 +95,9 @@ protected function initializeDocHeader(ModuleTemplate $moduleTemplate, Folder $f $moduleTemplate->getDocHeaderComponent()->setMetaInformationForResource($folder); $editStorageUri = $this->uriBuilder->reset() - ->uriFor('edit', ['fileStorageUid' => $folder->getStorage()->getUid()], 'FileStorage'); + ->uriFor('edit', ['fileStorageUid' => $folder->getStorage()->getUid(), 'id' => $folder->getCombinedIdentifier()], 'FileStorage'); $htaccessUri = $this->uriBuilder->reset() - ->uriFor('htaccess', ['fileStorageUid' => $folder->getStorage()->getUid()], 'FileStorage'); + ->uriFor('htaccess', ['fileStorageUid' => $folder->getStorage()->getUid(), 'id' => $folder->getCombinedIdentifier()], 'FileStorage'); // add buttons $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar(); From bb8023319f5eb173c1c438e1967ef543d775da12 Mon Sep 17 00:00:00 2001 From: medofrh Date: Tue, 7 Jul 2026 13:48:17 +0200 Subject: [PATCH 07/25] Make protection properties display dynamic with one partial per access right. --- Classes/Controller/FolderController.php | 7 ++- Classes/Service/AccessService.php | 10 ++++ .../Utility/Access/AccessUtilityInterface.php | 1 + .../Utility/Access/BeLoginAccessUtility.php | 5 ++ .../Utility/Access/FeLoginAccessUtility.php | 5 ++ Resources/Private/Partials/Properties/Be.html | 11 ++++ Resources/Private/Partials/Properties/Fe.html | 41 ++++++++++++++ .../Partials/Protection/Properties.html | 53 ++----------------- Resources/Private/Templates/Folder/Show.html | 2 +- 9 files changed, 83 insertions(+), 52 deletions(-) create mode 100644 Resources/Private/Partials/Properties/Be.html create mode 100644 Resources/Private/Partials/Properties/Fe.html diff --git a/Classes/Controller/FolderController.php b/Classes/Controller/FolderController.php index a17cef6..1ea8e7d 100644 --- a/Classes/Controller/FolderController.php +++ b/Classes/Controller/FolderController.php @@ -6,6 +6,7 @@ use Fixpunkt\FpFileprotector\Domain\Repository\FolderRepository; use Fixpunkt\FpFileprotector\Resource\Folder; +use Fixpunkt\FpFileprotector\Service\AccessService; use Psr\Http\Message\ResponseInterface; use TYPO3\CMS\Backend\Attribute\AsController; use TYPO3\CMS\Backend\Template\Components\ButtonBar; @@ -29,6 +30,7 @@ public function __construct( protected readonly IconFactory $iconFactory, protected readonly FolderRepository $folderRepository, protected readonly StorageRepository $storageRepository, + protected readonly AccessService $accessService, ) {} /** @@ -56,7 +58,10 @@ public function showAction(string $id = "", bool $refreshFolderTree = false): Re $moduleTemplate = $this->moduleTemplateFactory->create($this->request); $this->initializeDocHeader($moduleTemplate, $folder); $this->statusCheck($folder); - $moduleTemplate->assign('folder', $folder); + $moduleTemplate->assignMultiple([ + 'folder' => $folder, + 'propertiesPartials' => $this->accessService->getPropertiesPartials(), + ]); return $moduleTemplate->renderResponse('Folder/Show'); } diff --git a/Classes/Service/AccessService.php b/Classes/Service/AccessService.php index 6736b4e..6f49cc1 100644 --- a/Classes/Service/AccessService.php +++ b/Classes/Service/AccessService.php @@ -31,4 +31,14 @@ public function getPartials(): array } return $partials; } + + /** @return string[] */ + public function getPropertiesPartials(): array + { + $partials = []; + foreach ($this->accessUtilities as $utility) { + $partials[] = $utility->getPropertiesPartial(); + } + return $partials; + } } diff --git a/Classes/Utility/Access/AccessUtilityInterface.php b/Classes/Utility/Access/AccessUtilityInterface.php index 4c007d3..90df01b 100644 --- a/Classes/Utility/Access/AccessUtilityInterface.php +++ b/Classes/Utility/Access/AccessUtilityInterface.php @@ -10,4 +10,5 @@ interface AccessUtilityInterface { public function isGranted(Protection $protection): bool; public function getPartial(): string; + public function getPropertiesPartial(): string; } diff --git a/Classes/Utility/Access/BeLoginAccessUtility.php b/Classes/Utility/Access/BeLoginAccessUtility.php index ce41e3a..a56cc93 100644 --- a/Classes/Utility/Access/BeLoginAccessUtility.php +++ b/Classes/Utility/Access/BeLoginAccessUtility.php @@ -40,4 +40,9 @@ public function getPartial(): string { return 'Access/Be'; } + + public function getPropertiesPartial(): string + { + return 'Properties/Be'; + } } diff --git a/Classes/Utility/Access/FeLoginAccessUtility.php b/Classes/Utility/Access/FeLoginAccessUtility.php index 25ee9d4..1455611 100644 --- a/Classes/Utility/Access/FeLoginAccessUtility.php +++ b/Classes/Utility/Access/FeLoginAccessUtility.php @@ -43,4 +43,9 @@ public function getPartial(): string { return 'Access/Fe'; } + + public function getPropertiesPartial(): string + { + return 'Properties/Fe'; + } } diff --git a/Resources/Private/Partials/Properties/Be.html b/Resources/Private/Partials/Properties/Be.html new file mode 100644 index 0000000..b41746d --- /dev/null +++ b/Resources/Private/Partials/Properties/Be.html @@ -0,0 +1,11 @@ +
+
+
+ + + + +
+
diff --git a/Resources/Private/Partials/Properties/Fe.html b/Resources/Private/Partials/Properties/Fe.html new file mode 100644 index 0000000..1a40307 --- /dev/null +++ b/Resources/Private/Partials/Properties/Fe.html @@ -0,0 +1,41 @@ +
+
+
+ + + + +
+
+ + +
+
+ +
    + +
  • {userGroup.title}
  • +
    +
+
+
+
+ +
+
+ +
    + +
  • {user.username}
  • +
    +
+
+
+
+
diff --git a/Resources/Private/Partials/Protection/Properties.html b/Resources/Private/Partials/Protection/Properties.html index de0a61d..33c2dec 100644 --- a/Resources/Private/Partials/Protection/Properties.html +++ b/Resources/Private/Partials/Protection/Properties.html @@ -1,52 +1,5 @@
-
-
-
- - - - -
-
- - -
-
- -
    - -
  • {userGroup.title}
  • -
    -
-
-
-
- -
-
- -
    - -
  • {user.username}
  • -
    -
-
-
-
-
+ + +
-
-
- - - - -
\ No newline at end of file diff --git a/Resources/Private/Templates/Folder/Show.html b/Resources/Private/Templates/Folder/Show.html index 7be8fbf..3cafb5f 100644 --- a/Resources/Private/Templates/Folder/Show.html +++ b/Resources/Private/Templates/Folder/Show.html @@ -44,7 +44,7 @@

{folder.speakingName}

- +
From f62073aa8871e83e7e9850bb223398a8d55ed52c Mon Sep 17 00:00:00 2001 From: medofrh Date: Tue, 7 Jul 2026 14:03:39 +0200 Subject: [PATCH 08/25] Remove translate in AccessMiddleware --- Classes/Middleware/AccessMiddleware.php | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/Classes/Middleware/AccessMiddleware.php b/Classes/Middleware/AccessMiddleware.php index 4059011..d119a8e 100644 --- a/Classes/Middleware/AccessMiddleware.php +++ b/Classes/Middleware/AccessMiddleware.php @@ -17,7 +17,6 @@ use TYPO3\CMS\Core\Resource\ResourceStorage; use TYPO3\CMS\Core\Resource\StorageRepository; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Extbase\Utility\LocalizationUtility; class AccessMiddleware implements MiddlewareInterface { @@ -47,7 +46,7 @@ public function process( $storage = $this->getStorage($storageIdentifier); if (!$storage) { - return $this->createError($this->translate('sys_file_storage.errors.storage_not_found')); + return $this->createError('The storage could not be found.'); } $protected = $storage->getStorageRecord()['protected']; $protectedByDefault = $storage->getStorageRecord()['protected_by_default']; @@ -68,7 +67,7 @@ public function process( ) ) ) { - return $this->createError($this->translate('sys_file_storage.errors.file_not_found')); + return $this->createError('The file could not be found.'); } $originalFile = $file; if ($originalFile instanceof ProcessedFile) { @@ -81,14 +80,14 @@ public function process( $folder = $originalFile->getParentFolder(); if (!$folder) { - return $this->createError($this->translate('sys_file_storage.errors.folder_not_found')); + return $this->createError('The storage location could not be found.'); } $protection = ProtectionRepository::getProtectionStatic($folder); if ((!$protection && !$protectedByDefault) || ($protection && $this->accessService->isGranted($protection))) { return $this->releaseFile($storage, $filePath); } - return $this->createError($this->translate('sys_file_storage.errors.access_denied'), 500); + return $this->createError('You do not have permission to access this file.', 500); } /** @@ -110,15 +109,6 @@ private function getStorage(string $identifier): ?ResourceStorage return null; } - private function translate(string $key): string - { - try { - return LocalizationUtility::translate($key, 'FpFileprotector') ?? $key; - } catch (\Throwable) { - return $key; - } - } - /** * Returns an error response. * @@ -146,7 +136,7 @@ private function releaseFile(ResourceStorage $storage, string $fileIdentifier): { $file = $storage->getFile($fileIdentifier); if (!$file) { - return $this->createError($this->translate('sys_file_storage.errors.file_release_not_found')); + return $this->createError('The requested file could not be found.'); } $body = new Stream('php://temp', 'rw'); From 0c5a06b3046ebf728f82867379a4283c92cb88ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannik=20B=C3=B6rgener?= Date: Tue, 14 Jul 2026 00:43:52 +0200 Subject: [PATCH 09/25] run phpstan --- .build-v12/.gitignore | 5 ++ .build-v12/composer.json | 31 +++++++++ .build-v12/phpstan.neon | 6 ++ .build-v13/.gitignore | 5 ++ .build-v13/composer.json | 26 ++++++++ .github/workflows/ci.yml | 48 ++++++++++++++ Classes/Controller/FileStorageController.php | 1 - Classes/Controller/FolderController.php | 22 ++++--- .../Repository/FileStorageRepository.php | 16 ++++- .../Domain/Repository/FolderRepository.php | 7 +- .../Repository/ProtectionRepository.php | 10 ++- Classes/Middleware/AccessMiddleware.php | 8 +-- Classes/Resource/Folder.php | 18 ++++++ Classes/Utility/FrontendUserUtility.php | 1 - DEVELOPMENT.md | 64 +++++++++++++++++++ composer.json | 2 +- 16 files changed, 242 insertions(+), 28 deletions(-) create mode 100644 .build-v12/.gitignore create mode 100644 .build-v12/composer.json create mode 100644 .build-v12/phpstan.neon create mode 100644 .build-v13/.gitignore create mode 100644 .build-v13/composer.json create mode 100644 .github/workflows/ci.yml create mode 100644 DEVELOPMENT.md diff --git a/.build-v12/.gitignore b/.build-v12/.gitignore new file mode 100644 index 0000000..0003ce2 --- /dev/null +++ b/.build-v12/.gitignore @@ -0,0 +1,5 @@ +# Isolated v12 analysis toolchain: only the manifests are tracked. +/* +!.gitignore +!composer.json +!phpstan.neon diff --git a/.build-v12/composer.json b/.build-v12/composer.json new file mode 100644 index 0000000..4f6be37 --- /dev/null +++ b/.build-v12/composer.json @@ -0,0 +1,31 @@ +{ + "name": "fixpunkt/fp-fileprotector-cs-tools-v12", + "description": "Isolated tooling to static-analyse fp_fileprotector against TYPO3 12.4 (lowest supported). Mirror of .build/ but pinned to the v12 line.", + "autoload": { + "psr-4": { + "Fixpunkt\\FpFileprotector\\": "../Classes/" + } + }, + "require-dev": { + "phpstan/extension-installer": "^1.4", + "saschaegerer/phpstan-typo3": "^1.10", + "typo3/cms-core": "^12.4", + "typo3/cms-extbase": "*", + "typo3/cms-fluid": "*", + "typo3/cms-frontend": "*", + "typo3/cms-backend": "*", + "typo3/cms-install": "*" + }, + "config": { + "allow-plugins": { + "typo3/class-alias-loader": true, + "typo3/cms-composer-installers": true, + "phpstan/extension-installer": true + }, + "policy": { + "advisories": { + "block": false + } + } + } +} diff --git a/.build-v12/phpstan.neon b/.build-v12/phpstan.neon new file mode 100644 index 0000000..947b0b2 --- /dev/null +++ b/.build-v12/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: 5 + paths: + - ../Classes + bootstrapFiles: + - vendor/autoload.php diff --git a/.build-v13/.gitignore b/.build-v13/.gitignore new file mode 100644 index 0000000..9238e59 --- /dev/null +++ b/.build-v13/.gitignore @@ -0,0 +1,5 @@ +# Isolated dev toolchain: only the manifest is tracked, everything that +# composer/TYPO3 generates here (vendor, lock, published public assets, …) is not. +/* +!.gitignore +!composer.json diff --git a/.build-v13/composer.json b/.build-v13/composer.json new file mode 100644 index 0000000..6df5654 --- /dev/null +++ b/.build-v13/composer.json @@ -0,0 +1,26 @@ +{ + "name": "fixpunkt/fp-fileprotector-cs-tools-v13", + "description": "Isolated dev tooling (coding standards + static analysis) for fp_fileprotector — not part of the extension package. Mirror of .build/ but pinned to the v13 line.", + "autoload": { + "psr-4": { + "Fixpunkt\\FpFileprotector\\": "../Classes/" + } + }, + "require-dev": { + "phpstan/extension-installer": "^1.4", + "saschaegerer/phpstan-typo3": "^2.1", + "typo3/cms-core": "^13.4", + "typo3/cms-extbase": "*", + "typo3/cms-fluid": "*", + "typo3/cms-frontend": "*", + "typo3/cms-backend": "*", + "typo3/cms-install": "*" + }, + "config": { + "allow-plugins": { + "typo3/class-alias-loader": true, + "typo3/cms-composer-installers": true, + "phpstan/extension-installer": true + } + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..eb887a2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: CI + +# Static analysis + coding standards across the supported TYPO3 versions. +# Reuses the isolated toolchains in .build/ (v14), .build-v13/ (v13, also hosts +# the coding-standards tooling) and .build-v12/ (v12) — see DEVELOPMENT.md. +# All jobs run on PHP 8.4 because fp-social-bridge (dev-feature/typo3-14) requires ^8.4. + +on: + push: + pull_request: + +jobs: + phpstan: + name: PHPStan (TYPO3 ${{ matrix.typo3 }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - { typo3: '12.4', php: '8.4', build: '.build-v12', config: '.build-v12/phpstan.neon' } + - { typo3: '13.4', php: '8.4', build: '.build-v13', config: '.build-v13/phpstan.neon' } + - { typo3: '14.3', php: '8.4', build: '.build', config: 'phpstan.neon' } + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: + php-version: '${{ matrix.php }}' + tools: composer:v2 + coverage: none + - name: Install tooling (${{ matrix.build }}) + run: composer update --working-dir=${{ matrix.build }} --no-progress --no-interaction + - name: PHPStan + run: ${{ matrix.build }}/vendor/bin/phpstan analyse -c ${{ matrix.config }} --no-progress + + coding-standards: + name: Coding Standards + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + tools: composer:v2 + coverage: none + - name: Install tooling + run: composer update --working-dir=.build --no-progress --no-interaction + - name: php-cs-fixer (dry-run) + run: .build/vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --dry-run --diff --using-cache=no diff --git a/Classes/Controller/FileStorageController.php b/Classes/Controller/FileStorageController.php index 6626f3f..7cbc617 100644 --- a/Classes/Controller/FileStorageController.php +++ b/Classes/Controller/FileStorageController.php @@ -40,7 +40,6 @@ public function editAction(string $id, int $fileStorageUid): ResponseInterface /** * Updates settings of a file storage. * - * @param int $id * @param int $fileStorageUid * @param bool $protected * @param bool $protectedByDefault diff --git a/Classes/Controller/FolderController.php b/Classes/Controller/FolderController.php index 1ea8e7d..87dd240 100644 --- a/Classes/Controller/FolderController.php +++ b/Classes/Controller/FolderController.php @@ -119,17 +119,19 @@ protected function initializeDocHeader(ModuleTemplate $moduleTemplate, Folder $f ->setIcon($this->iconFactory->getIcon('tx-fpfileprotector-folder-public')) ->setLabel(LocalizationUtility::translate('module.storage_is_unprotected', 'FpFileprotector')); } + $storageSettingsItem = GeneralUtility::makeInstance(DropDownItem::class); + $storageSettingsItem + ->setLabel(LocalizationUtility::translate('module.dropdown.storage_settings', 'FpFileprotector')) + ->setHref($editStorageUri); + + $htaccessItem = GeneralUtility::makeInstance(DropDownItem::class); + $htaccessItem + ->setLabel(LocalizationUtility::translate('module.dropdown.htaccess_update', 'FpFileprotector')) + ->setHref($htaccessUri); + $dropdownButton - ->addItem( - GeneralUtility::makeInstance(DropDownItem::class) - ->setLabel(LocalizationUtility::translate('module.dropdown.storage_settings', 'FpFileprotector')) - ->setHref($editStorageUri) - ) - ->addItem( - GeneralUtility::makeInstance(DropDownItem::class) - ->setLabel(LocalizationUtility::translate('module.dropdown.htaccess_update', 'FpFileprotector')) - ->setHref($htaccessUri) - ); + ->addItem($storageSettingsItem) + ->addItem($htaccessItem); $buttonBar->addButton($dropdownButton, ButtonBar::BUTTON_POSITION_RIGHT); } diff --git a/Classes/Domain/Repository/FileStorageRepository.php b/Classes/Domain/Repository/FileStorageRepository.php index a2bce89..598f88b 100644 --- a/Classes/Domain/Repository/FileStorageRepository.php +++ b/Classes/Domain/Repository/FileStorageRepository.php @@ -29,7 +29,11 @@ public function findAll(): array $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); try { foreach ($query->fetchAllAssociative() as $data) { - $fileStorages[] = $resourceFactory->getStorageObject($data['uid']); + $storage = $resourceFactory->getStorageObject($data['uid']); + // The core storage is XCLASSed to our subclass (see ext_localconf.php). + if ($storage instanceof ResourceStorage) { + $fileStorages[] = $storage; + } } } catch (Exception) { } @@ -46,7 +50,15 @@ public function findByIdentifier(int $fileStorageUid): ResourceStorage { /** @var ResourceFactory $resourceFactory */ $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); - return $resourceFactory->getStorageObject($fileStorageUid); + $storage = $resourceFactory->getStorageObject($fileStorageUid); + // The core storage is XCLASSed to our subclass (see ext_localconf.php). + if (!$storage instanceof ResourceStorage) { + throw new \RuntimeException( + 'Expected an instance of ' . ResourceStorage::class . ', got ' . $storage::class . '.', + 1752480001 + ); + } + return $storage; } /** diff --git a/Classes/Domain/Repository/FolderRepository.php b/Classes/Domain/Repository/FolderRepository.php index b21d2e6..32dda7d 100644 --- a/Classes/Domain/Repository/FolderRepository.php +++ b/Classes/Domain/Repository/FolderRepository.php @@ -4,7 +4,7 @@ namespace Fixpunkt\FpFileprotector\Domain\Repository; -use TYPO3\CMS\Core\Resource\Folder; +use Fixpunkt\FpFileprotector\Resource\Folder; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -19,7 +19,8 @@ class FolderRepository public function findOneByCombinedIdentifier(string $combinedIdentifier): ?Folder { $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); - /** @var Folder $folder */ - return $resourceFactory->getFolderObjectFromCombinedIdentifier($combinedIdentifier); + $folder = $resourceFactory->getFolderObjectFromCombinedIdentifier($combinedIdentifier); + // The core Folder is XCLASSed to our subclass (see ext_localconf.php). + return $folder instanceof Folder ? $folder : null; } } diff --git a/Classes/Domain/Repository/ProtectionRepository.php b/Classes/Domain/Repository/ProtectionRepository.php index 0c46c2d..d1bb521 100644 --- a/Classes/Domain/Repository/ProtectionRepository.php +++ b/Classes/Domain/Repository/ProtectionRepository.php @@ -6,6 +6,7 @@ use Doctrine\DBAL\ParameterType; use Fixpunkt\FpFileprotector\Domain\Model\Protection; +use Fixpunkt\FpFileprotector\Resource\Folder; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Resource\FolderInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -32,7 +33,8 @@ public function findOneByFolder(FolderInterface $folder): ?Protection ); $results = $query->execute(); - return $results->current() ?: null; + $protection = $results->current(); + return $protection instanceof Protection ? $protection : null; } /** @@ -45,7 +47,8 @@ public function findOneByFolder(FolderInterface $folder): ?Protection public function getProtection(FolderInterface $folder, bool $recursive = true): ?Protection { $protection = $this->findOneByFolder($folder); - if (!$protection && $recursive && $folder->hasParentFolder()) { + // hasParentFolder() only exists on our XCLASSed Folder subclass. + if (!$protection && $recursive && $folder instanceof Folder && $folder->hasParentFolder()) { return $this->getProtection($folder->getParentFolder()); } return $protection; @@ -76,7 +79,8 @@ public static function getProtectionStatic(FolderInterface $folder): ?Protection $protections = $dataMapper->map(Protection::class, $statement->fetchAllAssociative()); $protection = count($protections) ? $protections[0] : null; - if (!$protection && $folder->hasParentFolder()) { + // hasParentFolder() only exists on our XCLASSed Folder subclass. + if (!$protection && $folder instanceof Folder && $folder->hasParentFolder()) { return self::getProtectionStatic($folder->getParentFolder()); } return $protection; diff --git a/Classes/Middleware/AccessMiddleware.php b/Classes/Middleware/AccessMiddleware.php index d119a8e..c195da5 100644 --- a/Classes/Middleware/AccessMiddleware.php +++ b/Classes/Middleware/AccessMiddleware.php @@ -71,7 +71,7 @@ public function process( } $originalFile = $file; if ($originalFile instanceof ProcessedFile) { - $originalFile = $file->getOriginalFile(); + $originalFile = $originalFile->getOriginalFile(); } if (!$protected) { @@ -79,9 +79,6 @@ public function process( } $folder = $originalFile->getParentFolder(); - if (!$folder) { - return $this->createError('The storage location could not be found.'); - } $protection = ProtectionRepository::getProtectionStatic($folder); if ((!$protection && !$protectedByDefault) || ($protection && $this->accessService->isGranted($protection))) { @@ -135,9 +132,6 @@ private function createError(string $reason, int $status = 404): Response private function releaseFile(ResourceStorage $storage, string $fileIdentifier): Response { $file = $storage->getFile($fileIdentifier); - if (!$file) { - return $this->createError('The requested file could not be found.'); - } $body = new Stream('php://temp', 'rw'); $body->write($file->getContents()); diff --git a/Classes/Resource/Folder.php b/Classes/Resource/Folder.php index 8259a41..b94d6ba 100644 --- a/Classes/Resource/Folder.php +++ b/Classes/Resource/Folder.php @@ -17,6 +17,24 @@ class Folder extends Core\Folder /** @var ResourceStorage */ protected $storage; + /** + * Returns the storage this folder belongs to. + * + * The core storage is XCLASSed to our subclass (see ext_localconf.php), + * so we can safely narrow the return type. + */ + public function getStorage(): ResourceStorage + { + $storage = parent::getStorage(); + if (!$storage instanceof ResourceStorage) { + throw new \RuntimeException( + 'Expected an instance of ' . ResourceStorage::class . ', got ' . $storage::class . '.', + 1752480000 + ); + } + return $storage; + } + /** * Returns protection for this folder or one of its parent folders. * diff --git a/Classes/Utility/FrontendUserUtility.php b/Classes/Utility/FrontendUserUtility.php index 630a315..d612ff2 100644 --- a/Classes/Utility/FrontendUserUtility.php +++ b/Classes/Utility/FrontendUserUtility.php @@ -20,7 +20,6 @@ public function getCurrentFrontendUser(): ?UserAspect try { /** @var Context $context */ $context = GeneralUtility::makeInstance(Context::class); - /** @var UserAspect $userAspect */ return $context->getAspect('frontend.user'); } catch (\Exception) { return null; diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..6463d6f --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,64 @@ +# Entwicklung / Dev-Tooling + +Diese Extension wird über ein **eigenständiges DDEV-Projekt** entwickelt. Das +Tooling (Coding Standards + statische Analyse) liegt isoliert in +* `.build/` (gegen TYPO3 14.3) +* `.build-v13/` (gegen TYPO3 13.4) +* `.build-v12/` (gegen TYPO3 12.4) + +damit die `composer.json` der Extension selbst sauber bleibt. Eingecheckt sind nur die +Tooling-Manifeste; `vendor/`, Lockfiles und generierte Assets sind ignoriert. + +## Einrichtung + +```bash +ddev start +ddev exec "cd .build && composer install" +``` + +> Das Tooling zieht TYPO3-Core, PHPStan und die Runtime-Deps zur +> Typauflösung. + +## Coding Standards (php-cs-fixer) + +Grundlage: `typo3/coding-standards` (PSR-12 / PER-CS) inkl. erzwungenem +`declare(strict_types=1)`. Konfiguration: `.php-cs-fixer.dist.php`, `.editorconfig`. + +```bash +# Nur prüfen – ändert nichts: +ddev exec ".build/vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --dry-run --diff --using-cache=no" + +# Automatisch korrigieren: +ddev exec ".build/vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --using-cache=no" +``` + +## Statische Analyse (PHPStan) + +Level 5 über `Classes/`, TYPO3-Regeln via `saschaegerer/phpstan-typo3`. + +```bash +# gegen TYPO3 14.3 (Standard-Toolchain, phpstan.neon wird automatisch gefunden): +ddev exec ".build/vendor/bin/phpstan analyse" + +# gegen TYPO3 13.4 (niedrigste unterstützte Version): +ddev exec "cd .build-v13 && composer install" # einmalig +ddev exec ".build-v1/vendor/bin/phpstan analyse" + +# gegen TYPO3 12.4 (niedrigste unterstützte Version): +ddev exec "cd .build-v12 && composer install" # einmalig +ddev exec ".build-v12/vendor/bin/phpstan analyse -c .build-v12/phpstan.neon" +``` + +## Continuous Integration + +`.github/workflows/ci.yml` fährt bei Push/Pull-Request PHPStan (Matrix +TYPO3 12.4 + 13.4 + 14.3) sowie php-cs-fixer und nutzt dafür dieselben `.build`-/ `.build-12`-/ +`.build-v13`-Toolchains. + +## Tooling aktualisieren + +```bash +ddev exec "cd .build && composer update" +ddev exec "cd .build-v12 && composer update" +ddev exec "cd .build-v13 && composer update" +``` diff --git a/composer.json b/composer.json index b9b3d5f..7f2a5bf 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ ], "require": { "typo3/cms-core": "^13 || ^12", - "php": "^8.2" + "php": "^8.4" }, "require-dev": { "typo3/coding-standards": "*", From 40bcb2cffccd7441e28c5cdb187b476436f6ba4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannik=20B=C3=B6rgener?= Date: Wed, 15 Jul 2026 00:07:16 +0200 Subject: [PATCH 10/25] add typo3 14 compatibility --- .build/.gitignore | 5 +++ .build/composer.json | 27 ++++++++++++ Classes/Controller/FolderController.php | 7 ++-- Classes/Controller/ProtectionController.php | 2 +- .../Repository/FileStorageRepository.php | 42 ++++++------------- ...odifyIconForResourcePropertiesListener.php | 3 +- Classes/Middleware/AccessMiddleware.php | 2 +- Classes/Resource/Folder.php | 9 ++-- composer.json | 2 +- ext_emconf.php | 2 +- phpstan.neon | 12 ++++++ 11 files changed, 68 insertions(+), 45 deletions(-) create mode 100644 .build/.gitignore create mode 100644 .build/composer.json create mode 100644 phpstan.neon diff --git a/.build/.gitignore b/.build/.gitignore new file mode 100644 index 0000000..90990e7 --- /dev/null +++ b/.build/.gitignore @@ -0,0 +1,5 @@ +# Isolated v14 analysis toolchain: only the manifests are tracked. +/* +!.gitignore +!composer.json +!phpstan.neon diff --git a/.build/composer.json b/.build/composer.json new file mode 100644 index 0000000..46b1c53 --- /dev/null +++ b/.build/composer.json @@ -0,0 +1,27 @@ +{ + "name": "fixpunkt/fp-fileprotector-cs-tools", + "description": "Isolated tooling to static-analyse fp_fileprotector against TYPO3 14.3 (highest supported).", + "autoload": { + "psr-4": { + "Fixpunkt\\FpFileprotector\\": "../Classes/" + } + }, + "require-dev": { + "typo3/coding-standards": "^0.8", + "phpstan/extension-installer": "^1.4", + "saschaegerer/phpstan-typo3": "^3", + "typo3/cms-core": "^14.3", + "typo3/cms-extbase": "*", + "typo3/cms-fluid": "*", + "typo3/cms-frontend": "*", + "typo3/cms-backend": "*", + "typo3/cms-install": "*" + }, + "config": { + "allow-plugins": { + "typo3/class-alias-loader": true, + "typo3/cms-composer-installers": true, + "phpstan/extension-installer": true + } + } +} diff --git a/Classes/Controller/FolderController.php b/Classes/Controller/FolderController.php index 87dd240..0bba7a0 100644 --- a/Classes/Controller/FolderController.php +++ b/Classes/Controller/FolderController.php @@ -39,7 +39,7 @@ public function __construct( * @param string $id * @return ResponseInterface */ - public function showAction(string $id = "", bool $refreshFolderTree = false): ResponseInterface + public function showAction(string $id = '', bool $refreshFolderTree = false): ResponseInterface { // modify id or redirect to first folder $id = $this->modifyId($id); @@ -93,7 +93,6 @@ protected function statusCheck(Folder $folder): void * * @param ModuleTemplate $moduleTemplate * @param Folder $folder - * @return void */ protected function initializeDocHeader(ModuleTemplate $moduleTemplate, Folder $folder): void { @@ -161,8 +160,8 @@ protected function modifyId(string $id): string $moduleData->set('id', $id); $GLOBALS['BE_USER']->pushModuleData($moduleData->getModuleIdentifier(), $moduleData->toArray()); return $id; - } else { - return $moduleData->get('id', ''); } + return $moduleData->get('id', ''); + } } diff --git a/Classes/Controller/ProtectionController.php b/Classes/Controller/ProtectionController.php index 7a4518f..951370d 100644 --- a/Classes/Controller/ProtectionController.php +++ b/Classes/Controller/ProtectionController.php @@ -129,4 +129,4 @@ public function deleteAction(Protection $protection): ResponseInterface )); return $this->redirect('show', 'Folder', null, ['id' => $protection->getFolderObject()->getCombinedIdentifier(), 'refreshFolderTree' => true]); } -} \ No newline at end of file +} diff --git a/Classes/Domain/Repository/FileStorageRepository.php b/Classes/Domain/Repository/FileStorageRepository.php index 598f88b..3351629 100644 --- a/Classes/Domain/Repository/FileStorageRepository.php +++ b/Classes/Domain/Repository/FileStorageRepository.php @@ -4,15 +4,17 @@ namespace Fixpunkt\FpFileprotector\Domain\Repository; -use Doctrine\DBAL\Driver\Exception; use Fixpunkt\FpFileprotector\Resource\ResourceStorage; use TYPO3\CMS\Core\Database\ConnectionPool; -use TYPO3\CMS\Core\Database\Query\QueryBuilder; -use TYPO3\CMS\Core\Resource\ResourceFactory; -use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Resource\StorageRepository; class FileStorageRepository { + public function __construct( + private readonly StorageRepository $storageRepository, + private readonly ConnectionPool $connectionPool, + ) {} + /** * Returns all file storages. * @@ -20,41 +22,26 @@ class FileStorageRepository */ public function findAll(): array { - /** @var QueryBuilder $queryBuilder */ - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_file_storage')->createQueryBuilder(); - $query = $queryBuilder - ->select('uid')->from('sys_file_storage')->executeQuery(); - $fileStorages = []; - $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); - try { - foreach ($query->fetchAllAssociative() as $data) { - $storage = $resourceFactory->getStorageObject($data['uid']); - // The core storage is XCLASSed to our subclass (see ext_localconf.php). - if ($storage instanceof ResourceStorage) { - $fileStorages[] = $storage; - } + foreach ($this->storageRepository->findAll() as $storage) { + // The core storage is XCLASSed to our subclass (see ext_localconf.php). + if ($storage instanceof ResourceStorage) { + $fileStorages[] = $storage; } - } catch (Exception) { } return $fileStorages; } /** * Returns a single resource storage. - * - * @param int $fileStorageUid - * @return ResourceStorage */ public function findByIdentifier(int $fileStorageUid): ResourceStorage { - /** @var ResourceFactory $resourceFactory */ - $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); - $storage = $resourceFactory->getStorageObject($fileStorageUid); + $storage = $this->storageRepository->findByUid($fileStorageUid); // The core storage is XCLASSed to our subclass (see ext_localconf.php). if (!$storage instanceof ResourceStorage) { throw new \RuntimeException( - 'Expected an instance of ' . ResourceStorage::class . ', got ' . $storage::class . '.', + 'Expected an instance of ' . ResourceStorage::class . ', got ' . ($storage === null ? 'null' : $storage::class) . '.', 1752480001 ); } @@ -63,13 +50,10 @@ public function findByIdentifier(int $fileStorageUid): ResourceStorage /** * Updates a file storage. - * - * @param ResourceStorage $fileStorage */ public function update(ResourceStorage $fileStorage): void { - /** @var QueryBuilder $queryBuilder */ - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_file_storage')->createQueryBuilder(); + $queryBuilder = $this->connectionPool->getConnectionForTable('sys_file_storage')->createQueryBuilder(); $queryBuilder ->update('sys_file_storage') ->set('protected', (int)$fileStorage->isProtected()) diff --git a/Classes/EventListener/ModifyIconForResourcePropertiesListener.php b/Classes/EventListener/ModifyIconForResourcePropertiesListener.php index 62117c1..c634ae4 100644 --- a/Classes/EventListener/ModifyIconForResourcePropertiesListener.php +++ b/Classes/EventListener/ModifyIconForResourcePropertiesListener.php @@ -7,7 +7,6 @@ use Fixpunkt\FpFileprotector\Resource\Folder; use Fixpunkt\FpFileprotector\Resource\ResourceStorage; use TYPO3\CMS\Core\Imaging\Event\ModifyIconForResourcePropertiesEvent; -use TYPO3\CMS\Core\Resource\ResourceInterface; class ModifyIconForResourcePropertiesListener { @@ -37,4 +36,4 @@ public function __invoke(ModifyIconForResourcePropertiesEvent $event): void } } } -} \ No newline at end of file +} diff --git a/Classes/Middleware/AccessMiddleware.php b/Classes/Middleware/AccessMiddleware.php index c195da5..a92b23d 100644 --- a/Classes/Middleware/AccessMiddleware.php +++ b/Classes/Middleware/AccessMiddleware.php @@ -139,4 +139,4 @@ private function releaseFile(ResourceStorage $storage, string $fileIdentifier): ->withHeader('Content-Type', $file->getMimeType()) ->withBody($body); } -} \ No newline at end of file +} diff --git a/Classes/Resource/Folder.php b/Classes/Resource/Folder.php index b94d6ba..c73d7a6 100644 --- a/Classes/Resource/Folder.php +++ b/Classes/Resource/Folder.php @@ -14,9 +14,6 @@ */ class Folder extends Core\Folder { - /** @var ResourceStorage */ - protected $storage; - /** * Returns the storage this folder belongs to. * @@ -86,10 +83,10 @@ public function getProtectionStatus(): string if ($protection && $protection->isProtected()) { // protection is set return $this->getOwnProtection() ? 'protected' : 'protected_by_parent'; - } else { - // no protection is set - return $this->storage->isProtectedByDefault() ? 'no_access' : 'public'; } + // no protection is set + return $this->getStorage()->isProtectedByDefault() ? 'no_access' : 'public'; + } /** diff --git a/composer.json b/composer.json index 7f2a5bf..a69e8a0 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ } ], "require": { - "typo3/cms-core": "^13 || ^12", + "typo3/cms-core": "^12 || ^13 || ^14", "php": "^8.4" }, "require-dev": { diff --git a/ext_emconf.php b/ext_emconf.php index c5c04e1..4703de0 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -11,7 +11,7 @@ 'version' => '3.2.0', 'constraints' => [ 'depends' => [ - 'typo3' => '12.0.0-13.99.99', + 'typo3' => '12.4.0-14.99.99', ], 'conflicts' => [], 'suggests' => [], diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..fb8db88 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,12 @@ +# PHPStan configuration for the fp_fileprotector extension. +# The toolchain (PHPStan + saschaegerer/phpstan-typo3 + TYPO3 core for type +# resolution) lives isolated in .build/ — see .build/composer.json. +# phpstan-typo3 is auto-registered via phpstan/extension-installer. +# +# Run: ddev exec ".build/vendor/bin/phpstan analyse" +parameters: + level: 5 + paths: + - Classes + bootstrapFiles: + - .build/vendor/autoload.php From 4a9c16eec1db80652cb68da68cbb81a70ebc64eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannik=20B=C3=B6rgener?= Date: Wed, 15 Jul 2026 18:01:21 +0200 Subject: [PATCH 11/25] remove cache from git --- .gitignore | 1 + .php-cs-fixer.cache | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 .php-cs-fixer.cache diff --git a/.gitignore b/.gitignore index c28b5b4..7642d29 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ vendor/ vendor/* Documentation-GENERATED-temp/ Documentation-GENERATED-temp/* +.php-cs-fixer.cache .env public diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache deleted file mode 100644 index 51e7fcf..0000000 --- a/.php-cs-fixer.cache +++ /dev/null @@ -1 +0,0 @@ -{"php":"8.4.12","version":"3.95.2:v3.95.2#a28d88a5e172b27e78d0816992b15a9df3da20f1","indent":" ","lineEnding":"\n","rules":{"doctrine_annotation_array_assignment":{"operator":":"},"doctrine_annotation_braces":true,"doctrine_annotation_indentation":true,"doctrine_annotation_spaces":{"before_array_assignments_colon":false},"binary_operator_spaces":{"default":"at_least_single_space"},"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"blank_lines_before_namespace":true,"braces_position":{"allow_single_line_anonymous_functions":false,"allow_single_line_empty_anonymous_classes":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_type_declaration":true,"declare_equal_normalize":{"space":"none"},"lowercase_cast":true,"lowercase_static_reference":true,"modifier_keywords":true,"new_with_parentheses":{"anonymous_class":true},"no_blank_lines_after_class_opening":true,"no_extra_blank_lines":true,"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"imports_order":["class","function","const"],"sort_algorithm":"alpha"},"return_type_declaration":{"space_before":"none"},"short_scalar_cast":true,"single_import_per_statement":{"group_to_single_imports":false},"single_space_around_construct":true,"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"unary_operator_spaces":{"only_dec_inc":true},"blank_line_after_namespace":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":{"closure_fn_spacing":"none"},"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":true,"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true,"array_indentation":true,"array_syntax":{"syntax":"short"},"cast_spaces":{"space":"none"},"concat_space":{"spacing":"one"},"declare_parentheses":true,"dir_constant":true,"function_to_constant":{"functions":["get_called_class","get_class","get_class_this","php_sapi_name","phpversion","pi"]},"type_declaration_spaces":true,"global_namespace_import":{"import_classes":false,"import_constants":false,"import_functions":false},"list_syntax":{"syntax":"short"},"modernize_strpos":true,"modernize_types_casting":true,"native_function_casing":true,"no_alias_functions":true,"no_blank_lines_after_phpdoc":true,"no_empty_phpdoc":true,"no_empty_statement":true,"no_leading_namespace_whitespace":true,"no_null_property_initialization":true,"no_short_bool_cast":true,"no_singleline_whitespace_before_semicolons":true,"no_superfluous_elseif":true,"no_trailing_comma_in_singleline":true,"no_unneeded_control_parentheses":true,"no_unused_imports":true,"no_useless_else":true,"no_useless_nullsafe_operator":true,"php_unit_construct":{"assertions":["assertEquals","assertSame","assertNotEquals","assertNotSame"]},"php_unit_mock_short_will_return":true,"php_unit_test_case_static_method_calls":{"call_type":"self"},"phpdoc_no_access":true,"phpdoc_no_empty_return":true,"phpdoc_no_package":true,"phpdoc_scalar":true,"phpdoc_trim":true,"phpdoc_types":true,"phpdoc_types_order":{"null_adjustment":"always_last","sort_algorithm":"none"},"single_quote":true,"single_line_comment_style":{"comment_types":["hash"]},"single_line_empty_body":true,"trailing_comma_in_multiline":{"elements":["arrays"]},"whitespace_after_comma_in_array":{"ensure_single_space":true},"yoda_style":{"equal":false,"identical":false,"less_and_greater":false}},"ruleCustomisationPolicyVersion":"null-policy","hashes":{"Classes\/Middleware\/AccessMiddleware.php":"311483c94efaf0dadfcf794073970873","Classes\/Controller\/StartController.php":"5b4b331a6a8a703e6c9f21056a90fe98","Classes\/Controller\/ProtectionController.php":"67c78eb64099532d2a2dad6a8025d8cb","Classes\/Controller\/FileStorageController.php":"36152c0c86616b51e389c7d653795074","Classes\/Controller\/FolderController.php":"6d9b2ca525a66652fadc0be32843a0b9","Classes\/Resource\/ResourceStorage.php":"b315c04560c8b44daf05a12c3e4e349a","Classes\/Resource\/Folder.php":"640723988f95aba989b3c8e1b310fbf9","Classes\/Domain\/Repository\/FolderRepository.php":"e0c2cf5f1d27d48eac6f15ace15b46c4","Classes\/Domain\/Repository\/FrontendUserRepository.php":"f49a807af02707e8cb019d821a05fdb2","Classes\/Domain\/Repository\/FileStorageRepository.php":"7ffe5ab1194a8d1e55db19df862d3a0c","Classes\/Domain\/Repository\/ProtectionRepository.php":"a20546bebd1f1ad0ca0158dbc1ff2190","Classes\/Domain\/Repository\/FrontendUserGroupRepository.php":"86079045c3ac1f9ae8fef5f384aaba76","Classes\/Domain\/Model\/FrontendUser.php":"08db50d4abfa9e242d24eccfe93dfd87","Classes\/Domain\/Model\/Protection.php":"4eed69e6f9f0ffdeed0ccb59a1d67851","Classes\/Domain\/Model\/FrontendUserGroup.php":"a1476b99031905684c806f60bc43e0aa","Classes\/Utility\/HtaccessUtility.php":"7062630e6ea0c7843608bf6b9a2d5d06","Classes\/Utility\/FrontendUserUtility.php":"76b6e4743db22452782ba3eca158879f"}} \ No newline at end of file From 54bfec0c88faa577081713752fada206aec1d550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannik=20B=C3=B6rgener?= Date: Wed, 15 Jul 2026 18:21:27 +0200 Subject: [PATCH 12/25] use database records to check access --- .../Repository/ProtectionRepository.php | 36 --------------- Classes/Middleware/AccessMiddleware.php | 3 +- Classes/Service/AccessService.php | 42 +++++++++++++++-- .../Utility/Access/AccessUtilityInterface.php | 5 +- .../Utility/Access/BeLoginAccessUtility.php | 8 ++-- .../Utility/Access/FeLoginAccessUtility.php | 46 ++++++++++++++++--- 6 files changed, 85 insertions(+), 55 deletions(-) diff --git a/Classes/Domain/Repository/ProtectionRepository.php b/Classes/Domain/Repository/ProtectionRepository.php index d1bb521..0da9609 100644 --- a/Classes/Domain/Repository/ProtectionRepository.php +++ b/Classes/Domain/Repository/ProtectionRepository.php @@ -4,13 +4,9 @@ namespace Fixpunkt\FpFileprotector\Domain\Repository; -use Doctrine\DBAL\ParameterType; use Fixpunkt\FpFileprotector\Domain\Model\Protection; use Fixpunkt\FpFileprotector\Resource\Folder; -use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Resource\FolderInterface; -use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper; use TYPO3\CMS\Extbase\Persistence\Repository; class ProtectionRepository extends Repository @@ -53,36 +49,4 @@ public function getProtection(FolderInterface $folder, bool $recursive = true): } return $protection; } - - /** - * Static function to get protection data for middleware. - * Injecting the repository itself into the middleware caused errors in some instances. - * - * @param FolderInterface $folder - * @return Protection|null - */ - public static function getProtectionStatic(FolderInterface $folder): ?Protection - { - // find protection data from database - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_fpfileprotector_domain_model_protection'); - $statement = $queryBuilder - ->select('*') - ->from('tx_fpfileprotector_domain_model_protection') - ->where( - $queryBuilder->expr()->eq('storage', $queryBuilder->createNamedParameter($folder->getStorage()->getUid(), ParameterType::INTEGER)), - $queryBuilder->expr()->eq('folder', $queryBuilder->createNamedParameter($folder->getIdentifier())) - ) - ->executeQuery(); - - // Convert data to object and return - $dataMapper = GeneralUtility::makeInstance(DataMapper::class); - $protections = $dataMapper->map(Protection::class, $statement->fetchAllAssociative()); - $protection = count($protections) ? $protections[0] : null; - - // hasParentFolder() only exists on our XCLASSed Folder subclass. - if (!$protection && $folder instanceof Folder && $folder->hasParentFolder()) { - return self::getProtectionStatic($folder->getParentFolder()); - } - return $protection; - } } diff --git a/Classes/Middleware/AccessMiddleware.php b/Classes/Middleware/AccessMiddleware.php index a92b23d..8ded215 100644 --- a/Classes/Middleware/AccessMiddleware.php +++ b/Classes/Middleware/AccessMiddleware.php @@ -4,7 +4,6 @@ namespace Fixpunkt\FpFileprotector\Middleware; -use Fixpunkt\FpFileprotector\Domain\Repository\ProtectionRepository; use Fixpunkt\FpFileprotector\Service\AccessService; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -80,7 +79,7 @@ public function process( $folder = $originalFile->getParentFolder(); - $protection = ProtectionRepository::getProtectionStatic($folder); + $protection = $this->accessService->getProtection($folder); if ((!$protection && !$protectedByDefault) || ($protection && $this->accessService->isGranted($protection))) { return $this->releaseFile($storage, $filePath); } diff --git a/Classes/Service/AccessService.php b/Classes/Service/AccessService.php index 6f49cc1..8457785 100644 --- a/Classes/Service/AccessService.php +++ b/Classes/Service/AccessService.php @@ -4,15 +4,51 @@ namespace Fixpunkt\FpFileprotector\Service; -use Fixpunkt\FpFileprotector\Domain\Model\Protection; +use Doctrine\DBAL\ParameterType; +use Fixpunkt\FpFileprotector\Resource\Folder; use Fixpunkt\FpFileprotector\Utility\Access\AccessUtilityInterface; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Resource\FolderInterface; class AccessService { /** @param iterable $accessUtilities */ - public function __construct(private readonly iterable $accessUtilities) {} + public function __construct( + private readonly iterable $accessUtilities, + private readonly ConnectionPool $connectionPool, + ) {} - public function isGranted(Protection $protection): bool + /** + * Reads the raw protection record for a folder or one of its parent folders. + * + * @return array|null + */ + public function getProtection(FolderInterface $folder): ?array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_fpfileprotector_domain_model_protection'); + $protection = $queryBuilder + ->select('*') + ->from('tx_fpfileprotector_domain_model_protection') + ->where( + $queryBuilder->expr()->eq('storage', $queryBuilder->createNamedParameter($folder->getStorage()->getUid(), ParameterType::INTEGER)), + $queryBuilder->expr()->eq('folder', $queryBuilder->createNamedParameter($folder->getIdentifier())) + ) + ->executeQuery() + ->fetchAssociative(); + + if ($protection !== false) { + return $protection; + } + + // hasParentFolder() only exists on our XCLASSed Folder subclass. + if ($folder instanceof Folder && $folder->hasParentFolder()) { + return $this->getProtection($folder->getParentFolder()); + } + return null; + } + + /** @param array $protection Raw protection database record */ + public function isGranted(array $protection): bool { foreach ($this->accessUtilities as $utility) { if ($utility->isGranted($protection)) { diff --git a/Classes/Utility/Access/AccessUtilityInterface.php b/Classes/Utility/Access/AccessUtilityInterface.php index 90df01b..eddd4e3 100644 --- a/Classes/Utility/Access/AccessUtilityInterface.php +++ b/Classes/Utility/Access/AccessUtilityInterface.php @@ -4,11 +4,10 @@ namespace Fixpunkt\FpFileprotector\Utility\Access; -use Fixpunkt\FpFileprotector\Domain\Model\Protection; - interface AccessUtilityInterface { - public function isGranted(Protection $protection): bool; + /** @param array $protection Raw protection database record */ + public function isGranted(array $protection): bool; public function getPartial(): string; public function getPropertiesPartial(): string; } diff --git a/Classes/Utility/Access/BeLoginAccessUtility.php b/Classes/Utility/Access/BeLoginAccessUtility.php index a56cc93..494f558 100644 --- a/Classes/Utility/Access/BeLoginAccessUtility.php +++ b/Classes/Utility/Access/BeLoginAccessUtility.php @@ -4,12 +4,12 @@ namespace Fixpunkt\FpFileprotector\Utility\Access; -use Fixpunkt\FpFileprotector\Domain\Model\Protection; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; class BeLoginAccessUtility implements AccessUtilityInterface { - public function isGranted(Protection $protection): bool + /** @param array $protection Raw protection database record */ + public function isGranted(array $protection): bool { /** @var BackendUserAuthentication|null $beUser */ $beUser = $GLOBALS['BE_USER'] ?? null; @@ -22,11 +22,11 @@ public function isGranted(Protection $protection): bool } foreach ($beUser->getFileStorages() as $storage) { - if ($storage->getUid() !== $protection->getStorage()) { + if ($storage->getUid() !== (int)$protection['storage']) { continue; } try { - $folder = $storage->getFolder($protection->getFolder()); + $folder = $storage->getFolder((string)$protection['folder']); return $storage->isWithinFileMountBoundaries($folder, false); } catch (\Exception) { return false; diff --git a/Classes/Utility/Access/FeLoginAccessUtility.php b/Classes/Utility/Access/FeLoginAccessUtility.php index 1455611..3f51e1d 100644 --- a/Classes/Utility/Access/FeLoginAccessUtility.php +++ b/Classes/Utility/Access/FeLoginAccessUtility.php @@ -4,16 +4,24 @@ namespace Fixpunkt\FpFileprotector\Utility\Access; -use Fixpunkt\FpFileprotector\Domain\Model\Protection; +use Doctrine\DBAL\ParameterType; use Fixpunkt\FpFileprotector\Utility\FrontendUserUtility; +use TYPO3\CMS\Core\Database\ConnectionPool; class FeLoginAccessUtility implements AccessUtilityInterface { - public function __construct(private readonly FrontendUserUtility $frontendUserUtility) {} + private const USERS_MM = 'tx_fpfileprotector_protection_feusers_mm'; + private const GROUPS_MM = 'tx_fpfileprotector_protection_fegroups_mm'; - public function isGranted(Protection $protection): bool + public function __construct( + private readonly FrontendUserUtility $frontendUserUtility, + private readonly ConnectionPool $connectionPool, + ) {} + + /** @param array $protection Raw protection database record */ + public function isGranted(array $protection): bool { - if (!$protection->isFeLogin()) { + if (!(bool)$protection['fe_login']) { return false; } @@ -22,16 +30,20 @@ public function isGranted(Protection $protection): bool return false; } - if ($protection->getUserGroups()->count() === 0 && $protection->getUsers()->count() === 0) { + $protectionUid = (int)$protection['uid']; + $userUids = $this->getRelatedUids(self::USERS_MM, $protectionUid); + $userGroupUids = $this->getRelatedUids(self::GROUPS_MM, $protectionUid); + + if ($userGroupUids === [] && $userUids === []) { return true; } - if (in_array($feUser->get('id'), $protection->getUsersUids(), true)) { + if (in_array($feUser->get('id'), $userUids, true)) { return true; } foreach ($feUser->get('groupIds') as $userGroupId) { - if (in_array($userGroupId, $protection->getUserGroupsUids(), true)) { + if (in_array($userGroupId, $userGroupUids, true)) { return true; } } @@ -39,6 +51,26 @@ public function isGranted(Protection $protection): bool return false; } + /** + * Reads the related foreign uids from an MM table for a protection record. + * + * @return int[] + */ + private function getRelatedUids(string $mmTable, int $protectionUid): array + { + $queryBuilder = $this->connectionPool->getConnectionForTable($mmTable)->createQueryBuilder(); + $uids = $queryBuilder + ->select('uid_foreign') + ->from($mmTable) + ->where( + $queryBuilder->expr()->eq('uid_local', $queryBuilder->createNamedParameter($protectionUid, ParameterType::INTEGER)) + ) + ->executeQuery() + ->fetchFirstColumn(); + + return array_map('intval', $uids); + } + public function getPartial(): string { return 'Access/Fe'; From 0b5e0b4efe2a90c7f4c9e0a38e772712101b86d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannik=20B=C3=B6rgener?= Date: Wed, 15 Jul 2026 18:27:04 +0200 Subject: [PATCH 13/25] move fe_settings to own tca file --- Configuration/TCA/Overrides/access_fe.php | 58 +++++++++++++++++++ ...pfileprotector_domain_model_protection.php | 51 ++-------------- ext_tables.sql | 7 ++- 3 files changed, 68 insertions(+), 48 deletions(-) create mode 100644 Configuration/TCA/Overrides/access_fe.php diff --git a/Configuration/TCA/Overrides/access_fe.php b/Configuration/TCA/Overrides/access_fe.php new file mode 100644 index 0000000..ee4a658 --- /dev/null +++ b/Configuration/TCA/Overrides/access_fe.php @@ -0,0 +1,58 @@ + [ + 'label' => 'LLL:EXT:fp_fileprotector/Resources/Private/Language/locallang.xlf:tx_fpfileprotector_domain_model_protection.fe_login', + 'exclude' => 1, + 'onChange' => 'reload', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + ] + ], + 'user_groups' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:fp_fileprotector/Resources/Private/Language/locallang.xlf:tx_fpfileprotector_domain_model_protection.user_groups', + 'displayCond' => 'FIELD:fe_login:REQ:true', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'fe_groups', + 'MM' => 'tx_fpfileprotector_protection_fegroups_mm' + ], + ], + 'users' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:fp_fileprotector/Resources/Private/Language/locallang.xlf:tx_fpfileprotector_domain_model_protection.users', + 'displayCond' => 'FIELD:fe_login:REQ:true', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'fe_users', + 'MM' => 'tx_fpfileprotector_protection_feusers_mm' + ], + ], +]; + +// Add fields to the general record description. +\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns($table, $tempColumns); + +// Group the FE fields in their own palette. +\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToPalette( + $table, + 'fe', + 'fe_login,--linebreak--,user_groups,--linebreak--,users' +); + +// Render the palette in the backend form. +\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes( + $table, + '--palette--;LLL:EXT:fp_fileprotector/Resources/Private/Language/locallang.xlf:tx_fpfileprotector_domain_model_protection.palette.fe;fe' +); diff --git a/Configuration/TCA/tx_fpfileprotector_domain_model_protection.php b/Configuration/TCA/tx_fpfileprotector_domain_model_protection.php index 6831d2b..03450cd 100644 --- a/Configuration/TCA/tx_fpfileprotector_domain_model_protection.php +++ b/Configuration/TCA/tx_fpfileprotector_domain_model_protection.php @@ -10,20 +10,16 @@ 'crdate' => 'crdate', 'delete' => 'deleted', 'searchFields' => 'folder', - 'iconfile' => 'EXT:fp_fileprotector/Resources/Public/Icons/Models/tx_fpfileprotector_domain_model_protection.svg' + 'iconfile' => 'EXT:fp_fileprotector/Resources/Public/Icons/Models/tx_fpfileprotector_domain_model_protection.svg', ], 'palettes' => [ 'folder' => [ 'label' => 'LLL:EXT:fp_fileprotector/Resources/Private/Language/locallang.xlf:tx_fpfileprotector_domain_model_protection.palette.folder', 'showitem' => 'storage,folder', ], - 'fe' => [ - 'label' => 'LLL:EXT:fp_fileprotector/Resources/Private/Language/locallang.xlf:tx_fpfileprotector_domain_model_protection.palette.fe', - 'showitem' => 'fe_login,--linebreak--,user_groups,--linebreak--,users', - ], ], 'types' => [ - 0 => ['showitem' => '--palette--;;folder,--palette--;;fe'], + 0 => ['showitem' => '--palette--;;folder'], ], 'columns' => [ 'storage' => [ @@ -33,7 +29,7 @@ 'type' => 'select', 'renderType' => 'selectSingle', 'items' => [ - ['label' => '', 'value' => 0] + ['label' => '', 'value' => 0], ], 'foreign_table' => 'sys_file_storage', 'foreign_table_where' => 'AND {#sys_file_storage}.{#protected} = 1', @@ -41,7 +37,7 @@ 'minitems' => 0, 'maxitems' => 1, 'default' => 0, - ] + ], ], 'folder' => [ 'label' => 'LLL:EXT:fp_fileprotector/Resources/Private/Language/locallang.xlf:tx_fpfileprotector_domain_model_protection.folder', @@ -51,45 +47,6 @@ 'items' => [], 'itemsProcFunc' => 'TYPO3\\CMS\\Core\\Resource\\Service\\UserFileMountService->renderTceformsSelectDropdown', 'default' => '', - ] - ], - 'fe_login' => [ - 'label' => 'LLL:EXT:fp_fileprotector/Resources/Private/Language/locallang.xlf:tx_fpfileprotector_domain_model_protection.fe_login', - 'exclude' => 1, - 'onChange' => 'reload', - 'config' => [ - 'type' => 'check', - 'renderType' => 'checkboxToggle', - ] - ], - 'be_login' => [ - 'label' => 'LLL:EXT:fp_fileprotector/Resources/Private/Language/locallang.xlf:tx_fpfileprotector_domain_model_protection.be_login', - 'exclude' => 1, - 'config' => [ - 'type' => 'check', - 'renderType' => 'checkboxToggle', - ] - ], - 'user_groups' => [ - 'exclude' => 1, - 'label' => 'LLL:EXT:fp_fileprotector/Resources/Private/Language/locallang.xlf:tx_fpfileprotector_domain_model_protection.user_groups', - 'displayCond' => 'FIELD:fe_login:REQ:true', - 'config' => [ - 'type' => 'select', - 'renderType' => 'selectMultipleSideBySide', - 'foreign_table' => 'fe_groups', - 'MM' => 'tx_fpfileprotector_protection_fegroups_mm' - ], - ], - 'users' => [ - 'exclude' => 1, - 'label' => 'LLL:EXT:fp_fileprotector/Resources/Private/Language/locallang.xlf:tx_fpfileprotector_domain_model_protection.users', - 'displayCond' => 'FIELD:fe_login:REQ:true', - 'config' => [ - 'type' => 'select', - 'renderType' => 'selectMultipleSideBySide', - 'foreign_table' => 'fe_users', - 'MM' => 'tx_fpfileprotector_protection_feusers_mm' ], ], ], diff --git a/ext_tables.sql b/ext_tables.sql index 76d3b92..da16492 100644 --- a/ext_tables.sql +++ b/ext_tables.sql @@ -10,8 +10,13 @@ CREATE TABLE sys_file_storage ( CREATE TABLE tx_fpfileprotector_domain_model_protection ( storage int(11) unsigned DEFAULT 0 NOT NULL, folder varchar(255) DEFAULT '' NOT NULL, +); + +# +# Table structure for table 'tx_fpfileprotector_domain_model_protection' for fe_login check +# +CREATE TABLE tx_fpfileprotector_domain_model_protection ( fe_login tinyint(3) DEFAULT 0 NOT NULL, - be_login tinyint(3) DEFAULT 0 NOT NULL, user_groups int(11) unsigned DEFAULT 0 NOT NULL, users int(11) unsigned DEFAULT 0 NOT NULL, ); \ No newline at end of file From cd5c7ed9fead446b588106db1b095b10f60911d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannik=20B=C3=B6rgener?= Date: Wed, 15 Jul 2026 18:42:41 +0200 Subject: [PATCH 14/25] move partials --- Classes/Controller/FolderController.php | 2 +- Classes/Service/AccessService.php | 25 ++++++++----------- .../Utility/Access/AccessUtilityInterface.php | 3 +-- .../Utility/Access/BeLoginAccessUtility.php | 7 +----- .../Utility/Access/FeLoginAccessUtility.php | 7 +----- Configuration/Services.yaml | 2 +- .../Partials/Access/{Be.html => Be/Edit.html} | 0 .../Be.html => Access/Be/Show.html} | 0 .../Partials/Access/{Fe.html => Fe/Edit.html} | 0 .../Fe.html => Access/Fe/Show.html} | 0 .../Partials/Protection/FormFields.html | 2 +- .../Partials/Protection/Properties.html | 2 +- 12 files changed, 17 insertions(+), 33 deletions(-) rename Resources/Private/Partials/Access/{Be.html => Be/Edit.html} (100%) rename Resources/Private/Partials/{Properties/Be.html => Access/Be/Show.html} (100%) rename Resources/Private/Partials/Access/{Fe.html => Fe/Edit.html} (100%) rename Resources/Private/Partials/{Properties/Fe.html => Access/Fe/Show.html} (100%) diff --git a/Classes/Controller/FolderController.php b/Classes/Controller/FolderController.php index 0bba7a0..da4dc3a 100644 --- a/Classes/Controller/FolderController.php +++ b/Classes/Controller/FolderController.php @@ -60,7 +60,7 @@ public function showAction(string $id = '', bool $refreshFolderTree = false): Re $this->statusCheck($folder); $moduleTemplate->assignMultiple([ 'folder' => $folder, - 'propertiesPartials' => $this->accessService->getPropertiesPartials(), + 'partials' => $this->accessService->getPartials(), ]); return $moduleTemplate->renderResponse('Folder/Show'); } diff --git a/Classes/Service/AccessService.php b/Classes/Service/AccessService.php index 8457785..0833da8 100644 --- a/Classes/Service/AccessService.php +++ b/Classes/Service/AccessService.php @@ -4,24 +4,29 @@ namespace Fixpunkt\FpFileprotector\Service; +use Doctrine\DBAL\Exception; use Doctrine\DBAL\ParameterType; use Fixpunkt\FpFileprotector\Resource\Folder; use Fixpunkt\FpFileprotector\Utility\Access\AccessUtilityInterface; use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException; use TYPO3\CMS\Core\Resource\FolderInterface; class AccessService { - /** @param iterable $accessUtilities */ + /** @param iterable $accessTypes */ public function __construct( - private readonly iterable $accessUtilities, + private readonly iterable $accessTypes, private readonly ConnectionPool $connectionPool, ) {} /** * Reads the raw protection record for a folder or one of its parent folders. * + * @param FolderInterface $folder * @return array|null + * @throws Exception + * @throws InsufficientFolderAccessPermissionsException */ public function getProtection(FolderInterface $folder): ?array { @@ -50,7 +55,7 @@ public function getProtection(FolderInterface $folder): ?array /** @param array $protection Raw protection database record */ public function isGranted(array $protection): bool { - foreach ($this->accessUtilities as $utility) { + foreach ($this->accessTypes as $utility) { if ($utility->isGranted($protection)) { return true; } @@ -62,18 +67,8 @@ public function isGranted(array $protection): bool public function getPartials(): array { $partials = []; - foreach ($this->accessUtilities as $utility) { - $partials[] = $utility->getPartial(); - } - return $partials; - } - - /** @return string[] */ - public function getPropertiesPartials(): array - { - $partials = []; - foreach ($this->accessUtilities as $utility) { - $partials[] = $utility->getPropertiesPartial(); + foreach ($this->accessTypes as $utility) { + $partials[] = $utility->getPartials(); } return $partials; } diff --git a/Classes/Utility/Access/AccessUtilityInterface.php b/Classes/Utility/Access/AccessUtilityInterface.php index eddd4e3..1db575d 100644 --- a/Classes/Utility/Access/AccessUtilityInterface.php +++ b/Classes/Utility/Access/AccessUtilityInterface.php @@ -8,6 +8,5 @@ interface AccessUtilityInterface { /** @param array $protection Raw protection database record */ public function isGranted(array $protection): bool; - public function getPartial(): string; - public function getPropertiesPartial(): string; + public function getPartials(): string; } diff --git a/Classes/Utility/Access/BeLoginAccessUtility.php b/Classes/Utility/Access/BeLoginAccessUtility.php index 494f558..423d521 100644 --- a/Classes/Utility/Access/BeLoginAccessUtility.php +++ b/Classes/Utility/Access/BeLoginAccessUtility.php @@ -36,13 +36,8 @@ public function isGranted(array $protection): bool return false; } - public function getPartial(): string + public function getPartials(): string { return 'Access/Be'; } - - public function getPropertiesPartial(): string - { - return 'Properties/Be'; - } } diff --git a/Classes/Utility/Access/FeLoginAccessUtility.php b/Classes/Utility/Access/FeLoginAccessUtility.php index 3f51e1d..5f17419 100644 --- a/Classes/Utility/Access/FeLoginAccessUtility.php +++ b/Classes/Utility/Access/FeLoginAccessUtility.php @@ -71,13 +71,8 @@ private function getRelatedUids(string $mmTable, int $protectionUid): array return array_map('intval', $uids); } - public function getPartial(): string + public function getPartials(): string { return 'Access/Fe'; } - - public function getPropertiesPartial(): string - { - return 'Properties/Fe'; - } } diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index c614b37..6ab3bd7 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -26,7 +26,7 @@ services: # Inject all tagged access utilities into AccessService Fixpunkt\FpFileprotector\Service\AccessService: arguments: - $accessUtilities: !tagged_iterator 'fp_fileprotector.access' + $accessTypes: !tagged_iterator 'fp_fileprotector.access' Fixpunkt\FpFileprotector\EventListener\ModifyIconForResourcePropertiesListener: tags: diff --git a/Resources/Private/Partials/Access/Be.html b/Resources/Private/Partials/Access/Be/Edit.html similarity index 100% rename from Resources/Private/Partials/Access/Be.html rename to Resources/Private/Partials/Access/Be/Edit.html diff --git a/Resources/Private/Partials/Properties/Be.html b/Resources/Private/Partials/Access/Be/Show.html similarity index 100% rename from Resources/Private/Partials/Properties/Be.html rename to Resources/Private/Partials/Access/Be/Show.html diff --git a/Resources/Private/Partials/Access/Fe.html b/Resources/Private/Partials/Access/Fe/Edit.html similarity index 100% rename from Resources/Private/Partials/Access/Fe.html rename to Resources/Private/Partials/Access/Fe/Edit.html diff --git a/Resources/Private/Partials/Properties/Fe.html b/Resources/Private/Partials/Access/Fe/Show.html similarity index 100% rename from Resources/Private/Partials/Properties/Fe.html rename to Resources/Private/Partials/Access/Fe/Show.html diff --git a/Resources/Private/Partials/Protection/FormFields.html b/Resources/Private/Partials/Protection/FormFields.html index 4eba5c9..675ef6d 100644 --- a/Resources/Private/Partials/Protection/FormFields.html +++ b/Resources/Private/Partials/Protection/FormFields.html @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/Resources/Private/Partials/Protection/Properties.html b/Resources/Private/Partials/Protection/Properties.html index 33c2dec..0eee639 100644 --- a/Resources/Private/Partials/Protection/Properties.html +++ b/Resources/Private/Partials/Protection/Properties.html @@ -1,5 +1,5 @@
- +
From 0b2480ab176ab43902789da48048933f18efb9f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannik=20B=C3=B6rgener?= Date: Wed, 15 Jul 2026 19:19:31 +0200 Subject: [PATCH 15/25] check rights with tca and remove models --- Classes/Service/ProtectionService.php | 271 ++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 Classes/Service/ProtectionService.php diff --git a/Classes/Service/ProtectionService.php b/Classes/Service/ProtectionService.php new file mode 100644 index 0000000..db161a2 --- /dev/null +++ b/Classes/Service/ProtectionService.php @@ -0,0 +1,271 @@ +|null + */ + public function getRecord(FolderInterface $folder, bool $recursive = true): ?array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE); + $record = $queryBuilder + ->select('*') + ->from(self::TABLE) + ->where( + $queryBuilder->expr()->eq('storage', $queryBuilder->createNamedParameter($folder->getStorage()->getUid(), ParameterType::INTEGER)), + $queryBuilder->expr()->eq('folder', $queryBuilder->createNamedParameter($folder->getIdentifier())) + ) + ->executeQuery() + ->fetchAssociative(); + + if ($record !== false) { + return $record; + } + + // hasParentFolder() only exists on our XCLASSed Folder subclass. + if ($recursive && $folder instanceof Folder && $folder->hasParentFolder()) { + return $this->getRecord($folder->getParentFolder(), true); + } + return null; + } + + /** + * Creates a new protection record and returns its uid. + * + * @param array $accessFields Raw access-type fields (keyed by TCA column) + */ + public function create(int $storage, string $folder, array $accessFields, int $pid = 0): int + { + $placeholder = StringUtility::getUniqueId('NEW'); + $data = [ + self::TABLE => [ + $placeholder => array_merge($this->sanitizeAccessFields($accessFields), [ + 'pid' => $pid, + 'storage' => $storage, + 'folder' => $folder, + ]), + ], + ]; + + $dataHandler = $this->getDataHandler(); + $dataHandler->start($data, []); + $dataHandler->process_datamap(); + + return (int)($dataHandler->substNEWwithIDs[$placeholder] ?? 0); + } + + /** + * Updates the access-type fields of an existing protection record. + * + * @param array $accessFields Raw access-type fields (keyed by TCA column) + */ + public function update(int $uid, array $accessFields): void + { + $data = [ + self::TABLE => [ + $uid => $this->sanitizeAccessFields($accessFields), + ], + ]; + + $dataHandler = $this->getDataHandler(); + $dataHandler->start($data, []); + $dataHandler->process_datamap(); + } + + public function delete(int $uid): void + { + $cmd = [ + self::TABLE => [ + $uid => ['delete' => 1], + ], + ]; + + $dataHandler = $this->getDataHandler(); + $dataHandler->start([], $cmd); + $dataHandler->process_cmdmap(); + } + + /** + * Reads a protection record for form prefill. Scalar columns are returned + * as-is; every column backed by an MM relation is resolved to an array of + * the currently selected foreign uids. + * + * @return array + */ + public function getFormValues(int $uid): array + { + $record = $this->fetchByUid($uid); + if ($record === null) { + return []; + } + + foreach ($this->getMmColumns() as $field => $mmTable) { + $record[$field] = $this->getRelationUids($mmTable, $uid); + } + + return $record; + } + + /** + * Reads a protection record for read-only display. Scalar columns are + * returned as-is; every column backed by an MM relation is resolved to a + * list of ['uid' => int, 'label' => string] entries. + * + * @return array + */ + public function getDisplayValues(int $uid): array + { + $record = $this->fetchByUid($uid); + if ($record === null) { + return []; + } + + foreach ($this->getMmColumns() as $field => $mmTable) { + $foreignTable = (string)($GLOBALS['TCA'][self::TABLE]['columns'][$field]['config']['foreign_table'] ?? ''); + $record[$field] = $this->resolveLabels($foreignTable, $this->getRelationUids($mmTable, $uid)); + } + + return $record; + } + + /** + * Restricts submitted fields to the access-type columns of the table so + * that neither the identifying columns nor system columns can be injected. + * + * @param array $accessFields + * @return array + */ + private function sanitizeAccessFields(array $accessFields): array + { + $columns = array_keys($GLOBALS['TCA'][self::TABLE]['columns'] ?? []); + $allowed = array_diff($columns, ['storage', 'folder']); + return array_intersect_key($accessFields, array_flip($allowed)); + } + + /** + * @return array|null + */ + private function fetchByUid(int $uid): ?array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE); + $record = $queryBuilder + ->select('*') + ->from(self::TABLE) + ->where( + $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, ParameterType::INTEGER)) + ) + ->executeQuery() + ->fetchAssociative(); + + return $record === false ? null : $record; + } + + /** + * Returns all columns of the protection table that are backed by an MM + * relation, mapped to their MM table. + * + * @return array + */ + private function getMmColumns(): array + { + $columns = []; + foreach (($GLOBALS['TCA'][self::TABLE]['columns'] ?? []) as $field => $config) { + $mmTable = $config['config']['MM'] ?? null; + if ($mmTable !== null) { + $columns[$field] = (string)$mmTable; + } + } + return $columns; + } + + /** + * @return int[] + */ + private function getRelationUids(string $mmTable, int $uid): array + { + $queryBuilder = $this->connectionPool->getConnectionForTable($mmTable)->createQueryBuilder(); + $uids = $queryBuilder + ->select('uid_foreign') + ->from($mmTable) + ->where( + $queryBuilder->expr()->eq('uid_local', $queryBuilder->createNamedParameter($uid, ParameterType::INTEGER)) + ) + ->orderBy('sorting') + ->executeQuery() + ->fetchFirstColumn(); + + return array_map('intval', $uids); + } + + /** + * Resolves foreign uids to ['uid' => int, 'label' => string] entries, + * preserving the given order. The label field is taken from the foreign + * table's TCA ctrl definition. + * + * @param int[] $uids + * @return array + */ + private function resolveLabels(string $foreignTable, array $uids): array + { + if ($foreignTable === '' || $uids === []) { + return []; + } + + $labelField = (string)($GLOBALS['TCA'][$foreignTable]['ctrl']['label'] ?? 'uid'); + + $queryBuilder = $this->connectionPool->getQueryBuilderForTable($foreignTable); + $rows = $queryBuilder + ->select('uid', $labelField) + ->from($foreignTable) + ->where( + $queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($uids, ArrayParameterType::INTEGER)) + ) + ->executeQuery() + ->fetchAllAssociative(); + + $labels = []; + foreach ($rows as $row) { + $labels[(int)$row['uid']] = (string)$row[$labelField]; + } + + $result = []; + foreach ($uids as $uid) { + if (isset($labels[$uid])) { + $result[] = ['uid' => $uid, 'label' => $labels[$uid]]; + } + } + return $result; + } + + private function getDataHandler(): DataHandler + { + return GeneralUtility::makeInstance(DataHandler::class); + } +} From a3db71f25bc4e88e611bd511dd6aa132bfee823e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannik=20B=C3=B6rgener?= Date: Wed, 15 Jul 2026 19:19:47 +0200 Subject: [PATCH 16/25] check rights with tca and remove model --- Classes/Controller/FolderController.php | 25 ++- Classes/Controller/ProtectionController.php | 77 ++++---- Classes/Domain/Model/Protection.php | 186 ------------------ .../Repository/ProtectionRepository.php | 52 ----- Classes/Resource/Folder.php | 34 ++-- Classes/Service/AccessService.php | 31 +-- .../Private/Partials/Access/Be/Show.html | 2 +- .../Private/Partials/Access/Fe/Edit.html | 14 +- .../Private/Partials/Access/Fe/Show.html | 14 +- .../Partials/Protection/Properties.html | 2 +- Resources/Private/Templates/Folder/Show.html | 10 +- .../Private/Templates/Protection/Edit.html | 10 +- .../Private/Templates/Protection/New.html | 7 +- 13 files changed, 116 insertions(+), 348 deletions(-) delete mode 100644 Classes/Domain/Model/Protection.php delete mode 100644 Classes/Domain/Repository/ProtectionRepository.php diff --git a/Classes/Controller/FolderController.php b/Classes/Controller/FolderController.php index da4dc3a..b984804 100644 --- a/Classes/Controller/FolderController.php +++ b/Classes/Controller/FolderController.php @@ -7,6 +7,7 @@ use Fixpunkt\FpFileprotector\Domain\Repository\FolderRepository; use Fixpunkt\FpFileprotector\Resource\Folder; use Fixpunkt\FpFileprotector\Service\AccessService; +use Fixpunkt\FpFileprotector\Service\ProtectionService; use Psr\Http\Message\ResponseInterface; use TYPO3\CMS\Backend\Attribute\AsController; use TYPO3\CMS\Backend\Template\Components\ButtonBar; @@ -31,6 +32,7 @@ public function __construct( protected readonly FolderRepository $folderRepository, protected readonly StorageRepository $storageRepository, protected readonly AccessService $accessService, + protected readonly ProtectionService $protectionService, ) {} /** @@ -58,18 +60,37 @@ public function showAction(string $id = '', bool $refreshFolderTree = false): Re $moduleTemplate = $this->moduleTemplateFactory->create($this->request); $this->initializeDocHeader($moduleTemplate, $folder); $this->statusCheck($folder); + + $protection = $folder->getProtection(); $moduleTemplate->assignMultiple([ 'folder' => $folder, - 'partials' => $this->accessService->getPartials(), + 'accessPartials' => $this->accessService->getPartials(), + 'protectionDisplay' => $protection ? $this->protectionService->getDisplayValues((int)$protection['uid']) : [], + 'inheritedFolder' => $this->getInheritedFolder($folder, $protection), ]); return $moduleTemplate->renderResponse('Folder/Show'); } + /** + * Returns the folder an inherited protection originates from, or null when + * the folder has its own (or no) protection. + * + * @param array|null $protection + */ + protected function getInheritedFolder(Folder $folder, ?array $protection): ?Folder + { + if ($protection === null || $folder->getOwnProtection() !== null) { + return null; + } + $combinedIdentifier = $protection['storage'] . ':' . $protection['folder']; + return $this->folderRepository->findOneByCombinedIdentifier($combinedIdentifier); + } + protected function statusCheck(Folder $folder): void { // show information if the storage is NOT protected if (!$folder->getStorage()->isProtected()) { - if ($folder->isProtected()) { + if ($folder->getProtection() !== null) { $this->addFlashMessage( LocalizationUtility::translate('folder.show.storage_not_protected_with_rule', 'FpFileprotector'), LocalizationUtility::translate('folder.show.storage_not_protected', 'FpFileprotector'), diff --git a/Classes/Controller/ProtectionController.php b/Classes/Controller/ProtectionController.php index 951370d..054a198 100644 --- a/Classes/Controller/ProtectionController.php +++ b/Classes/Controller/ProtectionController.php @@ -4,19 +4,17 @@ namespace Fixpunkt\FpFileprotector\Controller; -use Fixpunkt\FpFileprotector\Domain\Model\Protection; use Fixpunkt\FpFileprotector\Domain\Repository\FolderRepository; use Fixpunkt\FpFileprotector\Domain\Repository\FrontendUserGroupRepository; use Fixpunkt\FpFileprotector\Domain\Repository\FrontendUserRepository; -use Fixpunkt\FpFileprotector\Domain\Repository\ProtectionRepository; use Fixpunkt\FpFileprotector\Service\AccessService; +use Fixpunkt\FpFileprotector\Service\ProtectionService; use Psr\Http\Message\ResponseInterface; use TYPO3\CMS\Backend\Attribute\AsController; use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; -use TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException; -use TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException; use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; @@ -25,11 +23,11 @@ class ProtectionController extends ActionController { public function __construct( protected readonly ModuleTemplateFactory $moduleTemplateFactory, - protected readonly ProtectionRepository $protectionRepository, protected readonly FrontendUserGroupRepository $userGroupRepository, protected readonly FrontendUserRepository $userRepository, protected readonly FolderRepository $folderRepository, protected readonly AccessService $accessService, + protected readonly ProtectionService $protectionService, ) { $querySettings = GeneralUtility::makeInstance(Typo3QuerySettings::class); $querySettings->setRespectStoragePage(false); @@ -39,20 +37,17 @@ public function __construct( /** * Provides the folder protection creation form. - * - * @param string $combinedIdentifier - * @return ResponseInterface */ public function newAction(string $combinedIdentifier): ResponseInterface { $moduleTemplate = $this->moduleTemplateFactory->create($this->request); $moduleTemplate->assignMultiple([ - 'protection' => GeneralUtility::makeInstance(Protection::class), 'folder' => $this->folderRepository->findOneByCombinedIdentifier($combinedIdentifier), 'userGroups' => $this->userGroupRepository->findAll(), 'users' => $this->userRepository->findAll(), 'accessPartials' => $this->accessService->getPartials(), + 'record' => [], ]); return $moduleTemplate->renderResponse('Protection/New'); @@ -61,72 +56,82 @@ public function newAction(string $combinedIdentifier): ResponseInterface /** * Creates a new folder protection. * - * @param Protection $protection - * @return ResponseInterface - * @throws IllegalObjectTypeException + * @param array $access Raw access-type fields keyed by TCA column */ - public function createAction(Protection $protection): ResponseInterface + public function createAction(string $combinedIdentifier, array $access = []): ResponseInterface { - $this->protectionRepository->add($protection); + $folder = $this->folderRepository->findOneByCombinedIdentifier($combinedIdentifier); + $this->protectionService->create( + $folder->getStorage()->getUid(), + $folder->getIdentifier(), + $access, + $this->getStoragePid() + ); + $this->addFlashMessage(LocalizationUtility::translate( 'tx_fpfileprotector_domain_model_protection.flashmessages.created', 'FpFileprotector' )); - return $this->redirect('show', 'Folder', null, ['id' => $protection->getFolderObject()->getCombinedIdentifier(), 'refreshFolderTree' => true]); + return $this->redirect('show', 'Folder', null, ['id' => $combinedIdentifier, 'refreshFolderTree' => true]); } /** * Provides the folder protection edit form. - * - * @param Protection $protection - * @return ResponseInterface */ - public function editAction(Protection $protection): ResponseInterface + public function editAction(int $protectionUid, string $combinedIdentifier): ResponseInterface { $moduleTemplate = $this->moduleTemplateFactory->create($this->request); $moduleTemplate->assignMultiple([ - 'protection' => $protection, - 'folder' => $protection->getFolderObject(), + 'folder' => $this->folderRepository->findOneByCombinedIdentifier($combinedIdentifier), 'userGroups' => $this->userGroupRepository->findAll(), 'users' => $this->userRepository->findAll(), 'accessPartials' => $this->accessService->getPartials(), + 'protectionUid' => $protectionUid, + 'record' => $this->protectionService->getFormValues($protectionUid), ]); return $moduleTemplate->renderResponse('Protection/Edit'); } /** - * @param Protection $protection - * @return ResponseInterface - * @throws IllegalObjectTypeException - * @throws UnknownObjectException + * Updates an existing folder protection. + * + * @param array $access Raw access-type fields keyed by TCA column */ - public function updateAction(Protection $protection): ResponseInterface + public function updateAction(int $protectionUid, string $combinedIdentifier, array $access = []): ResponseInterface { - $this->protectionRepository->update($protection); + $this->protectionService->update($protectionUid, $access); + $this->addFlashMessage(LocalizationUtility::translate( 'tx_fpfileprotector_domain_model_protection.flashmessages.updated', 'FpFileprotector' )); - - return $this->redirect('show', 'Folder', null, ['id' => $protection->getFolderObject()->getCombinedIdentifier(), 'refreshFolderTree' => true]); + return $this->redirect('show', 'Folder', null, ['id' => $combinedIdentifier, 'refreshFolderTree' => true]); } /** * Removes folder protection. - * - * @param Protection $protection - * @return ResponseInterface - * @throws IllegalObjectTypeException */ - public function deleteAction(Protection $protection): ResponseInterface + public function deleteAction(int $protectionUid, string $combinedIdentifier): ResponseInterface { - $this->protectionRepository->remove($protection); + $this->protectionService->delete($protectionUid); + $this->addFlashMessage(LocalizationUtility::translate( 'tx_fpfileprotector_domain_model_protection.flashmessages.deleted', 'FpFileprotector' )); - return $this->redirect('show', 'Folder', null, ['id' => $protection->getFolderObject()->getCombinedIdentifier(), 'refreshFolderTree' => true]); + return $this->redirect('show', 'Folder', null, ['id' => $combinedIdentifier, 'refreshFolderTree' => true]); + } + + /** + * Returns the configured storage pid for new records (falls back to root). + */ + protected function getStoragePid(): int + { + $framework = $this->configurationManager->getConfiguration( + ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK + ); + return (int)($framework['persistence']['storagePid'] ?? 0); } } diff --git a/Classes/Domain/Model/Protection.php b/Classes/Domain/Model/Protection.php deleted file mode 100644 index 57b8818..0000000 --- a/Classes/Domain/Model/Protection.php +++ /dev/null @@ -1,186 +0,0 @@ -|null */ - protected ?ObjectStorage $userGroups = null; - /** @var ObjectStorage|null */ - protected ?ObjectStorage $users = null; - - public function __construct() - { - $this->userGroups = new ObjectStorage(); - $this->users = new ObjectStorage(); - } - - /** - * @return int - */ - public function getStorage(): int - { - return $this->storage; - } - /** - * @param int $storage - */ - public function setStorage(int $storage): void - { - $this->storage = $storage; - } - - /** - * @return string - */ - public function getFolder(): string - { - return $this->folder; - } - /** - * @param string $folder - */ - public function setFolder(string $folder): void - { - $this->folder = $folder; - } - /** - * Returns the folder as an object. - * - * @return Folder|null - */ - public function getFolderObject(): ?Folder - { - /** @var FolderRepository $folderRepository */ - $folderRepository = GeneralUtility::makeInstance(FolderRepository::class); - return $folderRepository->findOneByCombinedIdentifier($this->getStorage() . ':' . $this->getFolder()); - } - - /** - * @return bool - */ - public function isFeLogin(): bool - { - return $this->feLogin; - } - /** - * @param bool $feLogin - */ - public function setFeLogin(bool $feLogin): void - { - $this->feLogin = $feLogin; - } - - /** - * @return bool - */ - public function isBeLogin(): bool - { - return $this->beLogin; - } - /** - * @param bool $beLogin - */ - public function setBeLogin(bool $beLogin): void - { - $this->beLogin = $beLogin; - } - - /** - * @return ObjectStorage|null - */ - public function getUserGroups(): ?ObjectStorage - { - return $this->userGroups; - } - public function getUserGroupsUids(): array - { - $uids = []; - /** @var FrontendUserGroup $userGroup */ - foreach ($this->getUserGroups() as $userGroup) { - $uids[] = $userGroup->getUid(); - } - return $uids; - } - /** - * @param ObjectStorage $userGroups - */ - public function setUserGroups(ObjectStorage $userGroups): void - { - $this->userGroups = $userGroups; - } - /** - * @param FrontendUserGroup $userGroup - */ - public function addUserGroup(FrontendUserGroup $userGroup): void - { - $this->userGroups->attach($userGroup); - } - /** - * @param FrontendUserGroup $userGroup - */ - public function removeUserGroup(FrontendUserGroup $userGroup): void - { - $this->userGroups->detach($userGroup); - } - - /** - * @return ObjectStorage|null - */ - public function getUsers(): ?ObjectStorage - { - return $this->users; - } - public function getUsersUids(): array - { - $uids = []; - /** @var FrontendUser $user */ - foreach ($this->getUsers() as $user) { - $uids[] = $user->getUid(); - } - return $uids; - } - /** - * @param ObjectStorage $users - */ - public function setUsers(ObjectStorage $users): void - { - $this->users = $users; - } - /** - * @param FrontendUser $user - */ - public function addUser(FrontendUser $user): void - { - $this->users->attach($user); - } - /** - * @param FrontendUser $user - */ - public function removeUser(FrontendUser $user): void - { - $this->users->detach($user); - } - - /** - * Returns whether the folder protection applies any FE restrictions. - * - * @return bool - */ - public function isProtected(): bool - { - return $this->isFeLogin(); - } -} diff --git a/Classes/Domain/Repository/ProtectionRepository.php b/Classes/Domain/Repository/ProtectionRepository.php deleted file mode 100644 index 0da9609..0000000 --- a/Classes/Domain/Repository/ProtectionRepository.php +++ /dev/null @@ -1,52 +0,0 @@ -createQuery(); - $query->getQuerySettings()->setRespectStoragePage(false); - $query->matching( - $query->logicalAnd( - $query->equals('storage', $folder->getStorage()->getUid()), - $query->equals('folder', $folder->getIdentifier()) - ) - ); - $results = $query->execute(); - - $protection = $results->current(); - return $protection instanceof Protection ? $protection : null; - } - - /** - * Finds folder protection for a folder or one of its parent folders. - * - * @param FolderInterface $folder - * @param bool $recursive - * @return Protection|null - */ - public function getProtection(FolderInterface $folder, bool $recursive = true): ?Protection - { - $protection = $this->findOneByFolder($folder); - // hasParentFolder() only exists on our XCLASSed Folder subclass. - if (!$protection && $recursive && $folder instanceof Folder && $folder->hasParentFolder()) { - return $this->getProtection($folder->getParentFolder()); - } - return $protection; - } -} diff --git a/Classes/Resource/Folder.php b/Classes/Resource/Folder.php index c73d7a6..d034326 100644 --- a/Classes/Resource/Folder.php +++ b/Classes/Resource/Folder.php @@ -4,8 +4,8 @@ namespace Fixpunkt\FpFileprotector\Resource; -use Fixpunkt\FpFileprotector\Domain\Model\Protection; -use Fixpunkt\FpFileprotector\Domain\Repository\ProtectionRepository; +use Fixpunkt\FpFileprotector\Service\AccessService; +use Fixpunkt\FpFileprotector\Service\ProtectionService; use TYPO3\CMS\Core\Resource as Core; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -33,23 +33,21 @@ public function getStorage(): ResourceStorage } /** - * Returns protection for this folder or one of its parent folders. + * Returns the raw protection record for this folder or one of its parent + * folders. * - * @param bool $recursive - * @return Protection|null + * @return array|null */ - public function getProtection(bool $recursive = true): ?Protection + public function getProtection(bool $recursive = true): ?array { - /** @var ProtectionRepository $protectionRepository */ - $protectionRepository = GeneralUtility::makeInstance(ProtectionRepository::class); - return $protectionRepository->getProtection($this, $recursive); + return GeneralUtility::makeInstance(ProtectionService::class)->getRecord($this, $recursive); } /** - * Returns protection assigned directly to this folder. + * Returns the raw protection record assigned directly to this folder. * - * @return Protection|null + * @return array|null */ - public function getOwnProtection(): ?Protection + public function getOwnProtection(): ?array { return $this->getProtection(false); } @@ -80,11 +78,15 @@ public function hasParentFolder(): bool public function getProtectionStatus(): string { $protection = $this->getProtection(); - if ($protection && $protection->isProtected()) { - // protection is set - return $this->getOwnProtection() ? 'protected' : 'protected_by_parent'; + if ($protection) { + // A rule applies: "protected" means the current user is granted + // access by at least one access utility; otherwise it is locked. + if (GeneralUtility::makeInstance(AccessService::class)->isGranted($protection)) { + return $this->getOwnProtection() ? 'protected' : 'protected_by_parent'; + } + return 'no_access'; } - // no protection is set + // no rule applies return $this->getStorage()->isProtectedByDefault() ? 'no_access' : 'public'; } diff --git a/Classes/Service/AccessService.php b/Classes/Service/AccessService.php index 0833da8..835369d 100644 --- a/Classes/Service/AccessService.php +++ b/Classes/Service/AccessService.php @@ -4,12 +4,7 @@ namespace Fixpunkt\FpFileprotector\Service; -use Doctrine\DBAL\Exception; -use Doctrine\DBAL\ParameterType; -use Fixpunkt\FpFileprotector\Resource\Folder; use Fixpunkt\FpFileprotector\Utility\Access\AccessUtilityInterface; -use TYPO3\CMS\Core\Database\ConnectionPool; -use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException; use TYPO3\CMS\Core\Resource\FolderInterface; class AccessService @@ -17,39 +12,17 @@ class AccessService /** @param iterable $accessTypes */ public function __construct( private readonly iterable $accessTypes, - private readonly ConnectionPool $connectionPool, + private readonly ProtectionService $protectionService, ) {} /** * Reads the raw protection record for a folder or one of its parent folders. * - * @param FolderInterface $folder * @return array|null - * @throws Exception - * @throws InsufficientFolderAccessPermissionsException */ public function getProtection(FolderInterface $folder): ?array { - $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_fpfileprotector_domain_model_protection'); - $protection = $queryBuilder - ->select('*') - ->from('tx_fpfileprotector_domain_model_protection') - ->where( - $queryBuilder->expr()->eq('storage', $queryBuilder->createNamedParameter($folder->getStorage()->getUid(), ParameterType::INTEGER)), - $queryBuilder->expr()->eq('folder', $queryBuilder->createNamedParameter($folder->getIdentifier())) - ) - ->executeQuery() - ->fetchAssociative(); - - if ($protection !== false) { - return $protection; - } - - // hasParentFolder() only exists on our XCLASSed Folder subclass. - if ($folder instanceof Folder && $folder->hasParentFolder()) { - return $this->getProtection($folder->getParentFolder()); - } - return null; + return $this->protectionService->getRecord($folder); } /** @param array $protection Raw protection database record */ diff --git a/Resources/Private/Partials/Access/Be/Show.html b/Resources/Private/Partials/Access/Be/Show.html index b41746d..da91ab9 100644 --- a/Resources/Private/Partials/Access/Be/Show.html +++ b/Resources/Private/Partials/Access/Be/Show.html @@ -3,7 +3,7 @@
- + diff --git a/Resources/Private/Partials/Access/Fe/Edit.html b/Resources/Private/Partials/Access/Fe/Edit.html index c6555d5..5ac2f62 100644 --- a/Resources/Private/Partials/Access/Fe/Edit.html +++ b/Resources/Private/Partials/Access/Fe/Edit.html @@ -3,7 +3,7 @@

@@ -13,8 +13,9 @@

- +
@@ -33,8 +34,9 @@

- +
@@ -48,4 +50,4 @@

-
\ No newline at end of file + diff --git a/Resources/Private/Partials/Access/Fe/Show.html b/Resources/Private/Partials/Access/Fe/Show.html index 1a40307..db28450 100644 --- a/Resources/Private/Partials/Access/Fe/Show.html +++ b/Resources/Private/Partials/Access/Fe/Show.html @@ -3,28 +3,28 @@
- + - - + +
    - -
  • {userGroup.title}
  • + +
  • {userGroup.label}
- +
    -
  • {user.username}
  • +
  • {user.label}
diff --git a/Resources/Private/Partials/Protection/Properties.html b/Resources/Private/Partials/Protection/Properties.html index 0eee639..19179e5 100644 --- a/Resources/Private/Partials/Protection/Properties.html +++ b/Resources/Private/Partials/Protection/Properties.html @@ -1,5 +1,5 @@
- +
diff --git a/Resources/Private/Templates/Folder/Show.html b/Resources/Private/Templates/Folder/Show.html index 3cafb5f..613e18d 100644 --- a/Resources/Private/Templates/Folder/Show.html +++ b/Resources/Private/Templates/Folder/Show.html @@ -17,7 +17,7 @@

{folder.speakingName}

- + @@ -44,7 +44,7 @@

{folder.speakingName}

- +
@@ -56,7 +56,7 @@

{folder.speakingName}

- +
@@ -83,10 +83,10 @@

{folder.speakingName}

- + - + diff --git a/Resources/Private/Templates/Protection/Edit.html b/Resources/Private/Templates/Protection/Edit.html index 441ff59..240b4b7 100644 --- a/Resources/Private/Templates/Protection/Edit.html +++ b/Resources/Private/Templates/Protection/Edit.html @@ -3,11 +3,15 @@

{folder.name}

- + + + + +
- +
-
\ No newline at end of file + diff --git a/Resources/Private/Templates/Protection/New.html b/Resources/Private/Templates/Protection/New.html index 62e47ae..6be45e5 100644 --- a/Resources/Private/Templates/Protection/New.html +++ b/Resources/Private/Templates/Protection/New.html @@ -3,9 +3,8 @@

{folder.name}

- - - + + @@ -14,4 +13,4 @@

{folder.na

- \ No newline at end of file + From e2382e9098aa61ab5ffb896b2709905ec4cce585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannik=20B=C3=B6rgener?= Date: Wed, 15 Jul 2026 19:52:49 +0200 Subject: [PATCH 17/25] add phpstan --- .build-v13/phpstan.neon | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .build-v13/phpstan.neon diff --git a/.build-v13/phpstan.neon b/.build-v13/phpstan.neon new file mode 100644 index 0000000..947b0b2 --- /dev/null +++ b/.build-v13/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: 5 + paths: + - ../Classes + bootstrapFiles: + - vendor/autoload.php From 84cc8addd038d604d0b27d84f5e6044532cf1740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannik=20B=C3=B6rgener?= Date: Wed, 15 Jul 2026 19:55:55 +0200 Subject: [PATCH 18/25] move access types to own folder --- .../AccessTypeInterface.php} | 4 ++-- .../BeLoginAccessType.php} | 4 ++-- .../FeLoginAccessType.php} | 4 ++-- Classes/Service/AccessService.php | 12 ++++++------ Configuration/Services.yaml | 5 +---- Configuration/TCA/Overrides/access_fe.php | 2 +- 6 files changed, 14 insertions(+), 17 deletions(-) rename Classes/{Utility/Access/AccessUtilityInterface.php => AccessType/AccessTypeInterface.php} (72%) rename Classes/{Utility/Access/BeLoginAccessUtility.php => AccessType/BeLoginAccessType.php} (90%) rename Classes/{Utility/Access/FeLoginAccessUtility.php => AccessType/FeLoginAccessType.php} (95%) diff --git a/Classes/Utility/Access/AccessUtilityInterface.php b/Classes/AccessType/AccessTypeInterface.php similarity index 72% rename from Classes/Utility/Access/AccessUtilityInterface.php rename to Classes/AccessType/AccessTypeInterface.php index 1db575d..7c7549e 100644 --- a/Classes/Utility/Access/AccessUtilityInterface.php +++ b/Classes/AccessType/AccessTypeInterface.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Fixpunkt\FpFileprotector\Utility\Access; +namespace Fixpunkt\FpFileprotector\AccessType; -interface AccessUtilityInterface +interface AccessTypeInterface { /** @param array $protection Raw protection database record */ public function isGranted(array $protection): bool; diff --git a/Classes/Utility/Access/BeLoginAccessUtility.php b/Classes/AccessType/BeLoginAccessType.php similarity index 90% rename from Classes/Utility/Access/BeLoginAccessUtility.php rename to Classes/AccessType/BeLoginAccessType.php index 423d521..ee3d81b 100644 --- a/Classes/Utility/Access/BeLoginAccessUtility.php +++ b/Classes/AccessType/BeLoginAccessType.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Fixpunkt\FpFileprotector\Utility\Access; +namespace Fixpunkt\FpFileprotector\AccessType; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; -class BeLoginAccessUtility implements AccessUtilityInterface +class BeLoginAccessType implements AccessTypeInterface { /** @param array $protection Raw protection database record */ public function isGranted(array $protection): bool diff --git a/Classes/Utility/Access/FeLoginAccessUtility.php b/Classes/AccessType/FeLoginAccessType.php similarity index 95% rename from Classes/Utility/Access/FeLoginAccessUtility.php rename to Classes/AccessType/FeLoginAccessType.php index 5f17419..da8adac 100644 --- a/Classes/Utility/Access/FeLoginAccessUtility.php +++ b/Classes/AccessType/FeLoginAccessType.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace Fixpunkt\FpFileprotector\Utility\Access; +namespace Fixpunkt\FpFileprotector\AccessType; use Doctrine\DBAL\ParameterType; use Fixpunkt\FpFileprotector\Utility\FrontendUserUtility; use TYPO3\CMS\Core\Database\ConnectionPool; -class FeLoginAccessUtility implements AccessUtilityInterface +class FeLoginAccessType implements AccessTypeInterface { private const USERS_MM = 'tx_fpfileprotector_protection_feusers_mm'; private const GROUPS_MM = 'tx_fpfileprotector_protection_fegroups_mm'; diff --git a/Classes/Service/AccessService.php b/Classes/Service/AccessService.php index 835369d..d3e5528 100644 --- a/Classes/Service/AccessService.php +++ b/Classes/Service/AccessService.php @@ -4,12 +4,12 @@ namespace Fixpunkt\FpFileprotector\Service; -use Fixpunkt\FpFileprotector\Utility\Access\AccessUtilityInterface; +use Fixpunkt\FpFileprotector\AccessType\AccessTypeInterface; use TYPO3\CMS\Core\Resource\FolderInterface; class AccessService { - /** @param iterable $accessTypes */ + /** @param iterable $accessTypes */ public function __construct( private readonly iterable $accessTypes, private readonly ProtectionService $protectionService, @@ -28,8 +28,8 @@ public function getProtection(FolderInterface $folder): ?array /** @param array $protection Raw protection database record */ public function isGranted(array $protection): bool { - foreach ($this->accessTypes as $utility) { - if ($utility->isGranted($protection)) { + foreach ($this->accessTypes as $accessType) { + if ($accessType->isGranted($protection)) { return true; } } @@ -40,8 +40,8 @@ public function isGranted(array $protection): bool public function getPartials(): array { $partials = []; - foreach ($this->accessTypes as $utility) { - $partials[] = $utility->getPartials(); + foreach ($this->accessTypes as $accessType) { + $partials[] = $accessType->getPartials(); } return $partials; } diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 6ab3bd7..3ddf6ec 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -6,7 +6,7 @@ services: public: true _instanceof: - Fixpunkt\FpFileprotector\Utility\Access\AccessUtilityInterface: + Fixpunkt\FpFileprotector\AccessType\AccessTypeInterface: tags: - { name: 'fp_fileprotector.access' } @@ -17,9 +17,6 @@ services: - '../Classes/Resource/*' - '../Classes/Utility/*' - Fixpunkt\FpFileprotector\Utility\Access\: - resource: '../Classes/Utility/Access/*' - # Utilities needed for DI Fixpunkt\FpFileprotector\Utility\FrontendUserUtility: ~ diff --git a/Configuration/TCA/Overrides/access_fe.php b/Configuration/TCA/Overrides/access_fe.php index ee4a658..9d56232 100644 --- a/Configuration/TCA/Overrides/access_fe.php +++ b/Configuration/TCA/Overrides/access_fe.php @@ -6,7 +6,7 @@ $table = 'tx_fpfileprotector_domain_model_protection'; -// Fields required for the FE access check (see FeLoginAccessUtility). +// Fields required for the FE access check (see FeLoginAccessType). $tempColumns = [ 'fe_login' => [ 'label' => 'LLL:EXT:fp_fileprotector/Resources/Private/Language/locallang.xlf:tx_fpfileprotector_domain_model_protection.fe_login', From 79ad68be895a922688d04b11fe64841f3cee1e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannik=20B=C3=B6rgener?= Date: Wed, 15 Jul 2026 20:17:53 +0200 Subject: [PATCH 19/25] update documentation --- Documentation/AccessTypes/Be/Index.rst | 41 +++++ Documentation/AccessTypes/Fe/Index.rst | 56 +++++++ Documentation/AccessTypes/Index.rst | 54 +++++++ Documentation/ForDevelopers/Index.rst | 202 +++++++++++++++++++++++++ Documentation/FuturePlans/Index.rst | 2 - Documentation/Index.rst | 16 ++ Documentation/Usage/Folders/Index.rst | 26 +--- 7 files changed, 373 insertions(+), 24 deletions(-) create mode 100644 Documentation/AccessTypes/Be/Index.rst create mode 100644 Documentation/AccessTypes/Fe/Index.rst create mode 100644 Documentation/AccessTypes/Index.rst create mode 100644 Documentation/ForDevelopers/Index.rst diff --git a/Documentation/AccessTypes/Be/Index.rst b/Documentation/AccessTypes/Be/Index.rst new file mode 100644 index 0000000..03d7ffb --- /dev/null +++ b/Documentation/AccessTypes/Be/Index.rst @@ -0,0 +1,41 @@ +.. include:: /Includes.rst.txt + +.. _access-type-be: + +========================== +Backend Login Access Type +========================== + +The backend access type grants access based on the login status **and the file +permissions** of a **backend user**. It is implemented in +:php:`Fixpunkt\FpFileprotector\AccessType\BeLoginAccessType`. + +Unlike the frontend access type, the backend access type does not rely on a +list of users or groups stored on the protection rule, and it has **no +configuration of its own**. It is always evaluated for every protected folder +and reads the access rights **directly from the logged-in backend user**, +reusing the file mount and file storage permissions that TYPO3 already manages +for that user. + +How it works +============ + +The access type evaluates the request in the following order: + +#. If no backend user is currently logged in, access is denied. + +#. If the backend user is an **administrator**, access is always granted. + +#. Otherwise, the access type looks up the file storage referenced by the + protection rule among the user's own file storages. Access is granted if + the protected folder lies **within the file mount boundaries** of that + storage for the current user. + +This means a backend user can access exactly those protected folders they are +already allowed to see and manage in the TYPO3 file list — no additional +configuration per user or group is required. + +.. note:: + There is no toggle to enable or disable this access type per folder. Since + it only ever grants access to backend users who already have the necessary + file permissions, it is safe to evaluate it on every protected folder. diff --git a/Documentation/AccessTypes/Fe/Index.rst b/Documentation/AccessTypes/Fe/Index.rst new file mode 100644 index 0000000..c3ba73d --- /dev/null +++ b/Documentation/AccessTypes/Fe/Index.rst @@ -0,0 +1,56 @@ +.. include:: /Includes.rst.txt + +.. _access-type-fe: + +=========================== +Frontend Login Access Type +=========================== + +The frontend access type grants access based on the login status of a +**frontend user**. It is implemented in +:php:`Fixpunkt\FpFileprotector\AccessType\FeLoginAccessType`. + +How it works +============ + +The access type evaluates the protection record in the following order: + +#. If the *Must be logged in to the frontend* option is **disabled** for the + folder, the frontend access type never grants access. + +#. If no frontend user is currently logged in, access is denied. + +#. If neither individual users nor user groups are configured on the + protection rule, **every** logged-in frontend user is granted access. + +#. Otherwise, access is granted if the current frontend user is one of the + selected **users**, or is a member of one of the selected **user groups**. + +.. note:: + Users and user groups are combined with an **OR** conjunction: it is enough + to match either a selected user or a selected group. + +Configuration +============= + +The relevant fields are configured directly on the access rule in the +**File Protection** backend module: + +.. confval:: Must be logged in to the frontend + :name: access-type-fe-fe-login + + Master switch for this access type. If disabled, none of the fields below + have any effect and the frontend access type will not grant access. + +.. confval:: Frontend Users + :name: access-type-fe-users + + Optional list of individual frontend users who may access the folder. + +.. confval:: Frontend User Groups + :name: access-type-fe-user-groups + + Optional list of frontend user groups whose members may access the folder. + +If both the users and the user groups list are left empty, any logged-in +frontend user is granted access. diff --git a/Documentation/AccessTypes/Index.rst b/Documentation/AccessTypes/Index.rst new file mode 100644 index 0000000..3e5ff11 --- /dev/null +++ b/Documentation/AccessTypes/Index.rst @@ -0,0 +1,54 @@ +.. include:: /Includes.rst.txt + +.. _access-types: + +============ +Access Types +============ + +An **access type** is a single, self-contained rule that can grant access to a +protected folder. Every access type answers one simple question for a given +protection record: *"Should the current visitor be allowed in?"* + +When a folder is requested, fp-fileprotector asks every registered access type +in turn. Access is granted as soon as **one** access type grants it — the +access types are combined with an **OR** conjunction. If no access type grants +access, the request is denied. + +fp-fileprotector ships with two access types out of the box: + +.. card-grid:: + :columns: 1 + :columns-md: 2 + :gap: 4 + :card-height: 100 + + .. card:: Frontend Login + + Grant access to logged-in frontend users, optionally limited to + specific users or user groups. + + .. card-footer:: :ref:`How the frontend access type works ` + :button-style: btn btn-secondary stretched-link + + .. card:: Backend Login + + Grant access to backend users based on their own file storage + permissions. + + .. card-footer:: :ref:`How the backend access type works ` + :button-style: btn btn-secondary stretched-link + +.. note:: + The two shipped access types can also be combined on a single folder. A + visitor is then granted access if they satisfy the frontend rule **or** the + backend rule. + +You are not limited to these two access types. See +:ref:`For Developers ` to learn how to add your own. + +.. toctree:: + :hidden: + + Fe/Index + Be/Index diff --git a/Documentation/ForDevelopers/Index.rst b/Documentation/ForDevelopers/Index.rst new file mode 100644 index 0000000..4707e41 --- /dev/null +++ b/Documentation/ForDevelopers/Index.rst @@ -0,0 +1,202 @@ +.. include:: /Includes.rst.txt + +.. _for-developers: + +============== +For Developers +============== + +fp-fileprotector can be extended with your own :ref:`access types `. +An access type is a small PHP class that decides whether the current visitor may +access a protected folder, plus the Fluid partials used to display and edit its +settings in the backend module. + +At runtime, all registered access types are collected and combined with an +**OR** conjunction: access is granted as soon as one of them grants it. Adding +your own access type therefore never restricts existing rules — it only adds a +new way to *grant* access. + +A custom access type consists of the following parts. + +.. contents:: + :local: + +The access type class +====================== + +The heart of every access type is a class that implements +:php:`Fixpunkt\FpFileprotector\AccessType\AccessTypeInterface`. Place it under +:file:`Classes/AccessType/AccessType.php` in your own extension. + +.. code-block:: php + :caption: Classes/AccessType/AccessTypeInterface.php + + interface AccessTypeInterface + { + /** @param array $protection Raw protection database record */ + public function isGranted(array $protection): bool; + public function getPartials(): string; + } + +:php:`isGranted()` + Receives the raw protection database record as an associative array and + returns whether the current visitor should be granted access. Return + :php:`true` to grant access, :php:`false` to abstain. Because access types + are combined with **OR**, returning :php:`false` never blocks another + access type from granting access. + +:php:`getPartials()` + Returns the partial path used to render this access type in the backend + module, e.g. :php:`'Access/MyType'`. The module renders + :file:`/Show.html` to display the rule and :file:`/Edit.html` + to edit it (see :ref:`the partials section `). + +.. code-block:: php + :caption: Classes/AccessType/MyTypeAccessType.php + + $protection Raw protection database record */ + public function isGranted(array $protection): bool + { + // Your access decision, based on the protection record. + return (bool)($protection['my_field'] ?? false); + } + + public function getPartials(): string + { + return 'Access/MyType'; + } + } + +Registering the class +---------------------- + +fp-fileprotector discovers access types through the service container. Your +class must be registered as a service and tagged with +``fp_fileprotector.access``. Add the tag in your extension's +:file:`Configuration/Services.yaml`: + +.. code-block:: yaml + :caption: Configuration/Services.yaml + + services: + _defaults: + autowire: true + autoconfigure: true + + Vendor\MyExtension\: + resource: '../Classes/*' + + Vendor\MyExtension\AccessType\MyTypeAccessType: + tags: + - { name: 'fp_fileprotector.access' } + +.. _for-developers-partials: + +The partials +============ + +Each access type provides two Fluid partials, ideally located under +:file:`Access//` so they match the path returned by +:php:`getPartials()`: + +:file:`Show.html` + Renders the current settings of the rule read-only. It receives the + protection record as the :html:`{protection}` variable. + +:file:`Edit.html` + Renders the form fields used to create or edit the rule. It receives all + available variables (:html:`{_all}`), including the current record. + +.. code-block:: html + :caption: Resources/Private/Partials/Access/MyType/Show.html + +
+
+
+ + Yes + No + +
+
+ +.. code-block:: html + :caption: Resources/Private/Partials/Access/MyType/Edit.html + +
+ +
+ +Making the partials available to the backend module +---------------------------------------------------- + +Because the partials live in your own extension, you have to tell the backend +module where to find them. Add your partial folder to the module's +``partialRootPaths`` in TypoScript. Use an index that is not already taken (for +example ``100``) so you do not overwrite the paths shipped with +fp-fileprotector: + +.. code-block:: typoscript + :caption: Configuration/TypoScript/setup.typoscript + + module.tx_fpfileprotector { + view { + partialRootPaths { + 100 = EXT:my_extension/Resources/Private/Partials/ + } + } + } + +The partial path returned by :php:`getPartials()` (e.g. ``Access/MyType``) is +resolved relative to these ``partialRootPaths``. + +The TCA override (optional) +=========================== + +If your access type needs additional fields on the protection record — for +example a checkbox or a relation — add them via a TCA override for the table +:sql:`tx_fpfileprotector_domain_model_protection`. + +This step is **optional**: if your access decision does not require any extra +data stored on the rule, you can skip it entirely. + +.. code-block:: php + :caption: Configuration/TCA/Overrides/tx_fpfileprotector_domain_model_protection.php + + [ + 'label' => 'My setting', + 'exclude' => 1, + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + ], + ], + ]); + +.. note:: + The values written by your :file:`Edit.html` partial (the ``protection[...]`` + form fields) are what end up in the protection record and are handed to + :php:`isGranted()`. Make sure the field names in the partial, the TCA + override and the class match. diff --git a/Documentation/FuturePlans/Index.rst b/Documentation/FuturePlans/Index.rst index b6140da..d803515 100644 --- a/Documentation/FuturePlans/Index.rst +++ b/Documentation/FuturePlans/Index.rst @@ -6,7 +6,5 @@ Future Plans ============ -* Removing the Backend Access rule and get the access rights directly from the logged in backend user -* Making it possible to easily extend the extension by own protection rule classes. * Making it possible to create more detailed protection rules with conjunctions other than OR. * Allow protection rules to override inherited rules, so that a subfolder can be made accessible again despite a restrictive parent rule (solve problem of "disallow by default" storages) \ No newline at end of file diff --git a/Documentation/Index.rst b/Documentation/Index.rst index 58ecb99..6d7e392 100644 --- a/Documentation/Index.rst +++ b/Documentation/Index.rst @@ -64,6 +64,20 @@ and folders .. card-footer:: :ref:`Learn how fp-fileprotector is used ` :button-style: btn btn-secondary stretched-link + .. card:: Access Types + + How the frontend and backend access types work. + + .. card-footer:: :ref:`Understand the access types ` + :button-style: btn btn-secondary stretched-link + + .. card:: For Developers + + How to extend fp-fileprotector with your own access types. + + .. card-footer:: :ref:`Extend fp-fileprotector ` + :button-style: btn btn-secondary stretched-link + .. card:: Troubleshooting Common issues and solutions. @@ -76,6 +90,8 @@ and folders Introduction/Index Usage/Index + AccessTypes/Index + ForDevelopers/Index TroubleShooting/Index FuturePlans/Index Sitemap diff --git a/Documentation/Usage/Folders/Index.rst b/Documentation/Usage/Folders/Index.rst index 9970e85..65b6c4a 100644 --- a/Documentation/Usage/Folders/Index.rst +++ b/Documentation/Usage/Folders/Index.rst @@ -41,26 +41,8 @@ Creating or Editing a Protection Rule Select who is allowed to access the contents of the respective folder (including subfolders). .. note:: - The selection of groups and users is an **OR** conjunction. Both members - of the selected groups and the individually selected users will have access. - Also the selection of frontend and backend access is an **OR** conjunction. + All conditions are combined with an **OR** conjunction: a visitor is granted + access as soon as any one of them is satisfied. -.. confval:: Must be logged in to the frontend - - If enabled, all users logged into a frontend account can access the folder. To restrict access to specific users or groups, use the **Frontend Users** and **Frontend User Groups** options below. - -.. confval:: Frontend Users - - Select users, which are allowed to access the folder. This selection is an **OR** conjunction. - - This option only applies, if *Must be logged in to the frontend* is enabled! - -.. confval:: Frontend User Groups - - Select user groups, which are allowed to access the folder. This selection is an **OR** conjunction. - - This option only applies, if *Must be logged in to the frontend* is enabled! - -.. confval:: Must be logged in to the backend - - If enabled, all users, that are logged into an backend account can access the folder. \ No newline at end of file +The available conditions are provided by the :ref:`access types `. +See there for a detailed explanation of each condition and how it is evaluated. \ No newline at end of file From 13345ca44e0d0c2fea479a5135679dd6f76522d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannik=20B=C3=B6rgener?= Date: Wed, 15 Jul 2026 20:18:02 +0200 Subject: [PATCH 20/25] use same naming convention --- Classes/Controller/ProtectionController.php | 12 ++++++------ Resources/Private/Partials/Access/Fe/Edit.html | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Classes/Controller/ProtectionController.php b/Classes/Controller/ProtectionController.php index 054a198..b0cf717 100644 --- a/Classes/Controller/ProtectionController.php +++ b/Classes/Controller/ProtectionController.php @@ -56,15 +56,15 @@ public function newAction(string $combinedIdentifier): ResponseInterface /** * Creates a new folder protection. * - * @param array $access Raw access-type fields keyed by TCA column + * @param array $protection Raw access-type fields keyed by TCA column */ - public function createAction(string $combinedIdentifier, array $access = []): ResponseInterface + public function createAction(string $combinedIdentifier, array $protection = []): ResponseInterface { $folder = $this->folderRepository->findOneByCombinedIdentifier($combinedIdentifier); $this->protectionService->create( $folder->getStorage()->getUid(), $folder->getIdentifier(), - $access, + $protection, $this->getStoragePid() ); @@ -97,11 +97,11 @@ public function editAction(int $protectionUid, string $combinedIdentifier): Resp /** * Updates an existing folder protection. * - * @param array $access Raw access-type fields keyed by TCA column + * @param array $protection Raw access-type fields keyed by TCA column */ - public function updateAction(int $protectionUid, string $combinedIdentifier, array $access = []): ResponseInterface + public function updateAction(int $protectionUid, string $combinedIdentifier, array $protection = []): ResponseInterface { - $this->protectionService->update($protectionUid, $access); + $this->protectionService->update($protectionUid, $protection); $this->addFlashMessage(LocalizationUtility::translate( 'tx_fpfileprotector_domain_model_protection.flashmessages.updated', diff --git a/Resources/Private/Partials/Access/Fe/Edit.html b/Resources/Private/Partials/Access/Fe/Edit.html index 5ac2f62..a56fea9 100644 --- a/Resources/Private/Partials/Access/Fe/Edit.html +++ b/Resources/Private/Partials/Access/Fe/Edit.html @@ -3,7 +3,7 @@

@@ -13,7 +13,7 @@

-
@@ -34,7 +34,7 @@

-
From 65e86ecbd87f34ac3dba1d639d92b737ff6dee13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannik=20B=C3=B6rgener?= Date: Wed, 15 Jul 2026 20:40:41 +0200 Subject: [PATCH 21/25] change labels and backend templates --- Documentation/AccessTypes/Fe/Index.rst | 4 +- Documentation/TroubleShooting/Index.rst | 2 +- Resources/Private/Language/de.locallang.xlf | 22 +++-- Resources/Private/Language/locallang.xlf | 16 +++- .../Private/Partials/Access/Be/Edit.html | 5 +- .../Private/Partials/Access/Be/Show.html | 15 +--- .../Private/Partials/Access/Fe/Edit.html | 7 +- .../Private/Partials/Access/Fe/Show.html | 82 ++++++++++--------- .../Partials/Protection/FormFields.html | 11 ++- .../Partials/Protection/Properties.html | 15 ++-- 10 files changed, 106 insertions(+), 73 deletions(-) diff --git a/Documentation/AccessTypes/Fe/Index.rst b/Documentation/AccessTypes/Fe/Index.rst index c3ba73d..8b54191 100644 --- a/Documentation/AccessTypes/Fe/Index.rst +++ b/Documentation/AccessTypes/Fe/Index.rst @@ -15,7 +15,7 @@ How it works The access type evaluates the protection record in the following order: -#. If the *Must be logged in to the frontend* option is **disabled** for the +#. If the *Enable access for frontend users* option is **disabled** for the folder, the frontend access type never grants access. #. If no frontend user is currently logged in, access is denied. @@ -36,7 +36,7 @@ Configuration The relevant fields are configured directly on the access rule in the **File Protection** backend module: -.. confval:: Must be logged in to the frontend +.. confval:: Enable access for frontend users :name: access-type-fe-fe-login Master switch for this access type. If disabled, none of the fields below diff --git a/Documentation/TroubleShooting/Index.rst b/Documentation/TroubleShooting/Index.rst index c40cd7d..8f549b6 100644 --- a/Documentation/TroubleShooting/Index.rst +++ b/Documentation/TroubleShooting/Index.rst @@ -26,7 +26,7 @@ files (:bash:`AllowOverride All`). User groups and/or users are being ignored ========================================== -Check whether the option **Must be logged in on the frontend** is enabled in +Check whether the option **Enable access for frontend users** is enabled in the access rule. Without this checkbox, group and user restrictions have no effect. diff --git a/Resources/Private/Language/de.locallang.xlf b/Resources/Private/Language/de.locallang.xlf index 8153aba..10be221 100644 --- a/Resources/Private/Language/de.locallang.xlf +++ b/Resources/Private/Language/de.locallang.xlf @@ -47,8 +47,8 @@ Ordner - Must be logged in to the frontend - Muss im Frontend eingeloggt sein + Enable access for frontend users + Zugriff für Frontend User gewähren User Groups @@ -58,9 +58,21 @@ Users Benutzer:innen - - Must be logged in to the backend - Muss im Backend eingeloggt sein + + + + Access granted via frontend login + Zugriff via Frontend Login + + + + + Access granted via backend login + Zugriff via Backend Login + + + The access is granted on base of access rules given for the backend user and the groups that the user belongs to. + Der Zugriff wird auf Basis der Berechtigungen des Backend-Users und der Berechtigungen ihrer:seiner Gruppen gewährt. diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index 5e16160..0602f8c 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -37,7 +37,7 @@ Folder - Must be logged in to the frontend + Enable access for frontend users User Groups @@ -45,8 +45,18 @@ Users - - Must be logged in to the backend + + + + Access granted via frontend login + + + + + Access granted via backend login + + + The access is granted on base of access rules given for the backend user and the groups that the user belongs to. diff --git a/Resources/Private/Partials/Access/Be/Edit.html b/Resources/Private/Partials/Access/Be/Edit.html index 3396fdf..b54ab94 100644 --- a/Resources/Private/Partials/Access/Be/Edit.html +++ b/Resources/Private/Partials/Access/Be/Edit.html @@ -1,3 +1,4 @@

- -

\ No newline at end of file + : +

+ diff --git a/Resources/Private/Partials/Access/Be/Show.html b/Resources/Private/Partials/Access/Be/Show.html index da91ab9..b8f34c5 100644 --- a/Resources/Private/Partials/Access/Be/Show.html +++ b/Resources/Private/Partials/Access/Be/Show.html @@ -1,11 +1,4 @@ -
-
-
- - - - -
-
+

+ : +

+ diff --git a/Resources/Private/Partials/Access/Fe/Edit.html b/Resources/Private/Partials/Access/Fe/Edit.html index a56fea9..c3c56e3 100644 --- a/Resources/Private/Partials/Access/Fe/Edit.html +++ b/Resources/Private/Partials/Access/Fe/Edit.html @@ -1,6 +1,7 @@

- + :

+