Prevent access to untranslated pages with Drupal 8

Map world

It is not uncommon when a multilingual Drupal 8 project is implemented, that the pages translations are not ready at the time of production. If making a Drupal 8 multilingual site has become really easy to implement, there is now a fundamental difference with Drupal 7: it is impossible to disable a language. Either install a language, or delete it. This change is assumed, and voluntary, so as to not generate unnecessary complexity for questionable gain.

But what to do, after having configured, translated the interfaces and looked after a whole multilingual project, when comes the production and no content is translated?

Delete untranslated languages? And thus lose all the translation of the project configuration? It's an option.

Leave the pages untranslated accessible to users and search bots? This can give a real bad image of the site. What is this site that offers several languages ​​but is not translated. Not to mention the system url that will inevitably be present, nor SEO impacts (even if we can reduce them significantly by adding on these pages the metatag noindex). And more generally an unfinished aspect. And do not try to hide urls, they will inevitably be found.

There is a third alternative, softer. Leave in place the languages ​​installed on the site, and simply prevent visitors from accessing it, by redirecting to the default language page. While allowing publishers to access and translate these pages. It can also give content managers time to translate an entire site while it is online.

Let's look at how to implement this solution.

We will create an EventSubscriber in a custom module my_module. We declare in the file my_module.services.yml this service which we call my_module.language_access.

my_module.language_access:
  class: Drupal\my_module\EventSubscriber\LanguageAccessSubscriber
  arguments: ['@current_user', '@language_manager']
  tags:
    - { name: event_subscriber }

 

Note that we load as arguments for this service, the service allowing us to load the current user (@current_user), and the service managing the site languages (@language_manager).

We have declared our class LanguageAccessSubscriber. Let's look at the code right away.

<?php

namespace Drupal\my_module\EventSubscriber;

use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Drupal\Core\Routing\RouteMatch;
use Symfony\Component\HttpFoundation\RedirectResponse;

/**
 * Class Subscriber.
 *
 * @package Drupal\my_module
 */
class LanguageAccessSubscriber implements EventSubscriberInterface {

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $currentUser;

  /**
   * The language manager.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  /**
   * Constructor.
   *
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   the language manager.
   */
  public function __construct(AccountInterface $current_user, LanguageManagerInterface $language_manager) {
    $this->currentUser = $current_user;
    $this->languageManager = $language_manager;
  }

  /**
   * {@inheritdoc}
   */
  static function getSubscribedEvents() {
    $events[KernelEvents::REQUEST] = ['onRequestRedirect'];
    return $events;
  }

  /**
   * This method is called whenever the kernel.request event is
   * dispatched.
   *
   * @param GetResponseEvent $event
   */
  public function onRequestRedirect(GetResponseEvent $event) {
    $request = $event->getRequest();

    if ($this->currentUser->hasPermission('access non default language pages')) {
      return;
    }

    // If we've got an exception, nothing to do here.
    if ($request->get('exception') != NULL) {
      return;
    }

    $default_language = $this->languageManager->getDefaultLanguage();
    $active_language = $this->languageManager->getCurrentLanguage();

    if ($active_language->getId() != $default_language->getId()) {
      $route_match = RouteMatch::createFromRequest($request);
      $route_name = $route_match->getRouteName();
      $parameters = $route_match->getRawParameters()->all();
      $url = Url::fromRoute($route_name, $parameters, ['language' => $default_language]);
      $new_response = new RedirectResponse($url->toString(), '302');
      $event->setResponse($new_response);
    }
  }

}

This service declared as an Eventsubscriber (see the tags in the service declaration) will subscribe to the event kernel.request, event triggered very early during the propagation of a request. And with each request, this service will execute the onRequestRedirect() method.

If the current user has permission (custom permission to create in your custom module) to access all languages, then we do nothing.

if ($this->currentUser->hasPermission('access non default language pages')) {
  return;
}

Then, after having recovered both the active language and the default language, if they differ, we proceed to a temporary redirection (302) to the requested page but in the version of the default language of the site. For this, we create a RouteMatch object from the Request Symfony available from with the service, and generate a URL from that route by passing the default site language as an additional parameter.

$url = Url::fromRoute($route_name, $parameters, ['language' => $default_language]);

And it remains only to redirect to this new url.

$new_response = new RedirectResponse($url->toString(), '302');
$event->setResponse($new_response);

Of course you can just as easily decide to redirect to another page, or generate a 403 page or 404. It is according to your needs or your criteria.

In a few lines, we can now keep all the languages ​​installed on the Drupal 8 project, give time to translate them correctly, and this without impacting the site brand or its SEO.

To conclude, it is useful to exclude these untranslated pages from your sitemap.xml files if you generate one. Don't forget them. There is no need to tell search engines the existence of pages to index and for which they will be returned to their equivalent in the default language of the site.

A page-by-page redirection?

And if we needed to give access to content pages, as soon as they are translated. This is the same principle. It would be enough then to recover in the route parameters the entity in question (for example nodes), to check that the entity does indeed have a translation in the requested language, with the method hasTranslation(), and if not, proceeding again to a redirect. All waiting strategies are possible here.

A module ?

Would not there be space here for a module contributed? A motivated Drupal 8 developer?

 

Commentaires

Ajouter un commentaire