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

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.     /**
  29.      * @var PageRegistry
  30.      */
  31.     private $pageRegistry;
  32.     public function __construct(PageRegistry $pageRegistry)
  33.     {
  34.         $this->pageRegistry $pageRegistry;
  35.     }
  36.     /**
  37.      * @Route("/sitemap.xml")
  38.      */
  39.     public function __invoke(Request $request): Response
  40.     {
  41.         $this->initializeContaoFramework();
  42.         /** @var PageModel $pageModel */
  43.         $pageModel $this->get('contao.framework')->getAdapter(PageModel::class);
  44.         $rootPages $pageModel->findPublishedRootPages(['dns' => $request->server->get('HTTP_HOST')]);
  45.         if (null === $rootPages) {
  46.             // We did not find root pages by matching host name, let's fetch those that do not have any domain configured
  47.             $rootPages $pageModel->findPublishedRootPages(['dns' => '']);
  48.             if (null === $rootPages) {
  49.                 return new Response(''Response::HTTP_NOT_FOUND);
  50.             }
  51.         }
  52.         $urls = [];
  53.         $rootPageIds = [];
  54.         $tags = ['contao.sitemap'];
  55.         foreach ($rootPages as $rootPage) {
  56.             $pages $this->getPageAndArticleUrls((int) $rootPage->id);
  57.             $urls[] = $this->callLegacyHook($rootPage$pages);
  58.             $rootPageIds[] = $rootPage->id;
  59.             $tags[] = 'contao.sitemap.'.$rootPage->id;
  60.         }
  61.         $urls array_unique(array_merge(...$urls));
  62.         $sitemap = new \DOMDocument('1.0''UTF-8');
  63.         $sitemap->formatOutput true;
  64.         $urlSet $sitemap->createElementNS('https://www.sitemaps.org/schemas/sitemap/0.9''urlset');
  65.         foreach ($urls as $url) {
  66.             $loc $sitemap->createElement('loc'$url);
  67.             $urlEl $sitemap->createElement('url');
  68.             $urlEl->appendChild($loc);
  69.             $urlSet->appendChild($urlEl);
  70.         }
  71.         $sitemap->appendChild($urlSet);
  72.         $this->get('event_dispatcher')->dispatch(new SitemapEvent($sitemap$request$rootPageIds), ContaoCoreEvents::SITEMAP);
  73.         // Cache the response for a month in the shared cache and tag it for invalidation purposes
  74.         $response = new Response((string) $sitemap->saveXML(), 200, ['Content-Type' => 'application/xml; charset=UTF-8']);
  75.         $response->setSharedMaxAge(2592000); // will be unset by the MakeResponsePrivateListener if a user is logged in
  76.         $this->tagResponse($tags);
  77.         return $response;
  78.     }
  79.     private function callLegacyHook(PageModel $rootPage, array $pages): array
  80.     {
  81.         /** @var System $systemAdapter */
  82.         $systemAdapter $this->get('contao.framework')->getAdapter(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.         /** @var PageModel $pageModelAdapter */
  95.         $pageModelAdapter $this->get('contao.framework')->getAdapter(PageModel::class);
  96.         // Since the publication status of a page is not inherited by its child
  97.         // pages, we have to use findByPid() instead of findPublishedByPid() and
  98.         // filter out unpublished pages in the foreach loop (see #2217)
  99.         $pageModels $pageModelAdapter->findByPid($parentPageId, ['order' => 'sorting']);
  100.         if (null === $pageModels) {
  101.             return [];
  102.         }
  103.         /** @var ArticleModel $articleModelAdapter */
  104.         $articleModelAdapter $this->get('contao.framework')->getAdapter(ArticleModel::class);
  105.         $result = [];
  106.         // Recursively walk through all subpages
  107.         foreach ($pageModels as $pageModel) {
  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.                 && 'regular' === $pageModel->type // TODO: replace this with a better solution (see #3544)
  117.                 && $this->pageRegistry->supportsContentComposition($pageModel)
  118.             ) {
  119.                 $urls = [$pageModel->getAbsoluteUrl()];
  120.                 // Get articles with teaser
  121.                 if (null !== ($articleModels $articleModelAdapter->findPublishedWithTeaserByPid($pageModel->id, ['ignoreFePreview' => true]))) {
  122.                     foreach ($articleModels as $articleModel) {
  123.                         $urls[] = $pageModel->getAbsoluteUrl('/articles/'.($articleModel->alias ?: $articleModel->id));
  124.                     }
  125.                 }
  126.                 $result[] = $urls;
  127.             }
  128.             $result[] = $this->getPageAndArticleUrls((int) $pageModel->id);
  129.         }
  130.         // Backwards compatibility for PHP 7.3
  131.         if (empty($result)) {
  132.             return [];
  133.         }
  134.         return array_merge(...$result);
  135.     }
  136. }