8000 Dynamically add robots.txt and favicon.ico per root page by Toflar · Pull Request #717 · contao/contao · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

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

Merged
merged 8 commits into from
Sep 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@
"terminal42/service-annotation-bundle": "^1.0",
"toflar/psr6-symfony-http-cache-store": "^2.0",
"true/punycode": "^2.1",
"twig/twig": "^2.7"
"twig/twig": "^2.7",
"webignition/robots-txt-file": "^2.1"
},
"replace": {
"contao/calendar-bundle": "self.version",
Expand Down
3 changes: 2 additions & 1 deletion core-bundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@
"symfony/yaml": "4.2.* || 4.3.*",
"terminal42/service-annotation-bundle": "^1.0",
"true/punycode": "^2.1",
"twig/twig": "^2.7"
"twig/twig": "^2.7",
"webignition/robots-txt-file": "^2.1"
},
"conflict": {
"contao-community-alliance/composer-plugin": "<3.0",
Expand Down
83 changes: 83 additions & 0 deletions core-bundle/src/Controller/FaviconController.php
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")
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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

If you don't set the route name explicitly with the name option, Symfony generates an automatic name based on the controller and action.

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.

*/
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;
}
}
74 changes: 74 additions & 0 deletions core-bundle/src/Controller/RobotsTxtController.php
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']);
}
}
9 changes: 9 additions & 0 deletions core-bundle/src/Event/ContaoCoreEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Copy link
Member

Choose a reason for hiding this comment

The 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…

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not? Just use event::class as the event name?


/**
* The contao.slug_valid_characters event is triggered when the valid slug characters options are generated.
*
Expand Down
58 changes: 58 additions & 0 deletions core-bundle/src/Event/RobotsTxtEvent.php
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;
}
}
85 changes: 85 additions & 0 deletions core-bundle/src/EventListener/RobotsTxtListener.php
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/'));
}
}
6 changes: 6 additions & 0 deletions core-bundle/src/Resources/config/listener.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ services:
calls:
- ['setContainer', ['@service_container']]

Contao\CoreBundle\EventListener\RobotsTxtListener:
arguments:
- '@contao.framework'
tags:
- { name: kernel.event_listener, event: contao.robots_txt, method: onRobotsTxt }

contao.listener.add_to_search_index:
class: Contao\CoreBundle\EventListener\AddToSearchIndexListener
arguments:
Expand Down
16 changes: 16 additions & 0 deletions core-bundle/src/Resources/config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The controller.service_arguments tag is not needed if you don't get services in your action methods.

Copy link
Contributor
@fritzmg fritzmg Sep 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaik it is still needed for dependency injection in the constructor.

Copy link
Member

Choose a reason for hiding this comment

The 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 AbstractController so service locator is not used at all.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My custom controllers do not extend from AbstractController either and without the controller.service_arguments, no services would be injected into the constructor of the controller.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, or not, just checked it again. Setting the controller to public: true is enough.

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still following the discussion though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The __invoke() method needs the Request object. Will this still work without the tag?

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:
Expand Down
Loading
0