Problem/Motivation

PagedesignerFormatter::viewElements() calls getTranslation($langcode) on the host entity without checking that the translation exists. On a multilingual site, any node that has no translation in the requested content language produces a WSOD (HTTP 500) instead of falling back to the language it does have.

The formatter receives the content language in $langcode, not the entity language. For a node of a content type that is not translatable, or simply for a node that has not been translated yet, ContentEntityBase::getTranslation() throws:

InvalidArgumentException: Invalid translation language (en) specified.
  Drupal\Core\Entity\ContentEntityBase->getTranslation()
  Drupal\pagedesigner\Plugin\Field\FieldFormatter\PagedesignerFormatter->viewElements()  (line 101)
  Drupal\Core\Field\FormatterBase->view()
  Drupal\Core\Entity\Entity\EntityViewDisplay->buildMultiple()
  Drupal\Core\Entity\EntityViewBuilder->build()

The container entity on the next lines is already guarded with hasTranslation(), the host entity is not:

$node = $items->getEntity();
if ($node != NULL && $node instanceof ContentEntityInterface) {
  $node = $node->getTranslation($langcode);            // unguarded, throws
  foreach ($items as $item) {
    $container = $item->entity;
    if ($container != NULL && $container->hasTranslation($langcode)) {   // guarded
      $container = $container->getTranslation($langcode);

This is not an edge case on sites that use URL prefixes together with browser language detection: a visitor whose browser asks for a language the content does not have lands on the prefixed URL and gets the error page, and every hit is logged as an uncaught exception. Content types that are deliberately left untranslatable are affected permanently.

Affected: src/Plugin/Field/FieldFormatter/PagedesignerFormatter.php, line 101, identical in 4.2.7, 4.2.10, 4.2.11 and on the 3.x and 4.x branches.

Steps to reproduce

  1. Install a multilingual site with two languages and URL prefix negotiation, for example de as default and en as second language.
  2. Create a content type with a pagedesigner field and leave it not translatable.
  3. Create and publish a node of that type in the default language and add pagedesigner content to it.
  4. Request the node under the second prefix, for example /en/node/1.

Expected: the node renders in the language it exists in.
Actual: HTTP 500, with the exception above in the log.

Proposed resolution

Resolve the translation the way core does, through the entity repository, which falls back to the entity's own language instead of throwing:

$node = \Drupal::service('entity.repository')->getTranslationFromContext($node, $langcode);

Injected as a service in the formatter rather than called statically. A smaller, equivalent change is to mirror the guard used for the container:

if ($node->hasTranslation($langcode)) {
  $node = $node->getTranslation($langcode);
}

The first form is preferable because it also covers language fallback configurations. The container lookup below keeps its own guard either way, so containers without a translation are still skipped.

Remaining tasks

  • Patch the formatter and review whether the same fallback is wanted for the edit route.
  • Add a test that renders a node of a non-translatable bundle under a foreign language prefix.
  • Backport to the 3.x branch, which carries the same line.

User interface changes

None.

API changes

None, unless the entity repository is injected into the formatter, which changes its constructor signature.

Data model changes

None.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

guillaumepacilly created an issue. See original summary.

  • guillaumepacilly committed 698cb2ea on 4.x
    Issue #3624222 by guillaumepacilly: resolve the node translation through...
guillaumepacilly’s picture

Status: Active » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

guillaumepacilly’s picture

Status: Fixed » Closed (fixed)