vendor/contao/core-bundle/src/Controller/SitemapController.php line 32

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4.  * This file is part of Contao.
  5.  *
  6.  * (c) Leo Feyer
  7.  *
  8.  * @license LGPL-3.0-or-later
  9.  */
  10. namespace Contao\CoreBundle\Controller;
  11. use Contao\ArticleModel;
  12. use Contao\CoreBundle\Event\ContaoCoreEvents;
  13. use Contao\CoreBundle\Event\SitemapEvent;
  14. use Contao\CoreBundle\Routing\Page\PageRegistry;
  15. use Contao\CoreBundle\Security\ContaoCorePermissions;
  16. use Contao\PageModel;
  17. use Contao\System;
  18. use Symfony\Component\HttpFoundation\Request;
  19. use Symfony\Component\HttpFoundation\Response;
  20. use Symfony\Component\Routing\Annotation\Route;
  21. use Symfony\Component\Routing\Exception\ExceptionInterface;
  22. /**
  23.  * @Route(defaults={"_scope" = "frontend"})
  24.  *
  25.  * @internal
  26.  */
  27. class SitemapController extends AbstractController
  28. {
  29.     private PageRegistry $pageRegistry;
  30.     public function __construct(PageRegistry $pageRegistry)
  31.     {
  32.         $this->pageRegistry $pageRegistry;
  33.     }
  34.     /**
  35.      * @Route("/sitemap.xml")
  36.      */
  37.     public function __invoke(Request $request): Response
  38.     {
  39.         $this->initializeContaoFramework();
  40.         $pageModel $this->getContaoAdapter(PageModel::class);
  41.         $rootPages $pageModel->findPublishedRootPages(['dns' => $request->getHost()]);
  42.         if (null === $rootPages) {
  43.             // We did not find root pages by matching host name, let's fetch those that do not have any domain configured
  44.             $rootPages $pageModel->findPublishedRootPages(['dns' => '']);
  45.             if (null === $rootPages) {
  46.                 return new Response(''Response::HTTP_NOT_FOUND);
  47.             }
  48.         }
  49.         $urls = [];
  50.         $rootPageIds = [];
  51.         $tags = ['contao.sitemap'];
  52.         foreach ($rootPages as $rootPage) {
  53.             $pages $this->getPageAndArticleUrls($rootPage);
  54.             $urls[] = $this->callLegacyHook($rootPage$pages);
  55.             $rootPageIds[] = $rootPage->id;
  56.             $tags[] = 'contao.sitemap.'.$rootPage->id;
  57.         }
  58.         $urls array_unique(array_merge(...$urls));
  59.         $sitemap = new \DOMDocument('1.0''UTF-8');
  60.         $sitemap->formatOutput true;
  61.         $urlSet $sitemap->createElementNS('https://www.sitemaps.org/schemas/sitemap/0.9''urlset');
  62.         foreach ($urls as $url) {
  63.             $loc $sitemap->createElement('loc');
  64.             $loc->appendChild($sitemap->createTextNode($url));
  65.             $urlEl $sitemap->createElement('url');
  66.             $urlEl->appendChild($loc);
  67.             $urlSet->appendChild($urlEl);
  68.         }
  69.         $sitemap->appendChild($urlSet);
  70.         $this->container
  71.             ->get('event_dispatcher')
  72.             ->dispatch(new SitemapEvent($sitemap$request$rootPageIds), ContaoCoreEvents::SITEMAP)
  73.         ;
  74.         // Cache the response for a month in the shared cache and tag it for invalidation purposes
  75.         $response = new Response((string) $sitemap->saveXML(), 200, ['Content-Type' => 'application/xml; charset=UTF-8']);
  76.         $response->setSharedMaxAge(2592000); // will be unset by the MakeResponsePrivateListener if a user is logged in
  77.         // Make sure an authorized request does not retrieve the sitemap from the HTTP cache (see #6832)
  78.         $response->setVary('Cookie');
  79.         $this->tagResponse($tags);
  80.         return $response;
  81.     }
  82.     private function callLegacyHook(PageModel $rootPage, array $pages): array
  83.     {
  84.         $systemAdapter $this->getContaoAdapter(System::class);
  85.         // HOOK: take additional pages
  86.         if (isset($GLOBALS['TL_HOOKS']['getSearchablePages']) && \is_array($GLOBALS['TL_HOOKS']['getSearchablePages'])) {
  87.             trigger_deprecation('contao/core-bundle''4.11''Using the "getSearchablePages" hook is deprecated. Use the "contao.sitemap" event instead.');
  88.             foreach ($GLOBALS['TL_HOOKS']['getSearchablePages'] as $callback) {
  89.                 $pages $systemAdapter->importStatic($callback[0])->{$callback[1]}($pages$rootPage->idtrue$rootPage->language);
  90.             }
  91.         }
  92.         return $pages;
  93.     }
  94.     private function getPageAndArticleUrls(PageModel $parentPageModel): array
  95.     {
  96.         if ('root' === $parentPageModel->type && $parentPageModel->maintenanceMode) {
  97.             return [];
  98.         }
  99.         $pageModelAdapter $this->getContaoAdapter(PageModel::class);
  100.         // Since the publication status of a page is not inherited by its child
  101.         // pages, we have to use findByPid() instead of findPublishedByPid() and
  102.         // filter out unpublished pages in the foreach loop (see #2217)
  103.         $pageModels $pageModelAdapter->findByPid($parentPageModel->id, ['order' => 'sorting']);
  104.         if (null === $pageModels) {
  105.             return [];
  106.         }
  107.         $articleModelAdapter $this->getContaoAdapter(ArticleModel::class);
  108.         $result = [];
  109.         // Recursively walk through all subpages
  110.         foreach ($pageModels as $pageModel) {
  111.             // Load details in order to inherit permission settings (see #5556)
  112.             $pageModel->loadDetails();
  113.             if ($pageModel->protected && !$this->isGranted(ContaoCorePermissions::MEMBER_IN_GROUPS$pageModel->groups)) {
  114.                 continue;
  115.             }
  116.             $isPublished $pageModel->published && (!$pageModel->start || $pageModel->start <= time()) && (!$pageModel->stop || $pageModel->stop time());
  117.             if (
  118.                 $isPublished
  119.                 && !$pageModel->requireItem
  120.                 && 'noindex,nofollow' !== $pageModel->robots
  121.                 && $this->pageRegistry->supportsContentComposition($pageModel)
  122.                 && $this->pageRegistry->isRoutable($pageModel)
  123.                 && 'html' === $this->pageRegistry->getRoute($pageModel)->getDefault('_format')
  124.             ) {
  125.                 try {
  126.                     $urls = [$pageModel->getAbsoluteUrl()];
  127.                     // Get articles with teaser
  128.                     if (null !== ($articleModels $articleModelAdapter->findPublishedWithTeaserByPid($pageModel->id, ['ignoreFePreview' => true]))) {
  129.                         foreach ($articleModels as $articleModel) {
  130.                             $urls[] = $pageModel->getAbsoluteUrl('/articles/'.($articleModel->alias ?: $articleModel->id));
  131.                         }
  132.                     }
  133.                     $result[] = $urls;
  134.                 } catch (ExceptionInterface $exception) {
  135.                     // Skip URL for this page but generate child pages
  136.                 }
  137.             }
  138.             $result[] = $this->getPageAndArticleUrls($pageModel);
  139.         }
  140.         return array_merge(...$result);
  141.     }
  142. }