diff --git a/Classes/ContentObject/JsonContentContentObject.php b/Classes/ContentObject/JsonContentContentObject.php index 3304abeb..1427e82e 100755 --- a/Classes/ContentObject/JsonContentContentObject.php +++ b/Classes/ContentObject/JsonContentContentObject.php @@ -150,7 +150,7 @@ public function render($conf = []): string /** * @param array $contentElements * @param array $conf - * @return array> + * @return array> */ protected function groupContentElementsByColPos(array $contentElements, array $conf): array { @@ -167,11 +167,12 @@ protected function groupContentElementsByColPos(array $contentElements, array $c $element = $this->headlessUserInt->wrap($element); } - $element = json_decode($element, true); + $decoded = json_decode($element, true); - if ($element === []) { + if (!is_array($decoded) || $decoded === []) { continue; } + $element = $decoded; $colPos = $this->getColPosFromElement($groupingEnabled, $element); @@ -184,11 +185,14 @@ protected function groupContentElementsByColPos(array $contentElements, array $c if ($groupingEnabled && $this->isSortByBackendLayoutEnabled($conf)) { $backendLayoutView = GeneralUtility::makeInstance(BackendLayoutView::class); - $backendLayout = $backendLayoutView->getSelectedBackendLayout($this->request->getAttribute('routing')->getPageId()); + $routing = $this->request->getAttribute('routing'); + $backendLayout = $routing !== null + ? $backendLayoutView->getSelectedBackendLayout($routing->getPageId()) + : null; $sorted = []; foreach ($backendLayout['__colPosList'] ?? [] as $value) { - $sorted['colPos' . $value] = $data['colPos' . $value]; + $sorted['colPos' . $value] = $data['colPos' . $value] ?? []; } $data = $sorted; diff --git a/Classes/ContentObject/JsonContentObject.php b/Classes/ContentObject/JsonContentObject.php index ff08a25d..3691c55f 100755 --- a/Classes/ContentObject/JsonContentObject.php +++ b/Classes/ContentObject/JsonContentObject.php @@ -46,16 +46,16 @@ public function __construct( */ public function render($conf = []): string { + if (!is_array($conf)) { + $conf = []; + } + if (!empty($conf['if.']) && !$this->cObj->checkIf($conf['if.'])) { return ''; } $data = []; - if (!is_array($conf)) { - $conf = []; - } - $this->conf = $conf; if (isset($conf['fields.'])) { diff --git a/Classes/DataProcessing/DataProcessingTrait.php b/Classes/DataProcessing/DataProcessingTrait.php index 844cbd4a..5800d76f 100644 --- a/Classes/DataProcessing/DataProcessingTrait.php +++ b/Classes/DataProcessing/DataProcessingTrait.php @@ -42,7 +42,7 @@ protected function removeDataIfnotAppendInConfiguration(array $processorConfigur protected function isMenuProcessor(): bool { - return __CLASS__ === MenuProcessor::class; + return $this instanceof MenuProcessor; } /** diff --git a/Classes/DataProcessing/ExtractPropertyProcessor.php b/Classes/DataProcessing/ExtractPropertyProcessor.php index 6ce1bd28..9730d3c3 100644 --- a/Classes/DataProcessing/ExtractPropertyProcessor.php +++ b/Classes/DataProcessing/ExtractPropertyProcessor.php @@ -64,13 +64,17 @@ public function process( $key = GeneralUtility::trimExplode('.', $processorConfiguration['key'], true); - // Extract (nested) property - do { - $processedData = $processedData[array_shift($key)] ?? null; - } while (count($key)); + $value = $processedData; + foreach ($key as $segment) { + if (!is_array($value)) { + $value = null; + break; + } + $value = $value[$segment] ?? null; + } return [ - $targetFieldName => $processedData, + $targetFieldName => $value, ]; } } diff --git a/Classes/DataProcessing/FilesProcessor.php b/Classes/DataProcessing/FilesProcessor.php index 33d3e6a3..2be5da9b 100644 --- a/Classes/DataProcessing/FilesProcessor.php +++ b/Classes/DataProcessing/FilesProcessor.php @@ -93,12 +93,27 @@ public function process( $this->defaults['as'] ); + if (!$this->hasFileSources($processorConfiguration)) { + $processedData[$targetFieldName] = []; + return $this->removeDataIfnotAppendInConfiguration($processorConfiguration, $processedData); + } + $this->fileObjects = $this->fetchData(); $processedData[$targetFieldName] = $this->processFiles($properties); return $this->removeDataIfnotAppendInConfiguration($processorConfiguration, $processedData); } + private function hasFileSources(array $processorConfiguration): bool + { + foreach (['references', 'references.', 'files', 'files.', 'collections', 'collections.', 'folders', 'folders.'] as $key) { + if (!empty($processorConfiguration[$key])) { + return true; + } + } + return false; + } + /** * @return array */ diff --git a/Classes/DataProcessing/RootSiteProcessing/DomainSchema.php b/Classes/DataProcessing/RootSiteProcessing/DomainSchema.php index 8a8d5336..117f34b6 100644 --- a/Classes/DataProcessing/RootSiteProcessing/DomainSchema.php +++ b/Classes/DataProcessing/RootSiteProcessing/DomainSchema.php @@ -43,9 +43,10 @@ public function process(SiteProviderInterface $provider, array $options = []): a $result = []; foreach ($provider->getSites() as $site) { + $urlUtility = $this->urlUtility->withSite($site); $protocol = $site->getBase()->getScheme() . '://'; $baseUrl = $protocol . $site->getBase()->getHost(); - $url = $this->urlUtility->getFrontendUrlForPage($baseUrl, $site->getRootPageId()); + $url = $urlUtility->getFrontendUrlForPage($baseUrl, $site->getRootPageId()); $locales = []; @@ -57,7 +58,7 @@ public function process(SiteProviderInterface $provider, array $options = []): a 'name' => str_replace($protocol, '', $url), 'baseURL' => $url, 'api' => [ - 'baseURL' => $this->urlUtility->getProxyUrl(), + 'baseURL' => $urlUtility->getProxyUrl(), ], 'i18n' => [ 'locales' => $locales, diff --git a/Classes/DataProcessing/RootSiteProcessing/SiteProvider.php b/Classes/DataProcessing/RootSiteProcessing/SiteProvider.php index 2a907587..e5a25b33 100644 --- a/Classes/DataProcessing/RootSiteProcessing/SiteProvider.php +++ b/Classes/DataProcessing/RootSiteProcessing/SiteProvider.php @@ -81,10 +81,8 @@ public function prepare(array $config, int $siteUid): self $sorting = GeneralUtility::makeInstance($customSorting, $sites, $pages, $sortingField); $sites = $sorting->sort(); } else { - usort($sites, static function (Site $siteA, Site $siteB) use ($pages, $sortingField) { - // phpcs:ignore Generic.Files.LineLength - return (int)$pages[$siteA->getRootPageId()][$sortingField] >= (int)$pages[$siteB->getRootPageId()][$sortingField] ? 1 : -1; - }); + usort($sites, static fn(Site $siteA, Site $siteB): int => + (int)$pages[$siteA->getRootPageId()][$sortingField] <=> (int)$pages[$siteB->getRootPageId()][$sortingField]); } $this->sites = $sites; diff --git a/Classes/Event/Listener/AfterLinkIsGeneratedListener.php b/Classes/Event/Listener/AfterLinkIsGeneratedListener.php index a1a09424..1a0739b3 100644 --- a/Classes/Event/Listener/AfterLinkIsGeneratedListener.php +++ b/Classes/Event/Listener/AfterLinkIsGeneratedListener.php @@ -182,16 +182,16 @@ private function resolveTypolinkParameterString(string $mixedLinkParameter, arra )) { // Disallow insecure scheme's like javascript: or data: throw new UnableToLinkException( - 'Insuecure scheme for linking detected with "' . $mixedLinkParameter . "'", + 'Insecure scheme for linking detected with "' . $mixedLinkParameter . "'", 1641986533 ); } // additional parameters that need to be set if (($linkParameterParts['additionalParams'] ?? '') !== '') { - $forceParams = $linkParameterParts['additionalParams']; - // params value - $linkConfiguration['additionalParams'] = ($linkConfiguration['additionalParams'] ?? '') . $forceParams[0] === '&' ? $forceParams : '&' . $forceParams; + $forceParams = (string)$linkParameterParts['additionalParams']; + $prefix = $forceParams[0] === '&' ? '' : '&'; + $linkConfiguration['additionalParams'] = ($linkConfiguration['additionalParams'] ?? '') . $prefix . $forceParams; } return [ diff --git a/Classes/Event/Listener/RedirectUrlAdditionalParamsListener.php b/Classes/Event/Listener/RedirectUrlAdditionalParamsListener.php index 9027ddfc..00d35b26 100644 --- a/Classes/Event/Listener/RedirectUrlAdditionalParamsListener.php +++ b/Classes/Event/Listener/RedirectUrlAdditionalParamsListener.php @@ -27,7 +27,8 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; use function parse_str; -use function strpos; +use function parse_url; +use function str_contains; class RedirectUrlAdditionalParamsListener implements LoggerAwareInterface { @@ -61,14 +62,16 @@ public function __invoke(RedirectUrlEvent $event): void ); $redirectTarget = $linkParameterParts['url'] ?? ''; $linkDetails = $this->resolveLinkDetailsFromLinkTarget($redirectTarget); + $additionalParams = (string)($linkParameterParts['additionalParams'] ?? ''); - if (($linkDetails['type'] === LinkService::TYPE_PAGE) && - strpos($linkParameterParts['additionalParams'], '[action]=') > 0 && - strpos($linkParameterParts['additionalParams'], '[controller]=') > 0) { + if (($linkDetails['type'] ?? null) === LinkService::TYPE_PAGE && + str_contains($additionalParams, '[action]=') && + str_contains($additionalParams, '[controller]=')) { try { $site = $request->getAttribute('site'); - parse_str($linkParameterParts['url'], $typolinkData); - parse_str($linkParameterParts['additionalParams'], $params); + $urlQuery = parse_url((string)($linkParameterParts['url'] ?? ''), PHP_URL_QUERY) ?? ''; + parse_str($urlQuery, $typolinkData); + parse_str($additionalParams, $params); $languageId = isset($typolinkData['L']) ? (int)$typolinkData['L'] : 0; diff --git a/Classes/Resource/Rendering/AudioTagRenderer.php b/Classes/Resource/Rendering/AudioTagRenderer.php index 295e5bc0..8d450270 100644 --- a/Classes/Resource/Rendering/AudioTagRenderer.php +++ b/Classes/Resource/Rendering/AudioTagRenderer.php @@ -22,6 +22,8 @@ */ class AudioTagRenderer extends \TYPO3\CMS\Core\Resource\Rendering\AudioTagRenderer { + private ?FileUtility $fileUtility = null; + public function getPriority(): int { return 2; @@ -39,7 +41,8 @@ public function getPriority(): int public function render(FileInterface $file, $width, $height, array $options = []): string { if (($options['returnUrl'] ?? false) === true) { - return htmlspecialchars(GeneralUtility::makeInstance(FileUtility::class)->getAbsoluteUrl($file->getPublicUrl()), ENT_QUOTES | ENT_HTML5); + $fileUtility = $this->fileUtility ??= GeneralUtility::makeInstance(FileUtility::class); + return htmlspecialchars($fileUtility->getAbsoluteUrl($file->getPublicUrl()), ENT_QUOTES | ENT_HTML5); } return parent::render(...func_get_args()); } diff --git a/Classes/Resource/Rendering/VideoTagRenderer.php b/Classes/Resource/Rendering/VideoTagRenderer.php index 7aea3e23..b93df99f 100644 --- a/Classes/Resource/Rendering/VideoTagRenderer.php +++ b/Classes/Resource/Rendering/VideoTagRenderer.php @@ -22,6 +22,8 @@ */ class VideoTagRenderer extends \TYPO3\CMS\Core\Resource\Rendering\VideoTagRenderer { + private ?FileUtility $fileUtility = null; + public function getPriority(): int { return 2; @@ -40,7 +42,8 @@ public function getPriority(): int public function render(FileInterface $file, $width, $height, array $options = []): string { if (($options['returnUrl'] ?? false) === true) { - return htmlspecialchars(GeneralUtility::makeInstance(FileUtility::class)->getAbsoluteUrl($file->getPublicUrl()), ENT_QUOTES | ENT_HTML5); + $fileUtility = $this->fileUtility ??= GeneralUtility::makeInstance(FileUtility::class); + return htmlspecialchars($fileUtility->getAbsoluteUrl($file->getPublicUrl()), ENT_QUOTES | ENT_HTML5); } return parent::render(...func_get_args()); } diff --git a/Classes/Seo/MetaTag/AbstractMetaTagManager.php b/Classes/Seo/MetaTag/AbstractMetaTagManager.php index 0a540545..0601eab1 100644 --- a/Classes/Seo/MetaTag/AbstractMetaTagManager.php +++ b/Classes/Seo/MetaTag/AbstractMetaTagManager.php @@ -23,9 +23,16 @@ */ abstract class AbstractMetaTagManager extends \TYPO3\CMS\Core\MetaTag\AbstractMetaTagManager { + private ?HeadlessModeInterface $headlessMode = null; + + private function getHeadlessMode(): HeadlessModeInterface + { + return $this->headlessMode ??= GeneralUtility::makeInstance(HeadlessModeInterface::class); + } + public function renderAllProperties(): string { - if (GeneralUtility::makeInstance(HeadlessModeInterface::class)->withRequest($GLOBALS['TYPO3_REQUEST'])->isEnabled()) { + if ($this->getHeadlessMode()->withRequest($GLOBALS['TYPO3_REQUEST'])->isEnabled()) { return $this->renderAllHeadlessProperties(); } @@ -34,7 +41,7 @@ public function renderAllProperties(): string public function renderProperty(string $property): string { - if (GeneralUtility::makeInstance(HeadlessModeInterface::class)->withRequest($GLOBALS['TYPO3_REQUEST'])->isEnabled()) { + if ($this->getHeadlessMode()->withRequest($GLOBALS['TYPO3_REQUEST'])->isEnabled()) { return $this->renderHeadlessProperty($property); } diff --git a/Classes/Utility/FileUtility.php b/Classes/Utility/FileUtility.php index e405a49b..ad69759f 100644 --- a/Classes/Utility/FileUtility.php +++ b/Classes/Utility/FileUtility.php @@ -215,7 +215,6 @@ public function process(FileInterface $fileReference, ProcessingConfiguration $p private function onDemandProperties(ProcessingConfiguration $processingConfiguration, array $properties): array { - $processed = []; $props = []; foreach ($processingConfiguration->includeProperties as $prop) { @@ -246,7 +245,7 @@ private function onDemandProperties(ProcessingConfiguration $processingConfigura } } - return array_merge($processed, $props); + return $props; } private function filterProperties(ProcessingConfiguration $processingConfiguration, array $properties): array @@ -329,7 +328,7 @@ public function getAbsoluteUrl(string $fileUrl): string $siteUrl = $this->getNormalizedParams()->getSiteUrl(); $sitePath = str_replace($this->getNormalizedParams()->getRequestHost(), '', $siteUrl); $absoluteUrl = trim($fileUrl); - if (stripos($absoluteUrl, 'http') !== 0) { + if (stripos($absoluteUrl, 'http') !== 0 && !str_starts_with($absoluteUrl, '//')) { $fileUrl = preg_replace('#^' . preg_quote($sitePath, '#') . '#', '', $fileUrl); $fileUrl = $siteUrl . $fileUrl; } @@ -377,9 +376,12 @@ protected function getNormalizedParams(): NormalizedParams return $this->contentObjectRenderer->getRequest()->getAttribute('normalizedParams'); } + /** @var array */ + private array $cropVariantCache = []; + protected function createCropVariant(string $cropString): CropVariantCollection { - return CropVariantCollection::create($cropString); + return $this->cropVariantCache[$cropString] ??= CropVariantCollection::create($cropString); } /** @@ -396,8 +398,8 @@ private function processAutogenerate( array $processedFile, ProcessingConfiguration $processingConfiguration ): array { - $originalWidth = $originalReference->getProperty('width'); - $originalHeight = $originalReference->getProperty('height'); + $originalWidth = $this->getCroppedDimensionalProperty($originalReference, 'width', $processingConfiguration->cropVariant); + $originalHeight = $this->getCroppedDimensionalProperty($originalReference, 'height', $processingConfiguration->cropVariant); $targetWidth = (int)($processingConfiguration->width !== '' ? $processingConfiguration->width : $fileReference->getProperty('width')); $targetHeight = (int)($processingConfiguration->height !== '' ? $processingConfiguration->height : $fileReference->getProperty('height')); @@ -436,14 +438,14 @@ public function processCropVariants( */ $crop = $originalFileReference->getProperty('crop'); - if ($crop !== null) { + if ($crop !== null && $crop !== '') { if (!$processingConfiguration->legacyReturn) { unset($processedFile['crop'], $processedFile['properties']['crop']); } - $cropVariants = json_decode($originalFileReference->getProperty('crop'), true); + $cropVariants = json_decode($crop, true); - $collection = CropVariantCollection::create($originalFileReference->getProperty('crop')); + $collection = $this->createCropVariant($crop); if (is_array($cropVariants) && count($cropVariants) > 1 && str_starts_with( $originalFileReference->getMimeType(), @@ -454,10 +456,10 @@ public function processCropVariants( continue; } - $processingConfiguration = $processingConfiguration->withOptions(['cropVariant' => $cropVariantName]); - $file = $this->process($originalFileReference, $processingConfiguration); + $variantConfiguration = $processingConfiguration->withOptions(['cropVariant' => $cropVariantName]); + $file = $this->process($originalFileReference, $variantConfiguration); $processedFile['cropVariants'][$cropVariantName] = $this->cropVariant( - $processingConfiguration, + $variantConfiguration, $file, $cropVariants[$cropVariantName] ); diff --git a/Classes/Utility/HeadlessUserInt.php b/Classes/Utility/HeadlessUserInt.php index 4a25b1d3..40c4e0e1 100644 --- a/Classes/Utility/HeadlessUserInt.php +++ b/Classes/Utility/HeadlessUserInt.php @@ -71,7 +71,7 @@ public function unwrap(string $content): string protected function buildPattern(string $primary, string $nullable): string { - return self::$regexPatterns[$primary] ??= sprintf( + return self::$regexPatterns[$primary . '|' . $nullable] ??= sprintf( self::REGEX, preg_quote($nullable, '/'), preg_quote($primary, '/') @@ -98,7 +98,8 @@ protected function replace(array $m, bool $isNullable): string return $rawContent; } - return json_encode($rawContent); + $encoded = json_encode($rawContent); + return $encoded !== false ? $encoded : 'null'; } $jsonEncoded = json_encode($rawContent); diff --git a/Classes/Utility/UrlUtility.php b/Classes/Utility/UrlUtility.php index 66fafa25..5a9af18d 100644 --- a/Classes/Utility/UrlUtility.php +++ b/Classes/Utility/UrlUtility.php @@ -47,6 +47,7 @@ class UrlUtility implements LoggerAwareInterface, HeadlessFrontendUrlInterface private array $variants = []; private HeadlessModeInterface $headlessMode; private array $frontendDomains = []; + private array $backendDomains = []; public function __construct( ?Features $features = null, @@ -91,7 +92,10 @@ public function getFrontendUrlWithSite($url, SiteInterface $site, string $return $targetUri = new Uri($this->sanitizeBaseUrl($url)); - if (!$this->headlessMode->isEnabled() || $this->alreadyFrontendLink($url) || $targetUri->getHost() === '') { + if (!$this->headlessMode->isEnabled() || + $targetUri->getHost() === '' || + $this->isExternalUrl($targetUri->getHost()) || + $this->alreadyFrontendLink($targetUri->getHost())) { return $url; } @@ -257,6 +261,10 @@ private function handleLanguageConfiguration(SiteLanguage $language, HeadlessFro $frontendFileApi = trim($langConf['frontendFileApi'] ?? ''); $overrides = []; + if ($language->getBase()->getHost() !== '') { + $this->backendDomains[] = $language->getBase()->getHost(); + } + if ($frontendBase !== '') { $overrides['frontendBase'] = $frontendBase; $this->frontendDomains[] = (new Uri($this->sanitizeBaseUrl($frontendBase)))->getHost(); @@ -283,7 +291,17 @@ private function handleSiteConfiguration(Site $site, UrlUtility $object): self { $object->conf = $site->getConfiguration(); $object->variants = $object->conf['baseVariants'] ?? []; + $this->frontendDomains = []; + $this->backendDomains = []; + $this->backendDomains[] = $site->getBase()->getHost(); + + foreach ($object->variants as $variant) { + $variantBase = trim($variant['base'] ?? ''); + if ($variantBase !== '') { + $object->backendDomains[] = (new Uri($this->sanitizeBaseUrl($variantBase)))->getHost(); + } + } $base = trim($object->conf['frontendBase'] ?? ''); if ($base !== '') { @@ -333,6 +351,9 @@ private function overrideByLanguageIfNecessary(SiteInterface $site, string $back continue; } + if ($language->getBase()->getHost() !== '') { + $this->backendDomains[] = $language->getBase()->getHost(); + } $this->frontendDomains[] = (new Uri($this->sanitizeBaseUrl($base)))->getHost(); if ($language->getBase()->getHost() === $backendUri->getHost()) { @@ -342,6 +363,7 @@ private function overrideByLanguageIfNecessary(SiteInterface $site, string $back } } + $this->backendDomains = array_unique($this->backendDomains); $this->frontendDomains = array_unique($this->frontendDomains); return $matchedLanguage; @@ -349,8 +371,11 @@ private function overrideByLanguageIfNecessary(SiteInterface $site, string $back protected function alreadyFrontendLink(string $url): bool { - $targetUri = new Uri($this->sanitizeBaseUrl($url)); + return in_array($url, $this->frontendDomains, true); + } - return in_array($targetUri->getHost(), $this->frontendDomains, true); + protected function isExternalUrl(string $url): bool + { + return !in_array($url, array_merge($this->backendDomains, $this->frontendDomains), true); } } diff --git a/Tests/Unit/Middleware/CookieDomainPerSiteTest.php b/Tests/Unit/Middleware/CookieDomainPerSiteTest.php index 9f18f489..f67b65b1 100644 --- a/Tests/Unit/Middleware/CookieDomainPerSiteTest.php +++ b/Tests/Unit/Middleware/CookieDomainPerSiteTest.php @@ -25,6 +25,7 @@ use TYPO3\CMS\Core\Http\JsonResponse; use TYPO3\CMS\Core\Http\NormalizedParams; use TYPO3\CMS\Core\Http\ServerRequest; +use TYPO3\CMS\Core\Http\Uri; use TYPO3\CMS\Core\Site\Entity\Site; use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -77,6 +78,8 @@ public function emptyCookieDomain() ], ]); + $site->getBase()->willReturn(new Uri('https://www.typo3.org')); + $resolver = $this->prophesize(Resolver::class); $resolver->evaluate(Argument::containingString('Development'))->willReturn(true); @@ -135,6 +138,8 @@ public function cookieDomainIsSet() ], ]); + $site->getBase()->willReturn(new Uri('https://www.typo3.org')); + $resolver = $this->prophesize(Resolver::class); $resolver->evaluate(Argument::containingString('Development'))->willReturn(true); diff --git a/Tests/Unit/Utility/UrlUtilityTest.php b/Tests/Unit/Utility/UrlUtilityTest.php index d570b563..9800cabd 100644 --- a/Tests/Unit/Utility/UrlUtilityTest.php +++ b/Tests/Unit/Utility/UrlUtilityTest.php @@ -188,6 +188,8 @@ public function testFrontendUrlsWithBaseProductionAndLocalOverride(): void ], ]); + $site->getBase()->willReturn(new Uri('https://api.typo3.org')); + $siteFinder = $this->createMock(SiteFinder::class); // local override @@ -243,6 +245,7 @@ public function testOptimizedUrlsForFrontendApp(): void ], ]); $site->getLanguages()->willReturn([]); + $site->getBase()->willReturn(new Uri('https://test-backend-api.tld')); $resolver = $this->prophesize(Resolver::class); $resolver->evaluate(Argument::containingString('Development'))->willReturn(true); @@ -278,6 +281,12 @@ public function testOptimizedUrlsForFrontendApp(): void $urlUtility->getFrontendUrlWithSite('/test-page', $site->reveal()) ); + // do not touch external links + self::assertSame( + 'https://typo3.org/headless', + $urlUtility->getFrontendUrlWithSite('https://typo3.org/headless', $site->reveal()) + ); + // test reversed = "Testing" condition $resolver = $this->prophesize(Resolver::class); $resolver->evaluate(Argument::containingString('Development'))->willReturn(false); @@ -346,6 +355,8 @@ public function testLanguageResolver(): void ], ]); + $site->getBase()->willReturn(new Uri('https://www.typo3.org')); + $resolver = $this->prophesize(Resolver::class); $resolver->evaluate(Argument::containingString('Development'))->willReturn(true); $resolver->evaluate(Argument::containingString('Testing'))->willReturn(false);