OtPageRouter.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. <?php
  2. namespace Opentalent\OtOptimizer\Routing;
  3. use Doctrine\DBAL\Connection;
  4. use TYPO3\CMS\Core\Context\Context;
  5. use TYPO3\CMS\Core\Database\ConnectionPool;
  6. use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
  7. use TYPO3\CMS\Core\Database\Query\Restriction\FrontendWorkspaceRestriction;
  8. use TYPO3\CMS\Core\Exception\SiteNotFoundException;
  9. use TYPO3\CMS\Core\Routing\PageRouter;
  10. use TYPO3\CMS\Core\Routing\SiteMatcher;
  11. use TYPO3\CMS\Core\Site\Entity\Site;
  12. use TYPO3\CMS\Core\Utility\GeneralUtility;
  13. use TYPO3\CMS\Frontend\Page\PageRepository;
  14. /**
  15. * Override the default core PageRouter of typo3 to exclude the pages
  16. * which do not belong to the current website
  17. *
  18. * @see https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/Xclasses/Index.html
  19. * @package Opentalent\OtOptimizer\Routing
  20. */
  21. class OtPageRouter extends PageRouter
  22. {
  23. /**
  24. * special: patch 2020-09-22 by Opentalent, for performances reason
  25. * Returns an array containing all the subpages'uids, including the given $pageId
  26. * >> made for typo3 v9.5
  27. *
  28. * @param int $pageId
  29. * @return array|int[]
  30. */
  31. private function getAllSubpagesUidFor(int $pageId) {
  32. $subpages = [$pageId];
  33. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
  34. $queryBuilder
  35. ->getRestrictions()
  36. ->removeAll()
  37. ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
  38. $queryBuilder
  39. ->select('uid')
  40. ->from('pages')
  41. ->where(
  42. $queryBuilder->expr()->eq('pid', (int)$pageId)
  43. );
  44. $statement = $queryBuilder->execute();
  45. while ($row = $statement->fetch()) {
  46. $subpages = array_merge($subpages, $this->getAllSubpagesUidFor($row['uid']));
  47. }
  48. return $subpages;
  49. }
  50. /**
  51. * -- Override the original method from the typo3 PageRouter --
  52. * Check for records in the database which matches one of the slug candidates.
  53. *
  54. * @param array $slugCandidates
  55. * @param int $languageId
  56. * @param array $excludeUids when called recursively this is the mountpoint parameter of the original prefix
  57. * @return array
  58. */
  59. protected function getPagesFromDatabaseForCandidates(array $slugCandidates, int $languageId, array $excludeUids = []): array
  60. {
  61. $context = GeneralUtility::makeInstance(Context::class);
  62. $searchLiveRecordsOnly = $context->getPropertyFromAspect('workspace', 'isLive');
  63. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  64. ->getQueryBuilderForTable('pages');
  65. $queryBuilder
  66. ->getRestrictions()
  67. ->removeAll()
  68. ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
  69. ->add(GeneralUtility::makeInstance(FrontendWorkspaceRestriction::class, null, null, $searchLiveRecordsOnly));
  70. $statement = $queryBuilder
  71. ->select('uid', 'l10n_parent', 'pid', 'slug', 'mount_pid', 'mount_pid_ol', 't3ver_state', 'doktype', 't3ver_wsid', 't3ver_oid')
  72. ->from('pages')
  73. ->where(
  74. $queryBuilder->expr()->eq(
  75. 'sys_language_uid',
  76. $queryBuilder->createNamedParameter($languageId, \PDO::PARAM_INT)
  77. ),
  78. $queryBuilder->expr()->in(
  79. 'slug',
  80. $queryBuilder->createNamedParameter(
  81. $slugCandidates,
  82. Connection::PARAM_STR_ARRAY
  83. )
  84. )
  85. )
  86. // Exact match will be first, that's important
  87. ->orderBy('slug', 'desc')
  88. // Sort pages that are not MountPoint pages before mount points
  89. ->addOrderBy('mount_pid_ol', 'asc')
  90. ->addOrderBy('mount_pid', 'asc')
  91. ->execute();
  92. $isRecursiveCall = !empty($excludeUids);
  93. $pages = [];
  94. $siteMatcher = GeneralUtility::makeInstance(SiteMatcher::class);
  95. $pageRepository = GeneralUtility::makeInstance(PageRepository::class, $context);
  96. // <----
  97. // special: patch 2020-09-22 by Opentalent, for performances reason
  98. // >> made for typo3 v10.4.8
  99. $siteRootPageUid = $this->site->getRootPageId();
  100. $sitePages = $this->getAllSubpagesUidFor($siteRootPageUid);
  101. // ---->
  102. while ($row = $statement->fetch()) {
  103. $mountPageInformation = null;
  104. $pageRepository->fixVersioningPid('pages', $row);
  105. $pageIdInDefaultLanguage = (int)($languageId > 0 ? $row['l10n_parent'] : $row['uid']);
  106. // When this page was added before via recursion, this page should be skipped
  107. if (in_array($pageIdInDefaultLanguage, $excludeUids, true)) {
  108. continue;
  109. }
  110. // <----
  111. // special: patch 2020-09-22 by Opentalent, for performances reason
  112. // >> made for typo3 v10.4.8
  113. if (!in_array($pageIdInDefaultLanguage, $sitePages)) {
  114. continue;
  115. }
  116. // ---->
  117. try {
  118. $isOnSameSite = $siteMatcher->matchByPageId($pageIdInDefaultLanguage)->getRootPageId() === $this->site->getRootPageId();
  119. } catch (SiteNotFoundException $e) {
  120. // Page is not in a site, so it's not considered
  121. $isOnSameSite = false;
  122. }
  123. // If a MountPoint is found on the current site, and it hasn't been added yet by some other iteration
  124. // (see below "findPageCandidatesOfMountPoint"), then let's resolve the MountPoint information now
  125. if (!$isOnSameSite && $isRecursiveCall) {
  126. // Not in the same site, and called recursive, should be skipped
  127. continue;
  128. }
  129. $mountPageInformation = $pageRepository->getMountPointInfo($pageIdInDefaultLanguage, $row);
  130. // Mount Point Pages which are not on the same site (when not called on the first level) should be skipped
  131. // As they just clutter up the queries.
  132. if (!$isOnSameSite && !$isRecursiveCall && $mountPageInformation) {
  133. continue;
  134. }
  135. $mountedPage = null;
  136. if ($mountPageInformation) {
  137. // Add the MPvar to the row, so it can be used later-on in the PageRouter / PageArguments
  138. $row['MPvar'] = $mountPageInformation['MPvar'];
  139. $mountedPage = $pageRepository->getPage_noCheck($mountPageInformation['mount_pid_rec']['uid']);
  140. // Ensure to fetch the slug in the translated page
  141. $mountedPage = $pageRepository->getPageOverlay($mountedPage, $languageId);
  142. // Mount wasn't connected properly, so it is skipped
  143. if (!$mountedPage) {
  144. continue;
  145. }
  146. // If the page is a MountPoint which should be overlaid with the contents of the mounted page,
  147. // it must never be accessible directly, but only in the MountPoint context. Therefore we change
  148. // the current ID and slug.
  149. // This needs to happen before the regular case, as the $pageToAdd contains the MPvar information
  150. if (PageRepository::DOKTYPE_MOUNTPOINT === (int)$row['doktype'] && $row['mount_pid_ol']) {
  151. // If the mounted page was already added from above, this should not be added again (to include
  152. // the mount point parameter).
  153. if (in_array((int)$mountedPage['uid'], $excludeUids, true)) {
  154. continue;
  155. }
  156. $pageToAdd = $mountedPage;
  157. // Make sure target page "/about-us" is replaced by "/global-site/about-us" so router works
  158. $pageToAdd['MPvar'] = $mountPageInformation['MPvar'];
  159. $pageToAdd['slug'] = $row['slug'];
  160. $pages[] = $pageToAdd;
  161. $excludeUids[] = (int)$pageToAdd['uid'];
  162. $excludeUids[] = $pageIdInDefaultLanguage;
  163. }
  164. }
  165. // This is the regular "non-MountPoint page" case (must happen after the if condition so MountPoint
  166. // pages that have been replaced by the Mounted Page will not be added again.
  167. if ($isOnSameSite && !in_array($pageIdInDefaultLanguage, $excludeUids, true)) {
  168. $pages[] = $row;
  169. $excludeUids[] = $pageIdInDefaultLanguage;
  170. }
  171. // Add possible sub-pages prepended with the MountPoint page slug
  172. if ($mountPageInformation) {
  173. $siteOfMountedPage = $siteMatcher->matchByPageId((int)$mountedPage['uid']);
  174. if ($siteOfMountedPage instanceof Site) {
  175. $morePageCandidates = $this->findPageCandidatesOfMountPoint(
  176. $row,
  177. $mountedPage,
  178. $siteOfMountedPage,
  179. $languageId,
  180. $slugCandidates,
  181. $context
  182. );
  183. foreach ($morePageCandidates as $candidate) {
  184. // When called previously this MountPoint page should be skipped
  185. if (in_array((int)$candidate['uid'], $excludeUids, true)) {
  186. continue;
  187. }
  188. $pages[] = $candidate;
  189. }
  190. }
  191. }
  192. }
  193. return $pages;
  194. }
  195. }