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

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. /**
  22.  * @Route(defaults={"_scope" = "frontend"})
  23.  *
  24.  * @internal
  25.  */
  26. class SitemapController extends AbstractController
  27. {
  28.     private PageRegistry $pageRegistry;
  29.     public function __construct(PageRegistry $pageRegistry)
  30.     {
  31.         $this->pageRegistry $pageRegistry;
  32.     }
  33.     /**
  34.      * @Route("/sitemap.xml")
  35.      */
  36.     public function __invoke(Request $request): Response
  37.     {
  38.         $this->initializeContaoFramework();
  39.         $pageModel $this->getContaoAdapter(PageModel::class);
  40.         $rootPages $pageModel->findPublishedRootPages(['dns' => $request->getHost()]);
  41.         if (null === $rootPages) {
  42.             // We did not find root pages by matching host name, let's fetch those that do not have any domain configured
  43.             $rootPages $pageModel->findPublishedRootPages(['dns' => '']);
  44.             if (null === $rootPages) {
  45.                 return new Response(''Response::HTTP_NOT_FOUND);
  46.             }
  47.         }
  48.         $urls = [];
  49.         $rootPageIds = [];
  50.         $tags = ['contao.sitemap'];
  51.         foreach ($rootPages as $rootPage) {
  52.             $pages $this->getPageAndArticleUrls((int) $rootPage->id);
  53.             $urls[] = $this->callLegacyHook($rootPage$pages);
  54.             $rootPageIds[] = $rootPage->id;
  55.             $tags[] = 'contao.sitemap.'.$rootPage->id;
  56.         }
  57.         $urls array_unique(array_merge(...$urls));
  58.         $sitemap = new \DOMDocument('1.0''UTF-8');
  59.         $sitemap->formatOutput true;
  60.         $urlSet $sitemap->createElementNS('https://www.sitemaps.org/schemas/sitemap/0.9''urlset');
  61.         foreach ($urls as $url) {
  62.             $loc $sitemap->createElement('loc'$url);
  63.             $urlEl $sitemap->createElement('url');
  64.             $urlEl->appendChild($loc);
  65.             $urlSet->appendChild($urlEl);
  66.         }
  67.         $sitemap->appendChild($urlSet);
  68.         $this->container
  69.             ->get('event_dispatcher')
  70.             ->dispatch(new SitemapEvent($sitemap$request$rootPageIds), ContaoCoreEvents::SITEMAP)
  71.         ;
  72.         // Cache the response for a month in the shared cache and tag it for invalidation purposes
  73.         $response = new Response((string) $sitemap->saveXML(), 200, ['Content-Type' => 'application/xml; charset=UTF-8']);
  74.         $response->setSharedMaxAge(2592000); // will be unset by the MakeResponsePrivateListener if a user is logged in
  75.         $this->tagResponse($tags);
  76.         return $response;
  77.     }
  78.     private function callLegacyHook(PageModel $rootPage, array $pages): array
  79.     {
  80.         $systemAdapter $this->getContaoAdapter(System::class);
  81.         // HOOK: take additional pages
  82.         if (isset($GLOBALS['TL_HOOKS']['getSearchablePages']) && \is_array($GLOBALS['TL_HOOKS']['getSearchablePages'])) {
  83.             trigger_deprecation('contao/core-bundle''4.11''Using the "getSearchablePages" hook is deprecated. Use the "contao.sitemap" event instead.');
  84.             foreach ($GLOBALS['TL_HOOKS']['getSearchablePages'] as $callback) {
  85.                 $pages $systemAdapter->importStatic($callback[0])->{$callback[1]}($pages$rootPage->idtrue$rootPage->language);
  86.             }
  87.         }
  88.         return $pages;
  89.     }
  90.     private function getPageAndArticleUrls(int $parentPageId): array
  91.     {
  92.         $pageModelAdapter $this->getContaoAdapter(PageModel::class);
  93.         // Since the publication status of a page is not inherited by its child
  94.         // pages, we have to use findByPid() instead of findPublishedByPid() and
  95.         // filter out unpublished pages in the foreach loop (see #2217)
  96.         $pageModels $pageModelAdapter->findByPid($parentPageId, ['order' => 'sorting']);
  97.         if (null === $pageModels) {
  98.             return [];
  99.         }
  100.         $articleModelAdapter $this->getContaoAdapter(ArticleModel::class);
  101.         $result = [];
  102.         // Recursively walk through all subpages
  103.         foreach ($pageModels as $pageModel) {
  104.             if ($pageModel->protected && !$this->isGranted(ContaoCorePermissions::MEMBER_IN_GROUPS$pageModel->groups)) {
  105.                 continue;
  106.             }
  107.             $isPublished $pageModel->published && (!$pageModel->start || $pageModel->start <= time()) && (!$pageModel->stop || $pageModel->stop time());
  108.             if (
  109.                 $isPublished
  110.                 && !$pageModel->requireItem
  111.                 && 'noindex,nofollow' !== $pageModel->robots
  112.                 && $this->pageRegistry->supportsContentComposition($pageModel)
  113.                 && $this->pageRegistry->isRoutable($pageModel)
  114.                 && 'html' === $this->pageRegistry->getRoute($pageModel)->getDefault('_format')
  115.             ) {
  116.                 $urls = [$pageModel->getAbsoluteUrl()];
  117.                 // Get articles with teaser
  118.                 if (null !== ($articleModels $articleModelAdapter->findPublishedWithTeaserByPid($pageModel->id, ['ignoreFePreview' => true]))) {
  119.                     foreach ($articleModels as $articleModel) {
  120.                         $urls[] = $pageModel->getAbsoluteUrl('/articles/'.($articleModel->alias ?: $articleModel->id));
  121.                     }
  122.                 }
  123.                 $result[] = $urls;
  124.             }
  125.             $result[] = $this->getPageAndArticleUrls((int) $pageModel->id);
  126.         }
  127.         return array_merge(...$result);
  128.     }
  129. }