From 8a37207fb5a95944643391612c1709a0baccf303 Mon Sep 17 00:00:00 2001 From: Mario Lubenka Date: Wed, 29 Mar 2023 14:08:31 +0200 Subject: [PATCH] [BUGFIX] Retry Uri Building after exception When there are route enhancers defined for a page containing the Solr search plugin, e.g. to realize pretty URLs for the search term, filters or pagination, there are issues with the URI caching that utilizes placeholders. Route Enhancers should specify the expected format using the requirements setting of the route enhancer parameters. This will then lead to an exception, which is caught and will then proceed the URI building with the original arguments instead of the placeholder arguments. Resolves: #2984 --- .../Domain/Search/Uri/SearchUriBuilder.php | 26 +++++-- .../Search/Uri/SearchUriBuilderTest.php | 67 +++++++++++++++++++ 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/Classes/Domain/Search/Uri/SearchUriBuilder.php b/Classes/Domain/Search/Uri/SearchUriBuilder.php index e42771888d..b5e91cf1c4 100644 --- a/Classes/Domain/Search/Uri/SearchUriBuilder.php +++ b/Classes/Domain/Search/Uri/SearchUriBuilder.php @@ -27,6 +27,7 @@ use ApacheSolrForTypo3\Solr\System\Url\UrlHelper; use ApacheSolrForTypo3\Solr\Utility\ParameterSortingUtility; use Psr\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Routing\Exception\InvalidParameterException; use TYPO3\CMS\Core\Http\Uri; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; @@ -277,11 +278,26 @@ protected function buildLinkWithInMemoryCache(?int $pageUid, array $arguments): } else { self::$missCount++; $this->uriBuilder->reset()->setTargetPageUid($pageUid); - $uriCacheTemplate = $this->uriBuilder->setArguments($structure)->build(); - - /** @var UrlHelper $urlHelper */ - $urlHelper = GeneralUtility::makeInstance(UrlHelper::class, $uriCacheTemplate); - self::$preCompiledLinks[$hash] = (string)$urlHelper; + try { + $uriCacheTemplate = $this->uriBuilder->setArguments($structure)->build(); + + /** @var UrlHelper $urlHelper */ + $urlHelper = GeneralUtility::makeInstance(UrlHelper::class, $uriCacheTemplate); + self::$preCompiledLinks[$hash] = (string)$urlHelper; + } catch (InvalidParameterException $exception) { + // the placeholders may result in an exception when route enhancers with requirements are active + // In this case, try to build the URL with original arguments + $hash = md5($pageUid . json_encode($arguments)); + if (isset(self::$preCompiledLinks[$hash])) { + self::$hitCount++; + $uriCacheTemplate = self::$preCompiledLinks[$hash]; + } else { + $uriCacheTemplate = $this->uriBuilder->setArguments($arguments)->build(); + /** @var UrlHelper $urlHelper */ + $urlHelper = GeneralUtility::makeInstance(UrlHelper::class, $uriCacheTemplate); + self::$preCompiledLinks[$hash] = (string)$urlHelper; + } + } } $keys = array_map(static function ($value) { diff --git a/Tests/Unit/Domain/Search/Uri/SearchUriBuilderTest.php b/Tests/Unit/Domain/Search/Uri/SearchUriBuilderTest.php index 223b44bcd7..69d2197162 100644 --- a/Tests/Unit/Domain/Search/Uri/SearchUriBuilderTest.php +++ b/Tests/Unit/Domain/Search/Uri/SearchUriBuilderTest.php @@ -23,6 +23,7 @@ use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration; use ApacheSolrForTypo3\Solr\Tests\Unit\SetUpUnitTestCase; use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Routing\Exception\InvalidParameterException; use Symfony\Component\Yaml\Yaml; use TYPO3\CMS\Core\EventDispatcher\EventDispatcher; use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; @@ -469,4 +470,70 @@ public function siteConfigurationModifyUriKeepUnmappedFilterTest(): void $uri = $this->searchUrlBuilder->getResultPageUri($previousRequest, 0); self::assertEquals($linkBuilderResult, $uri); } + + /** + * @test + */ + public function uriErrorsResultInNonMappedProcessing(): void + { + $configuration = Yaml::parse($this->getFixtureContentByName('siteConfiguration.yaml')); + $routingServiceMock = $this->createMock(RoutingService::class); + $routingServiceMock->expects(self::any()) + ->method('fetchEnhancerByPageUid') + ->willReturn($configuration['routeEnhancers']['example']); + $queryParameters = [ + 'tx_solr' => [ + 'filter' => [ + 'type:pages', + 'color:green', + 'color:red', + 'color:yellow', + 'taste:matcha', + 'taste:sour', + 'product:candy', + 'product:sweets', + 'quantity:20', + ], + ], + ]; + $subsitutedQueryParameters = [ + 'tx_solr' => [ + 'filter' => [ + '###tx_solr:filter:0:type###', + '###tx_solr:filter:1:color###', + '###tx_solr:filter:2:color###', + '###tx_solr:filter:3:color###', + '###tx_solr:filter:4:taste###', + '###tx_solr:filter:5:taste###', + '###tx_solr:filter:6:product###', + '###tx_solr:filter:7:product###', + '###tx_solr:filter:8:quantity###', + ], + ], + ]; + $linkBuilderResult = '/index.php?id=42&color=' . urlencode('green,red,yellow') . + '&taste=' . urlencode('matcha,sour') . + '&product=' . urlencode('candy,sweets') . + '&' . urlencode('tx_solr[filter][0]') . '=' . urlencode('quantity:20'); + $configurationMock = $this->createMock(TypoScriptConfiguration::class); + $configurationMock->expects(self::any())->method('getSearchPluginNamespace')->willReturn('tx_solr'); + $configurationMock->expects(self::once())->method('getSearchTargetPage')->willReturn(42); + + $previousRequest = new SearchRequest($queryParameters, 42, 0, $configurationMock); + $this->extBaseUriBuilderMock->expects(self::any())->method('setArguments') + ->withConsecutive([$subsitutedQueryParameters], [$queryParameters]) + ->willReturn($this->extBaseUriBuilderMock); + $this->extBaseUriBuilderMock->expects(self::once())->method('reset')->with()->willReturn($this->extBaseUriBuilderMock); + $buildCounter = 0; + $this->extBaseUriBuilderMock->expects(self::exactly(2))->method('build') + ->willReturnCallback(function () use ($linkBuilderResult, &$buildCounter) { + if (++$buildCounter === 1) { + throw new InvalidParameterException('First call fails, should reprocess with regular arguments'); + } + return $linkBuilderResult; + }); + $this->searchUrlBuilder->injectRoutingService($routingServiceMock); + $uri = $this->searchUrlBuilder->getResultPageUri($previousRequest, 0); + self::assertEquals($linkBuilderResult, $uri); + } }