From c15b6e5bc18e5bff6646dbf41f0b1f2c93202791 Mon Sep 17 00:00:00 2001 From: Alexander Grein Date: Wed, 3 Jul 2024 11:29:19 +0200 Subject: [PATCH 1/4] Add typo3 13 compatibility --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index dd74d19..2f345ed 100644 --- a/composer.json +++ b/composer.json @@ -10,8 +10,8 @@ }, "license": "GPL-2.0-or-later", "require": { - "typo3/cms-core": "^9.0 || ^10.0 || ^11.0 || ^12.0", - "typo3/cms-frontend": "^9.0 || ^10.0 || ^11.0 || ^12.0" + "typo3/cms-core": "^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0", + "typo3/cms-frontend": "^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0" }, "require-dev": { "nimut/testing-framework": "^5.0", From e9c2a149199696e3d8eb3f4c679a741d5d67b0d5 Mon Sep 17 00:00:00 2001 From: Alexander Grein Date: Wed, 3 Jul 2024 11:29:43 +0200 Subject: [PATCH 2/4] Add typo3 13 compatibility --- ext_emconf.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext_emconf.php b/ext_emconf.php index d2bc035..be5a5be 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -11,7 +11,7 @@ 'version' => '8.2.2', 'constraints' => [ 'depends' => [ - 'typo3' => '9.5.0-12.4.99', + 'typo3' => '9.5.0-13.4.99', ], 'conflicts' => [], 'suggests' => [], From e9ec9d0ba7d3fdeb8ad86d8bfdc7ae199583c5c0 Mon Sep 17 00:00:00 2001 From: Alexander Grein Date: Wed, 3 Jul 2024 12:26:38 +0200 Subject: [PATCH 3/4] Add getPageTsConfig to replace removed core method; Add getTypoScriptFrontendController to link modifier since method became protected in TYPO3 13 --- Classes/Middleware/JumpUrlHandler.php | 36 ++++++++++++++++++++--- Classes/TypoLink/LinkModifier.php | 42 ++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/Classes/Middleware/JumpUrlHandler.php b/Classes/Middleware/JumpUrlHandler.php index 0232c79..0ef1e46 100644 --- a/Classes/Middleware/JumpUrlHandler.php +++ b/Classes/Middleware/JumpUrlHandler.php @@ -19,12 +19,16 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; +use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Http\RedirectResponse; use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Resource\Security\FileNameValidator; +use TYPO3\CMS\Core\Site\Entity\NullSite; use TYPO3\CMS\Core\TimeTracker\TimeTracker; +use TYPO3\CMS\Core\TypoScript\PageTsConfig; +use TYPO3\CMS\Core\TypoScript\PageTsConfigFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; @@ -69,7 +73,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface } // Regular jump URL $this->validateIfJumpUrlRedirectIsAllowed($jumpUrl, $juHash); - return $this->redirectToJumpUrl($jumpUrl); + return $this->redirectToJumpUrl($jumpUrl, $request); } return $handler->handle($request); } @@ -81,9 +85,9 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface * @throws \Exception * @return ResponseInterface */ - protected function redirectToJumpUrl(string $jumpUrl): ResponseInterface + protected function redirectToJumpUrl(string $jumpUrl, ServerRequestInterface $request): ResponseInterface { - $pageTSconfig = $this->getTypoScriptFrontendController()->getPagesTSconfig(); + $pageTSconfig = $this->getPageTsConfig($request); $pageTSconfig = array_key_exists('TSFE.', $pageTSconfig) && is_array($pageTSconfig['TSFE.'] ?? false) ? $pageTSconfig['TSFE.'] : []; // Allow sections in links @@ -94,6 +98,30 @@ protected function redirectToJumpUrl(string $jumpUrl): ResponseInterface return new RedirectResponse($jumpUrl, $statusCode); } + /** + * @param ServerRequestInterface $request + * @return array + * @throws \JsonException + * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException + */ + protected function getPageTsConfig(ServerRequestInterface $request): array + { + $pageInformation = $request->getAttribute('frontend.page.information'); + $id = $pageInformation->getId(); + $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime'); + $pageTsConfig = $runtimeCache->get('pageTsConfig-' . $id); + if ($pageTsConfig instanceof PageTsConfig) { + return $pageTsConfig->getPageTsConfigArray(); + } + $fullRootLine = $pageInformation->getRootLine(); + ksort($fullRootLine); + $site = $request->getAttribute('site') ?? new NullSite(); + $pageTsConfigFactory = GeneralUtility::makeInstance(PageTsConfigFactory::class); + $pageTsConfig = $pageTsConfigFactory->create($fullRootLine, $site); + $runtimeCache->set('pageTsConfig-' . $id, $pageTsConfig); + return $pageTsConfig->getPageTsConfigArray(); + } + /** * If the submitted hash is correct and the user has access to the * related content element the contents of the submitted file will @@ -157,7 +185,7 @@ protected function forwardJumpUrlSecureFileData(string $jumpUrl, string $locatio protected function isLocationDataValid(string $locationData): bool { $isValidLocationData = false; - list($pageUid, $table, $recordUid) = explode(':', $locationData); + [$pageUid, $table, $recordUid] = explode(':', $locationData); $pageRepository = $this->getTypoScriptFrontendController()->sys_page; if (empty($table) || $pageRepository->checkRecord($table, $recordUid, true)) { // This check means that a record is checked only if the locationData has a value for a diff --git a/Classes/TypoLink/LinkModifier.php b/Classes/TypoLink/LinkModifier.php index a1ebc12..52b845e 100644 --- a/Classes/TypoLink/LinkModifier.php +++ b/Classes/TypoLink/LinkModifier.php @@ -17,6 +17,10 @@ use FoT3\Jumpurl\JumpUrlUtility; use TYPO3\CMS\Core\LinkHandling\LinkService; +use TYPO3\CMS\Core\Site\Entity\NullSite; +use TYPO3\CMS\Core\Site\Entity\Site; +use TYPO3\CMS\Core\Site\Entity\SiteLanguage; +use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\StringUtility; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; @@ -36,12 +40,12 @@ class LinkModifier /** * @var TypoScriptFrontendController */ - protected $frontendController; + protected $typoScriptFrontendController; public function __invoke(AfterLinkIsGeneratedEvent $event): void { $this->contentObjectRenderer = $event->getContentObjectRenderer(); - $this->frontendController = $this->contentObjectRenderer->getTypoScriptFrontendController(); + $this->typoScriptFrontendController = $this->contentObjectRenderer->getTypoScriptFrontendController(); if ($this->isEnabled($event)) { $url = $event->getLinkResult()->getUrl(); @@ -192,13 +196,35 @@ protected function getTypoLinkParameter(array $configuration) protected function getTypoScriptFrontendController(): TypoScriptFrontendController { - $tsfe = $this->frontendController ?? $GLOBALS['TSFE'] ?? null; - if ($tsfe instanceof TypoScriptFrontendController) { - return $tsfe; + if ($this->typoScriptFrontendController instanceof TypoScriptFrontendController) { + return $this->typoScriptFrontendController; } - // workaround for getting a TSFE object in Backend - $linkBuilder = GeneralUtility::makeInstance(PageLinkBuilder::class, new ContentObjectRenderer()); - return $linkBuilder->getTypoScriptFrontendController(); + + // This usually happens when typolink is created by the TYPO3 Backend, where no TSFE object + // is there. This functionality is currently completely internal, as these links cannot be + // created properly from the Backend. + // However, this is added to avoid any exceptions when trying to create a link. + // Detecting the "first" site usually comes from the fact that TSFE needs to be instantiated + // during tests + $request = $this->contentObjectRenderer->getRequest(); + $site = $request->getAttribute('site'); + if (!$site instanceof Site) { + $sites = GeneralUtility::makeInstance(SiteFinder::class)->getAllSites(); + $site = reset($sites); + if (!$site instanceof Site) { + $site = new NullSite(); + } + } + $language = $request->getAttribute('language'); + if (!$language instanceof SiteLanguage) { + $language = $site->getDefaultLanguage(); + } + $request = $request->withAttribute('language', $language); + + $this->typoScriptFrontendController = GeneralUtility::makeInstance(TypoScriptFrontendController::class); + $this->typoScriptFrontendController->initializePageRenderer($request); + $this->typoScriptFrontendController->initializeLanguageService($request); + return $this->typoScriptFrontendController; } protected function getContentObjectRenderer(): ContentObjectRenderer From 9060fb693ab7e4ca1fb3676619989665340a9539 Mon Sep 17 00:00:00 2001 From: Alexander Grein Date: Sun, 10 Nov 2024 22:18:28 +0100 Subject: [PATCH 4/4] Remove all code related to typo3 below version 12; set version to 9.0.0 --- .travis.yml | 10 +- Classes/JumpUrlProcessor.php | 222 -------------------------- Classes/Middleware/JumpUrlHandler.php | 12 +- Documentation/Installation/Index.rst | 2 + README.md | 5 +- Tests/Unit/JumpUrlProcessorMock.php | 35 ---- Tests/Unit/JumpUrlProcessorTest.php | 92 ----------- composer.json | 4 +- ext_emconf.php | 4 +- ext_localconf.php | 5 - 10 files changed, 16 insertions(+), 375 deletions(-) delete mode 100644 Classes/JumpUrlProcessor.php delete mode 100644 Tests/Unit/JumpUrlProcessorMock.php delete mode 100644 Tests/Unit/JumpUrlProcessorTest.php delete mode 100644 ext_localconf.php diff --git a/.travis.yml b/.travis.yml index 7307eae..58ab82f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,15 +37,15 @@ jobs: fast_finish: true include: - stage: test - php: 7.2 - env: TYPO3=^8.7 + php: 8.2 + env: TYPO3=^12.4 - stage: test - php: 7.2 - env: TYPO3=^9.5 + php: 8.2 + env: TYPO3=^13.4 - stage: publish to ter if: tag IS present - php: 7.2 + php: 8.2 before_install: skip install: skip before_script: skip diff --git a/Classes/JumpUrlProcessor.php b/Classes/JumpUrlProcessor.php deleted file mode 100644 index cda2f6a..0000000 --- a/Classes/JumpUrlProcessor.php +++ /dev/null @@ -1,222 +0,0 @@ -frontendController = $typoScriptFrontendController ?? $GLOBALS['TSFE'] ?? null; - $this->contentObjectRenderer = $contentObjectRenderer; - } - - /** - * Generates the JumpURL for the given parameters. - * - * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::processUrlModifiers() - * @param string $context The context in which the URL is generated (e.g. "typolink"). - * @param string $url The URL that should be processed. - * @param array $configuration The link configuration. - * @param ContentObjectRenderer $contentObjectRenderer The calling content object renderer. - * @param bool $keepProcessing If this is set to FALSE no further hooks will be processed after the current one. - * @return string - */ - public function process($context, $url, array $configuration, ContentObjectRenderer $contentObjectRenderer, &$keepProcessing) - { - if (!$this->isEnabled($context, $configuration)) { - return $url; - } - - $this->contentObjectRenderer = $contentObjectRenderer; - - // Strip the absRefPrefix from the URLs. - $urlPrefix = (string)$this->getTypoScriptFrontendController()->absRefPrefix; - if ($urlPrefix !== '' && StringUtility::beginsWith($url, $urlPrefix)) { - $url = substr($url, strlen($urlPrefix)); - } - - // Make sure the slashes in the file URL are not encoded. - if ($context === UrlProcessorInterface::CONTEXT_FILE) { - $url = str_replace('%2F', '/', rawurlencode(rawurldecode($url))); - } - - $url = $this->build($url, isset($configuration['jumpurl.']) ? $configuration['jumpurl.'] : []); - - // Now add the prefix again if it was not added by a typolink call already. - if ($urlPrefix !== '' && !StringUtility::beginsWith($url, $urlPrefix)) { - $url = $urlPrefix . $url; - } - - return $url; - } - - /** - * Returns TRUE if jumpurl was enabled in the global configuration - * or in the given configuration - * - * @param string $context separate check for the MAIL context needed - * @param array $configuration Optional jump URL configuration - * @return bool TRUE if enabled, FALSE if disabled - */ - protected function isEnabled($context, array $configuration = []) - { - if (!empty($configuration['jumpurl.']['forceDisable'] ?? false)) { - return false; - } - - $enabled = !empty($configuration['jumpurl'] ?? false); - - // if jumpurl is explicitly set to 0 we override the global configuration - if (!$enabled && ($this->getTypoScriptFrontendController()->config['config']['jumpurl_enable'] ?? false)) { - $enabled = !isset($configuration['jumpurl']) || $configuration['jumpurl']; - } - - // If we have a mailto link and jumpurl is not explicitly enabled - // but globally disabled for mailto links we disable it - if ( - empty($configuration['jumpurl']) && $context === UrlProcessorInterface::CONTEXT_MAIL - && ($this->getTypoScriptFrontendController()->config['config']['jumpurl_mailto_disable'] ?? false) - ) { - $enabled = false; - } - - return $enabled; - } - - /** - * Builds a jump URL for the given URL - * - * @param string $url The URL to which will be jumped - * @param array $configuration Optional TypoLink configuration - * @return string The generated URL - */ - protected function build($url, array $configuration) - { - $urlParameters = ['jumpurl' => $url]; - - // see if a secure File URL should be built - if (!empty($configuration['secure'])) { - $secureParameters = $this->getParametersForSecureFile( - $url, - isset($configuration['secure.']) ? $configuration['secure.'] : [] - ); - $urlParameters = array_merge($urlParameters, $secureParameters); - } else { - $urlParameters['juHash'] = JumpUrlUtility::calculateHash($url); - } - - $typoLinkConfiguration = [ - 'parameter' => $this->getTypoLinkParameter($configuration), - 'additionalParams' => GeneralUtility::implodeArrayForUrl('', $urlParameters), - // make sure jump URL is not called again - 'jumpurl.' => ['forceDisable' => '1'] - ]; - - return $this->getContentObjectRenderer()->typoLink_URL($typoLinkConfiguration); - } - - /** - * Returns a URL parameter array containing parameters for secure downloads by "jumpurl". - * Helper function for filelink() - * - * The array returned has the following structure: - * juSecure => is always 1, - * locationData => information about the record that created the jumpUrl, - * juHash => the hash that will be checked before the file is downloadable - * [mimeType => the mime type of the file] - * - * @param string $jumpUrl The URL to jump to, basically the filepath - * @param array $configuration TypoScript properties for the "jumpurl.secure" property of "filelink" - * @return array URL parameters required for jumpUrl secure - */ - protected function getParametersForSecureFile($jumpUrl, array $configuration) - { - $parameters = [ - 'juSecure' => 1, - 'locationData' => $this->getTypoScriptFrontendController()->id . ':' . $this->getContentObjectRenderer()->currentRecord - ]; - - $pathInfo = pathinfo($jumpUrl); - if (!empty($pathInfo['extension'])) { - $mimeTypes = GeneralUtility::trimExplode(',', $configuration['mimeTypes'], true); - foreach ($mimeTypes as $mimeType) { - [$fileExtension, $mimeType] = GeneralUtility::trimExplode('=', $mimeType, false, 2); - if (strtolower($pathInfo['extension']) === strtolower($fileExtension)) { - $parameters['mimeType'] = $mimeType; - break; - } - } - } - $parameters['juHash'] = JumpUrlUtility::calculateHashSecure($jumpUrl, $parameters['locationData'], $parameters['mimeType']); - return $parameters; - } - - /** - * Checks if an alternative link parameter was configured and if not - * a default parameter will be generated based on the current page - * ID and type. - * When linking to a file this method is needed - * - * @param array $configuration Data from the TypoLink jumpurl configuration - * @return string The parameter for the jump URL TypoLink - */ - protected function getTypoLinkParameter(array $configuration) - { - $linkParameter = $this->getContentObjectRenderer()->stdWrapValue('parameter', $configuration); - - if (empty($linkParameter)) { - $frontendController = $this->getTypoScriptFrontendController(); - $linkParameter = $frontendController->id . ',' . $frontendController->type; - } - - return $linkParameter; - } - - protected function getTypoScriptFrontendController(): TypoScriptFrontendController - { - $tsfe = $this->frontendController ?? $GLOBALS['TSFE'] ?? null; - if ($tsfe instanceof TypoScriptFrontendController) { - return $tsfe; - } - // workaround for getting a TSFE object in Backend - $linkBuilder = GeneralUtility::makeInstance(PageLinkBuilder::class, new ContentObjectRenderer()); - return $linkBuilder->getTypoScriptFrontendController(); - } - - protected function getContentObjectRenderer(): ContentObjectRenderer - { - return $this->contentObjectRenderer ?: $this->getTypoScriptFrontendController()->cObj; - } -} diff --git a/Classes/Middleware/JumpUrlHandler.php b/Classes/Middleware/JumpUrlHandler.php index 0ef1e46..f5c933c 100644 --- a/Classes/Middleware/JumpUrlHandler.php +++ b/Classes/Middleware/JumpUrlHandler.php @@ -154,15 +154,9 @@ protected function forwardJumpUrlSecureFileData(string $jumpUrl, string $locatio // Check if requested file accessable $fileAccessAllowed = false; - if ((new Typo3Version())->getMajorVersion() < 11) { - $fileAccessAllowed = GeneralUtility::isAllowedAbsPath($absoluteFileName) - && GeneralUtility::verifyFilenameAgainstDenyPattern($absoluteFileName) - && !GeneralUtility::isFirstPartOfStr($absoluteFileName, Environment::getLegacyConfigPath()); - } else { - $fileAccessAllowed = GeneralUtility::isAllowedAbsPath($absoluteFileName) - && GeneralUtility::makeInstance(FileNameValidator::class)->isValid($absoluteFileName) - && !str_starts_with($absoluteFileName, Environment::getLegacyConfigPath()); - } + $fileAccessAllowed = GeneralUtility::isAllowedAbsPath($absoluteFileName) + && GeneralUtility::makeInstance(FileNameValidator::class)->isValid($absoluteFileName) + && !str_starts_with($absoluteFileName, Environment::getLegacyConfigPath()); if (!$fileAccessAllowed) { throw new \Exception('The requested file was not allowed to be accessed through Jump URL. The path or file is not allowed.', 1294585194); } diff --git a/Documentation/Installation/Index.rst b/Documentation/Installation/Index.rst index 512fdcb..4f12d2f 100644 --- a/Documentation/Installation/Index.rst +++ b/Documentation/Installation/Index.rst @@ -16,5 +16,7 @@ in a TYPO3 CMS 7+ installation. EXT:jumpurl v7 is compatible with TYPO3 v7 / v8 / v9, whereas EXT:jumpurl v8 is compatible with TYPO3 v9+ as it is based on the PSR-15 middleware approach. +EXT:jumpurl v9 is compatible with TYPO3 v12+ and drops support for older +versions. .. _TER: https://extensions.typo3.org/extension/jumpurl diff --git a/README.md b/README.md index 4841ec3..d0fb083 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ [![Latest Stable Version](https://poser.pugx.org/friendsoftypo3/jumpurl/v/stable.svg)](https://extensions.typo3.org/extension/jumpurl/) -[![TYPO3 11](https://img.shields.io/badge/TYPO3-11-orange.svg?style=flat-square)](https://get.typo3.org/version/11) -[![TYPO3 10](https://img.shields.io/badge/TYPO3-10-orange.svg?style=flat-square)](https://get.typo3.org/version/10) -[![TYPO3 9](https://img.shields.io/badge/TYPO3-9-orange.svg?style=flat-square)](https://get.typo3.org/version/9) +[![TYPO3 13](https://img.shields.io/badge/TYPO3-13-orange.svg?style=flat-square)](https://get.typo3.org/version/13) +[![TYPO3 12](https://img.shields.io/badge/TYPO3-12-orange.svg?style=flat-square)](https://get.typo3.org/version/12) [![Total Downloads](https://poser.pugx.org/friendsoftypo3/jumpurl/d/total.svg)](https://packagist.org/packages/friendsoftypo3/jumpurl) [![Monthly Downloads](https://poser.pugx.org/friendsoftypo3/jumpurl/d/monthly)](https://packagist.org/packages/friendsoftypo3/jumpurl) diff --git a/Tests/Unit/JumpUrlProcessorMock.php b/Tests/Unit/JumpUrlProcessorMock.php deleted file mode 100644 index f352367..0000000 --- a/Tests/Unit/JumpUrlProcessorMock.php +++ /dev/null @@ -1,35 +0,0 @@ -tsfe = $this->getAccessibleMock( - TypoScriptFrontendController::class, - ['getPagesTSconfig'], - [], - '', - false - ); - - $this->contentObjectRenderer = new ContentObjectRenderer($this->tsfe); - $this->jumpUrlProcessor = new JumpUrlProcessorMock($this->tsfe, $this->contentObjectRenderer); - } - - /** - * @test - */ - public function getJumpUrlSecureParametersReturnsValidParameters() - { - $this->tsfe->id = 456; - $this->contentObjectRenderer->currentRecord = 'tt_content:123'; - - $jumpUrlSecureParameters = $this->jumpUrlProcessor->getParametersForSecureFile( - '/fileadmin/a/test/file.txt', - ['mimeTypes' => 'dummy=application/x-executable,txt=text/plain'] - ); - - $this->assertSame( - [ - 'juSecure' => 1, - 'locationData' => '456:tt_content:123', - 'mimeType' => 'text/plain', - 'juHash' => '1cccb7f01c8a3f58ee890377b5de9bdc05115a37', - ], - $jumpUrlSecureParameters - ); - } -} diff --git a/composer.json b/composer.json index 2f345ed..4010764 100644 --- a/composer.json +++ b/composer.json @@ -10,8 +10,8 @@ }, "license": "GPL-2.0-or-later", "require": { - "typo3/cms-core": "^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0", - "typo3/cms-frontend": "^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0" + "typo3/cms-core": "^12.4 || ^13.4", + "typo3/cms-frontend": "^12.4 || ^13.4" }, "require-dev": { "nimut/testing-framework": "^5.0", diff --git a/ext_emconf.php b/ext_emconf.php index be5a5be..d0377e3 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -8,10 +8,10 @@ 'state' => 'stable', 'clearCacheOnLoad' => 1, 'author_company' => '', - 'version' => '8.2.2', + 'version' => '9.0.0', 'constraints' => [ 'depends' => [ - 'typo3' => '9.5.0-13.4.99', + 'typo3' => '12.4.0-13.4.99', ], 'conflicts' => [], 'suggests' => [], diff --git a/ext_localconf.php b/ext_localconf.php deleted file mode 100644 index e7f6d68..0000000 --- a/ext_localconf.php +++ /dev/null @@ -1,5 +0,0 @@ -