-
-
Notifications
You must be signed in to change notification settings - Fork 165
Dynamically add robots.txt and favicon.ico per root page #717
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
06b2512
c94ebe4
750facb
a8d2c3d
777dee6
f7e132a
d4e14ff
c0cc961
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of Contao. | ||
* | ||
* (c) Leo Feyer | ||
* | ||
* @license LGPL-3.0-or-later | ||
*/ | ||
|
||
namespace Contao\CoreBundle\Controller; | ||
|
||
use Contao\CoreBundle\Framework\ContaoFramework; | ||
use Contao\FilesModel; | ||
use Contao\PageModel; | ||
use FOS\HttpCache\ResponseTagger; | ||
use Symfony\Component\HttpFoundation\BinaryFileResponse; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\Routing\Annotation\Route; | ||
|
||
/** | ||
* @Route(defaults={"_scope" = "frontend"}) | ||
*/ | ||
class FaviconController | ||
{ | ||
/** | ||
* @var ContaoFramework | ||
*/ | ||
private $contaoFramework; | ||
|
||
/** | ||
* @var ResponseTagger|null | ||
*/ | ||
private $responseTagger; | ||
|
||
public function __construct(ContaoFramework $contaoFramework, ResponseTagger $responseTagger = null) | ||
{ | ||
$this->contaoFramework = $contaoFramework; | ||
$this->responseTagger = $responseTagger; | ||
} | ||
|
||
/** | ||
* @Route("/favicon.ico", name="contao_favicon") | ||
*/ | ||
public function __invoke(Request $request): Response | ||
{ | ||
$this->contaoFramework->initialize(); | ||
|
||
/** @var PageModel $pageModel */ | ||
$pageModel = $this->contaoFramework->getAdapter(PageModel::class); | ||
|
||
/** @var PageModel|null $rootPage */ | ||
$rootPage = $pageModel->findPublishedFallbackByHostname( | ||
$request->server->get('HTTP_HOST'), | ||
['fallbackToEmpty' => true] | ||
); | ||
|
||
if (null === $rootPage || null === ($favicon = $rootPage->favicon)) { | ||
return new Response('', Response::HTTP_NOT_FOUND); | ||
} | ||
|
||
/** @var FilesModel $filesModel */ | ||
$filesModel = $this->contaoFramework->getAdapter(FilesModel::class); | ||
$faviconModel = $filesModel->findByUuid($favicon); | ||
|
||
if (null === $faviconModel) { | ||
return new Response('', Response::HTTP_NOT_FOUND); | ||
} | ||
|
||
// Cache the response for 1 year and tag it so it is invalidated when the settings are edited | ||
$response = new BinaryFileResponse($faviconModel->path); | ||
$response->setSharedMaxAge(31556952); | ||
|
||
if (null !== $this->responseTagger) { | ||
$this->responseTagger->addTags(['contao.db.tl_page.'.$rootPage->id]); | ||
} | ||
|
||
return $response; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of Contao. | ||
* | ||
* (c) Leo Feyer | ||
* | ||
* @license LGPL-3.0-or-later | ||
*/ | ||
|
||
namespace Contao\CoreBundle\Controller; | ||
|
||
use Contao\CoreBundle\Event\ContaoCoreEvents; | ||
use Contao\CoreBundle\Event\RobotsTxtEvent; | ||
use Contao\CoreBundle\Framework\ContaoFramework; | ||
use Contao\PageModel; | ||
use Symfony\Component\EventDispatcher\EventDispatcherInterface; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\Routing\Annotation\Route; | ||
use webignition\RobotsTxt\File\Parser; | ||
|
||
/** | ||
* @Route(defaults={"_scope" = "frontend"}) | ||
*/ | ||
class RobotsTxtController | ||
{ | ||
/** | ||
* @var ContaoFramework | ||
*/ | ||
private $contaoFramework; | ||
|
||
/** | ||
* @var EventDispatcherInterface | ||
*/ | ||
private $eventDispatcher; | ||
|
||
public function __construct(ContaoFramework $contaoFramework, EventDispatcherInterface $eventDispatcher) | ||
{ | ||
$this->contaoFramework = $contaoFramework; | ||
$this->eventDispatcher = $eventDispatcher; | ||
} | ||
|
||
/** | ||
* @Route("/robots.txt", name="contao_robots_txt") | ||
*/ | ||
public function __invoke(Request $request): Response | ||
{ | ||
$this->contaoFramework->initialize(); | ||
|
||
/** @var PageModel $pageModel */ | ||
$pageModel = $this->contaoFramework->getAdapter(PageModel::class); | ||
|
||
/** @var PageModel|null $rootPage */ | ||
$rootPage = $pageModel->findPublishedFallbackByHostname( | ||
$request->server->get('HTTP_HOST'), | ||
['fallbackToEmpty' => true] | ||
); | ||
|
||
if (null === $rootPage) { | ||
return new Response('', Response::HTTP_NOT_FOUND); | ||
} | ||
|
||
$parser = new Parser(); | ||
$parser->setSource($rootPage->robotsTxt); | ||
$file = $parser->getFile(); | ||
|
||
$this->eventDispatcher->dispatch(ContaoCoreEvents::ROBOTS_TXT, new RobotsTxtEvent($file, $request, $rootPage)); | ||
|
||
return new Response((string) $file, 200, ['Content-Type' => 'text/plain; charset=UTF-8']); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -68,6 +68,15 @@ final class ContaoCoreEvents | |
*/ | ||
public const PREVIEW_URL_CONVERT = 'contao.preview_url_convert'; | ||
|
||
/** | ||
* The contao.robots_txt event is triggered when the /robots.txt route is called. | ||
* | ||
* @var string | ||
* | ||
* @see RobotsTxtEvent | ||
*/ | ||
public const ROBOTS_TXT = 'contao.robots_txt'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is deprecated in Symfony to use event names. The event class should be the name… There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes but I cannot just change that atm as we do not require 4.4 yet. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not? Just use |
||
|
||
/** | ||
* The contao.slug_valid_characters event is triggered when the valid slug characters options are generated. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of Contao. | ||
* | ||
* (c) Leo Feyer | ||
* | ||
* @license LGPL-3.0-or-later | ||
*/ | ||
|
||
namespace Contao\CoreBundle\Event; | ||
|
||
use Contao\PageModel; | ||
use Symfony\Component\EventDispatcher\Event; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use webignition\RobotsTxt\File\File; | ||
|
||
class RobotsTxtEvent extends Event | ||
{ | ||
/** | ||
* @var File | ||
*/ | ||
private $file; | ||
|
||
/** | ||
* @var Request | ||
*/ | ||
private $request; | ||
|
||
/** | ||
* @var PageModel | ||
*/ | ||
private $rootPage; | ||
|
||
public function __construct(File $file, Request $request, PageModel $rootPage) | ||
{ | ||
$this->file = $file; | ||
$this->request = $request; | ||
$this->rootPage = $rootPage; | ||
} | ||
|
||
public function getFile(): File | ||
{ | ||
return $this->file; | ||
} | ||
|
||
public function getRequest(): Request | ||
{ | ||
return $this->request; | ||
} | ||
|
||
public function getRootPage(): PageModel | ||
{ | ||
return $this->rootPage; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of Contao. | ||
* | ||
* (c) Leo Feyer | ||
* | ||
* @license LGPL-3.0-or-later | ||
*/ | ||
|
||
namespace Contao\CoreBundle\EventListener; | ||
|
||
use Contao\CoreBundle\Event\RobotsTxtEvent; | ||
use Contao\CoreBundle\Framework\ContaoFramework; | ||
use Contao\PageModel; | ||
use webignition\RobotsTxt\Directive\Directive; | ||
use webignition\RobotsTxt\Inspector\Inspector; | ||
use webignition\RobotsTxt\Record\Record; | ||
|
||
class RobotsTxtListener | ||
{ | ||
/** | ||
* @var ContaoFramework | ||
*/ | ||
private $contaoFramework; | ||
|
||
public function __construct(ContaoFramework $contaoFramework) | ||
{ | ||
$this->contaoFramework = $contaoFramework; | ||
} | ||
|
||
public function onRobotsTxt(RobotsTxtEvent $event): void | ||
{ | ||
$this->contaoFramework->initialize(); | ||
|
||
$file = $event->getFile(); | ||
$inspector = new Inspector($file); | ||
|
||
// Get all directives for user-agent: * | ||
$directiveList = $inspector->getDirectives(); | ||
|
||
// If no directive for user-agent: * exists, we add the record | ||
if (0 === $directiveList->getLength()) { | ||
$record = new Record(); | ||
$this->addContaoDisallowDirectivesToRecord($record); | ||
$file->addRecord($record); | ||
} | ||
|
||
$records = $file->getRecords(); | ||
|
||
foreach ($records as $record) { | ||
$this->addContaoDisallowDirectivesToRecord($record); | ||
} | ||
|
||
/** @var PageModel $pageModel */ | ||
$pageModel = $this->contaoFramework->getAdapter(PageModel::class); | ||
$rootPages = $pageModel->findPublishedRootPages(['dns' => $event->getRootPage()->dns]); | ||
|
||
// Generate the sitemaps | ||
foreach ($rootPages as $rootPage) { | ||
if (!$rootPage->createSitemap) { | ||
continue; | ||
} | ||
|
||
$sitemap = sprintf( | ||
'%s%s/share/%s.xml', | ||
$rootPage->useSSL ? 'https://' : 'http://', | ||
$rootPage->dns ?: $event->getRequest()->server->get('HTTP_HOST'), | ||
$rootPage->sitemapName | ||
); | ||
|
||
$event->getFile()->getNonGroupDirectives()->add(new Directive('Sitemap', $sitemap)); | ||
} | ||
} | ||
|
||
private function addContaoDisallowDirectivesToRecord(Record $record): void | ||
{ | ||
$directiveList = $record->getDirectiveList(); | ||
$directiveList->add(new Directive('Disallow', '/contao$')); | ||
$directiveList->add(new Directive('Disallow', '/contao?')); | ||
$directiveList->add(new Directive('Disallow', '/contao/')); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,22 @@ services: | |
- { name: container.service_subscriber } | ||
public: true | ||
|
||
Contao\CoreBundle\Controller\FaviconController: | ||
arguments: | ||
- '@contao.framework' | ||
- '@?fos_http_cache.http.symfony_response_tagger' | ||
tags: | ||
- controller.service_arguments | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. afaik it is still needed for dependency injection in the constructor. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, what you mean is the service locator. But this controller does not extend from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My custom controllers do not extend from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, or not, just checked it again. Setting the controller to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guys, this PR is merged. If you want changes, create a new PR. Nothing will happen here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still following the discussion though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
public: true | ||
|
||
Contao\CoreBundle\Controller\RobotsTxtController: | ||
arguments: | ||
- '@contao.framework' | ||
- '@event_dispatcher' | ||
tags: | ||
- controller.service_arguments | ||
public: true | ||
|
||
contao.assets.assets_context: | ||
class: Contao\CoreBundle\Asset\ContaoContext | ||
arguments: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does the route really need a route name? I mean noone will ever generate that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Imho a route should have a name yes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://symfony.com/doc/current/routing.html#generating-urls
I think that would be totally fine in this case, noone ever will override the named route, as it is not used to generate a route anyway.