Removing Duplicate Path of the Site Contact Form in Drupal 8
If you, just like me, use Drupal’s Contact module to manage your site-wide contact form, you may have noticed that the contact form marked as default is available on two paths. This behavior is very bad for SEO. In this article, I will present a solution to you that solves this problem.
The problem we solve
Let’s say you have the Contact module installed and the feedback
form supplied by it is also available. If this is the default form then it is both available on the /contact
and on the /contact/feedback
path.
This is because the Contact module defines two routes for rendering contact forms. One of them is the entity.contact_form.canonical
with the /contact/{contact_form}
path, where {contact_form}
represents the entity ID of a contact form. The other route is contact.site_page
with the /contact
path that renders the contact form that is marked default.
And this is why we have two URIs for the same thing: the default contact form can be reached by the /contact
page because… yes, because it is the default form. And it is also available on its canonical path, at /contact/feedback
, because its ID is feedback.
Solution
My suggestion is: keep the contact.site_page
(/contact path
) for the default contact form, and redirect the canonical path of the default contact form to the contact.site_page
route.
So, this functional test should pass:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php
namespace Drupal\Tests\st_contact\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests contact form canonical route redirection.
*
* @group contact
*/
class ContactFormPathTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['block', 'contact_test', 'st_contact'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
$this->drupalPlaceBlock('system_main_block');
}
/**
* Tests node canonical route access.
*/
public function testContactFormRoutes() {
user_role_grant_permissions('anonymous', ['access site-wide contact form']);
// Test that contact path is accessible.
$this->drupalGet('contact');
$this->assertSession()->addressEquals('contact');
$this->assertSession()->statusCodeEquals('200');
$this->assertSession()->pageTextContains('Website feedback');
// Test that if the default forms canonical path is requested, we are
// redirected to the canonical path.
$this->drupalGet('contact/feedback');
$this->assertSession()->addressEquals('contact');
$this->assertSession()->statusCodeEquals('200');
$this->assertSession()->pageTextContains('Website feedback');
}
}
The implementation
If you followed my previous articles, you already know what I’m going to do to accomplish this. In a new route subscriber service we will replace the controller of the canonical route with a new one; and in the new controller, we will implement the logic above.
Let’s create a new module with the st_contact
name(space)! Contents of st_contact.info.yml
:
1
2
3
4
5
6
name: 'Standard Contact'
description: 'A module for contact forms that redirects the path of the default contact form to the contact.site_page route.'
core: '8.x'
type: module
dependencies:
- drupal:contact
st_contact.services.yml
:
1
2
3
4
5
services:
st_contact.route_subscriber:
class: Drupal\st_contact\Routing\RouteSubscriber
tags:
- { name: 'event_subscriber' }
The RouteSubscriber
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
namespace Drupal\st_contact\Routing;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;
/**
* Modifies contact form routes.
*/
class RouteSubscriber extends RouteSubscriberBase {
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
if ($route = $collection->get('entity.contact_form.canonical')) {
$route->setDefault('_controller', '\Drupal\st_contact\Controller\ContactController::contactSitePage');
}
}
}
The new controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php
namespace Drupal\st_contact\Controller;
use Drupal\contact\ContactFormInterface;
use Drupal\contact\Controller\ContactController as CoreContactController;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Enhanced Controller for contact form routes.
*/
class ContactController extends CoreContactController {
/**
* Presents the site-wide contact form.
*
* @param \Drupal\contact\ContactFormInterface|null $contact_form
* The contact form to use.
*
* @return array|RedirectResponse
* The form as render array as expected by
* \Drupal\Core\Render\RendererInterface::render() or a redirect response.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* Exception is thrown when user tries to access non existing default
* contact form.
*/
public function contactSitePage(ContactFormInterface $contact_form = NULL) {
if (!empty($contact_form)) {
$config = $this->config('contact.settings');
$default_form = $this->entityManager()
->getStorage('contact_form')
->load($config->get('default_form'));
if ($contact_form->id() === $default_form->id()) {
return new RedirectResponse(Url::fromRoute('contact.site_page', [], ['absolute' => TRUE])->toString(), 307);
}
}
return parent::contactSitePage($contact_form);
}
}
Problem solved 🥳!