Bug #90648
Updated by Christian Eßl over 4 years ago
We have a multidomain website that is structured like:
* Website A
** mounted-subpage (Original)
* Website B
** mounted-subpage (Mount of the page in Website A)
* Website C (hidden)
** mounted-subpage (Mount of the page in Website A)
Basically every website has the subpage "mounted-subpage" with identical slugs. Website A has the original page, all the other websites use a mountpoint set to this page.
Now when I open the subpage in the frontend from Website A like:
https://website-a.com/mounted-subpage
we seem to get an infinite recursion here. (Nginx just logs an "Allowed memory size exhausted" here).
This happens in SiteFinder::getSiteByPageId() in the last line:
<pre><code class="php">
/**
* Traverses the rootline of a page up until a Site was found.
*
* @param int $pageId
* @param array $rootLine
* @param string|null $mountPointParameter
* @return Site
* @throws SiteNotFoundException
*/
public function getSiteByPageId(int $pageId, array $rootLine = null, string $mountPointParameter = null): Site
{
if ($pageId === 0) {
// page uid 0 has no root line. We don't need to ask the root line resolver to know that.
$rootLine = [];
}
if (!is_array($rootLine)) {
try {
$rootLine = GeneralUtility::makeInstance(RootlineUtility::class, $pageId, $mountPointParameter)->get();
} catch (PageNotFoundException $e) {
// Usually when a page was hidden or disconnected
// This could be improved by handing in a Context object and decide whether hidden pages
// Should be linkeable too
$rootLine = [];
}
}
foreach ($rootLine as $pageInRootLine) {
if (isset($this->mappingRootPageIdToIdentifier[(int)$pageInRootLine['uid']])) {
return $this->sites[$this->mappingRootPageIdToIdentifier[(int)$pageInRootLine['uid']]];
}
}
throw new SiteNotFoundException('No site found in root line of page ' . $pageId, 1521716622);
}
</code></pre>
The SiteNotFoundException is thrown, when we get the error about the memory exhaustion (what exactly happens recursion-wise, I can't say yet).
* Now look at the example page tree above. When I debugged the problem, I found out, that one of our websites root page was hidden. (Website C is not in use). But according to the stacktrace, the PageRouter inside matchRequest() looks up possible candidates in getPagesFromDatabaseForCandidates().
* In there happens the new mount point magic. The function now returns our possible candidates for the slug "mounted-subpage", including the one of the hidden Website C.
* Later, SiteFinder::getSiteByPageId() tries to find the site for this page, but fails doing so (Expected, the website is hidden) and throws a SiteNotFoundException.
* SiteMatcher::matchByPageId catches the exception and falls back to "$this->pseudoSiteFinder->getSiteByPageId()). It successfully returns a PseudoSite for our hidden Website C;
* Later in PageRouter::getPagesFromDatabaseForCandidates(), if $mountPageInformation is set (yes in this case), the recursion happens when $this->findPageCandidatesOfMountPoint() is called. Why it ends up in an infinite recursion I am not really sure yet. From what I see it constantly juggles between the mountpoints of Website B and Website C. (Which both point to the original page in Website A)