diff --git a/core-bundle/src/Resources/config/services.yml b/core-bundle/src/Resources/config/services.yml
index a970a31a90a..03c61a3bab0 100644
--- a/core-bundle/src/Resources/config/services.yml
+++ b/core-bundle/src/Resources/config/services.yml
@@ -702,6 +702,7 @@ services:
- '@Contao\CoreBundle\Routing\ResponseContext\ResponseContextAccessor'
- '@event_dispatcher'
- '@contao.security.token_checker'
+ - '@contao.framework'
public: true
contao.search.indexer.default:
diff --git a/core-bundle/src/Resources/contao/pages/PageRegular.php b/core-bundle/src/Resources/contao/pages/PageRegular.php
index a840e84d36e..118ec4e029a 100644
--- a/core-bundle/src/Resources/contao/pages/PageRegular.php
+++ b/core-bundle/src/Resources/contao/pages/PageRegular.php
@@ -13,7 +13,6 @@
use Contao\CoreBundle\Exception\NoLayoutSpecifiedException;
use Contao\CoreBundle\Routing\ResponseContext\CoreResponseContextFactory;
use Contao\CoreBundle\Routing\ResponseContext\HtmlHeadBag\HtmlHeadBag;
-use Contao\CoreBundle\Routing\ResponseContext\JsonLd\ContaoPageSchema;
use Contao\CoreBundle\Routing\ResponseContext\JsonLd\JsonLdManager;
use Contao\CoreBundle\Routing\ResponseContext\ResponseContext;
use Contao\CoreBundle\Routing\ResponseContext\ResponseContextAccessor;
@@ -80,7 +79,8 @@ protected function prepare($objPage)
$locale = LocaleUtil::formatAsLocale($objPage->language);
$container = System::getContainer();
- $container->get('request_stack')->getCurrentRequest()->setLocale($locale);
+ $request = $container->get('request_stack')->getCurrentRequest();
+ $request->setLocale($locale);
$container->get('translator')->setLocale($locale);
$this->responseContext = $container->get(CoreResponseContextFactory::class)->createContaoWebpageResponseContext($objPage);
@@ -224,6 +224,12 @@ protected function prepare($objPage)
// Meta robots tag
$this->Template->robots = $this->responseContext->get(HtmlHeadBag::class)->getMetaRobots();
+ // Canonical
+ if ($objPage->enableCanonical)
+ {
+ $this->Template->canonical = $this->responseContext->get(HtmlHeadBag::class)->getCanonicalUri($request);
+ }
+
// Fall back to the default title tag
if (!$objLayout->titleTag)
{
diff --git a/core-bundle/src/Resources/contao/templates/frontend/fe_page.html5 b/core-bundle/src/Resources/contao/templates/frontend/fe_page.html5
index 97e9983a854..24e13184525 100644
--- a/core-bundle/src/Resources/contao/templates/frontend/fe_page.html5
+++ b/core-bundle/src/Resources/contao/templates/frontend/fe_page.html5
@@ -13,6 +13,12 @@
endblock(); ?>
+ block('canonical'); ?>
+ canonical): ?>
+
+
+ endblock(); ?>
+
= $this->viewport ?>
= $this->framework ?>
= $this->stylesheets ?>
diff --git a/core-bundle/src/Routing/ResponseContext/CoreResponseContextFactory.php b/core-bundle/src/Routing/ResponseContext/CoreResponseContextFactory.php
index a93f61f2e95..4f5b1643347 100644
--- a/core-bundle/src/Routing/ResponseContext/CoreResponseContextFactory.php
+++ b/core-bundle/src/Routing/ResponseContext/CoreResponseContextFactory.php
@@ -12,10 +12,13 @@
namespace Contao\CoreBundle\Routing\ResponseContext;
+use Contao\Controller;
+use Contao\CoreBundle\Framework\ContaoFramework;
use Contao\CoreBundle\Routing\ResponseContext\HtmlHeadBag\HtmlHeadBag;
use Contao\CoreBundle\Routing\ResponseContext\JsonLd\ContaoPageSchema;
use Contao\CoreBundle\Routing\ResponseContext\JsonLd\JsonLdManager;
use Contao\CoreBundle\Security\Authentication\Token\TokenChecker;
+use Contao\Environment;
use Contao\PageModel;
use Contao\StringUtil;
use Spatie\SchemaOrg\WebPage;
@@ -26,12 +29,14 @@ class CoreResponseContextFactory
private ResponseContextAccessor $responseContextAccessor;
private EventDispatcherInterface $eventDispatcher;
private TokenChecker $tokenChecker;
+ private ContaoFramework $contaoFramework;
- public function __construct(ResponseContextAccessor $responseContextAccessor, EventDispatcherInterface $eventDispatcher, TokenChecker $tokenChecker)
+ public function __construct(ResponseContextAccessor $responseContextAccessor, EventDispatcherInterface $eventDispatcher, TokenChecker $tokenChecker, ContaoFramework $contaoFramework)
{
$this->responseContextAccessor = $responseContextAccessor;
$this->eventDispatcher = $eventDispatcher;
$this->tokenChecker = $tokenChecker;
+ $this->contaoFramework = $contaoFramework;
}
public function createResponseContext(): ResponseContext
@@ -83,6 +88,20 @@ public function createContaoWebpageResponseContext(PageModel $pageModel): Respon
$htmlHeadBag->setMetaRobots($pageModel->robots);
}
+ if ($pageModel->enableCanonical && $pageModel->canonicalLink) {
+ $url = $this->contaoFramework->getAdapter(Controller::class)->replaceInsertTags($pageModel->canonicalLink, false);
+
+ // Ensure absolute links (FIXME: Remove once we remove support for relative urls)
+ if (!preg_match('#^https?://#', $url)) {
+ $url = $this->contaoFramework->getAdapter(Environment::class)->get('base').$url;
+ }
+ $htmlHeadBag->setCanonicalUri($url);
+ }
+
+ if ($pageModel->enableCanonical && $pageModel->canonicalKeepParams) {
+ $htmlHeadBag->setKeepParamsForCanonical(array_map('trim', explode(',', (string) $pageModel->canonicalKeepParams)));
+ }
+
$jsonLdManager
->getGraphForSchema(JsonLdManager::SCHEMA_CONTAO)
->set(