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

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((int) $rootPage->id);
  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.         $this->tagResponse($tags);
  78.         return $response;
  79.     }
  80.     private function callLegacyHook(PageModel $rootPage, array $pages): array
  81.     {
  82.         $systemAdapter $this->getContaoAdapter(System::class);
  83.         // HOOK: take additional pages
  84.         if (isset($GLOBALS['TL_HOOKS']['getSearchablePages']) && \is_array($GLOBALS['TL_HOOKS']['getSearchablePages'])) {
  85.             trigger_deprecation('contao/core-bundle''4.11''Using the "getSearchablePages" hook is deprecated. Use the "contao.sitemap" event instead.');
  86.             foreach ($GLOBALS['TL_HOOKS']['getSearchablePages'] as $callback) {
  87.                 $pages $systemAdapter->importStatic($callback[0])->{$callback[1]}($pages$rootPage->idtrue$rootPage->language);
  88.             }
  89.         }
  90.         return $pages;
  91.     }
  92.     private function getPageAndArticleUrls(int $parentPageId): array
  93.     {
  94.         $pageModelAdapter $this->getContaoAdapter(PageModel::class);
  95.         // Since the publication status of a page is not inherited by its child
  96.         // pages, we have to use findByPid() instead of findPublishedByPid() and
  97.         // filter out unpublished pages in the foreach loop (see #2217)
  98.         $pageModels $pageModelAdapter->findByPid($parentPageId, ['order' => 'sorting']);
  99.         if (null === $pageModels) {
  100.             return [];
  101.         }
  102.         $articleModelAdapter $this->getContaoAdapter(ArticleModel::class);
  103.         $result = [];
  104.         // Recursively walk through all subpages
  105.         foreach ($pageModels as $pageModel) {
  106.             // Load details in order to inherit permission settings (see #5556)
  107.             $pageModel->loadDetails();
  108.             if ($pageModel->protected && !$this->isGranted(ContaoCorePermissions::MEMBER_IN_GROUPS$pageModel->groups)) {
  109.                 continue;
  110.             }
  111.             $isPublished $pageModel->published && (!$pageModel->start || $pageModel->start <= time()) && (!$pageModel->stop || $pageModel->stop time());
  112.             if (
  113.                 $isPublished
  114.                 && !$pageModel->requireItem
  115.                 && 'noindex,nofollow' !== $pageModel->robots
  116.                 && $this->pageRegistry->supportsContentComposition($pageModel)
  117.                 && $this->pageRegistry->isRoutable($pageModel)
  118.                 && 'html' === $this->pageRegistry->getRoute($pageModel)->getDefault('_format')
  119.             ) {
  120.                 try {
  121.                     $urls = [$pageModel->getAbsoluteUrl()];
  122.                     // Get articles with teaser
  123.                     if (null !== ($articleModels $articleModelAdapter->findPublishedWithTeaserByPid($pageModel->id, ['ignoreFePreview' => true]))) {
  124.                         foreach ($articleModels as $articleModel) {
  125.                             $urls[] = $pageModel->getAbsoluteUrl('/articles/'.($articleModel->alias ?: $articleModel->id));
  126.                         }
  127.                     }
  128.                     $result[] = $urls;
  129.                 } catch (ExceptionInterface $exception) {
  130.                     // Skip URL for this page but generate child pages
  131.                 }
  132.             }
  133.             $result[] = $this->getPageAndArticleUrls((int) $pageModel->id);
  134.         }
  135.         return array_merge(...$result);
  136.     }
  137. }