From 4da434349ed90a603452eba6287f7efcae118205 Mon Sep 17 00:00:00 2001 From: Mario Lubenka Date: Wed, 29 Mar 2023 14:08:31 +0200 Subject: [PATCH] [BUGFIX:BP:11.5] 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. Ports: #3562 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 f87462448d..227ed7159d 100644 --- a/Classes/Domain/Search/Uri/SearchUriBuilder.php +++ b/Classes/Domain/Search/Uri/SearchUriBuilder.php @@ -26,6 +26,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; @@ -368,11 +369,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(function ($value) { diff --git a/Tests/Unit/Domain/Search/Uri/SearchUriBuilderTest.php b/Tests/Unit/Domain/Search/Uri/SearchUriBuilderTest.php index 617d4d36fc..aebde98ddc 100644 --- a/Tests/Unit/Domain/Search/Uri/SearchUriBuilderTest.php +++ b/Tests/Unit/Domain/Search/Uri/SearchUriBuilderTest.php @@ -22,6 +22,7 @@ use ApacheSolrForTypo3\Solr\Routing\RoutingService; use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration; use ApacheSolrForTypo3\Solr\Tests\Unit\UnitTest; +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; @@ -480,4 +481,70 @@ public function siteConfigurationModifyUriKeepUnmappedFilterTest() $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); + } }