Problem/Motivation

I am currently adding Facebook Instant Articles to one of our project and stumbled upon the FieldItemListNormalizer::normalize() fallback that uses the FBIA SDK's Transformer class to transform rendered markup into the appropriate FBIA elements. This is especially useful when dealing with media entities (e.g. images, where you cannot use the "FBIA Image" formatter directly due to the referenced media entity)

Unfortunately all transformed FBIA elements are added to the content element of the article, but you may have use cases where you want to add things to the region, where the field is added to (e.g. place the header image media entity into the article header element and not the content element).

Is there a special reason, why these transformed FBIA elements are added to the article's content element during FieldItemListNormalizer::normalize()?

Proposed resolution

Extend the FieldItemListNormalizer::normalize() transformation fallback to use the appropriate article header/footer element as transformation context (instead of the article context everywhere).

Example for fields in the header region of the fb_instant_articles view mode:

$context = $article;

if ($component['region'] === Regions::REGION_HEADER) {
  $header = $article->getHeader();
  if (!$header) {
    $header = Header::create();
    $article->withHeader($header);
  }
  $context = $header;
}

$this->transformer->transform($context, $document);

This would e.g. allow to add a custom transformer rule using the HeaderImageRule to inject a referenced image media entity into the article header, where it belongs. With the current approach, such a rule would not create any markup, because the HeaderImageRule is used in the article content context and is therefore ignored.

Remaining tasks

  • Discuss whether such an approach would be the right way to go
  • Create a pull request for this functionality

User interface changes

n/a

API changes

  • Users that rely on added header fields with transformed markup being added to the article's content, will have to move their fields to the appropriate content region in the fb_instant_articles view mode instead

Data model changes

n/a

Comments

hctom created an issue. See original summary.

hctom’s picture

... another idea to solve this for referenced entites would be to create a "FBIA Rendered entity" formatter (extendingDrupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter class) that renders and transforms the referenced entities with the above stated additions for correct context handling like so:

  public function viewInstantArticle(FieldItemListInterface $items, InstantArticle $article, $region, $langcode = NULL) {
    // Need to call parent::prepareView() to populate the entities since it's
    // not otherwise getting called.
    $this->prepareView([$items->getEntity()->id() => $items]);

    /* @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) {
      $elements = parent::viewElements($items, $langcode);

      if ($markup = (string) $this->renderer->renderPlain($elements)) {
        // Pass the markup through the Transformer.
        $document = new \DOMDocument();
        // Before loading into DOMDocument, setup for success by taking care
        // of encoding issues.  Since we're dealing with HTML snippets, it
        // will always be missing a <meta charset="utf-8" /> or equivalent.
        $markup = '<!doctype html><html><head><meta charset="utf-8" /></head><body>' . $markup . '</body></html>';
        @$document->loadHTML(Html::decodeEntities($markup));

        $context = $article;

        if ($region === Regions::REGION_HEADER) {
          $header = $article->getHeader();

          if (!$header) {
            $header = Header::create();
            $article->withHeader($header);
          }

          $context = $header;
        }

        elseif ($region === Regions::REGION_FOOTER) {
          $footer = $article->getFooter();

          if (!$footer) {
            $footer = Footer::create();
            $article->withFooter($footer);
          }

          $context = $footer;
        }

        $this->transformer->transform($context, $document);
      }
    }
  }

Of course there should be another formatter for entities referenced via entity_reference_revisions module (e.g. for paragraphs).

hctom’s picture

Issue summary: View changes
hctom’s picture

Status: Active » Needs review
StatusFileSize
new2.61 KB

Thinking more about this, I would prefer to extend the transformation fallback in FieldItemListNormalizer::normalize(), because this is more generic and sou everybody can use it in combination with added transformer rules - regardless of what field type or formatter is in use.

Attached is a patch for this (if anybody needs to reference this e.g. in their composer.json file easily) and you can find a corresponding PR at:

https://github.com/BurdaMagazinOrg/module-fb_instant_articles/pull/121

m4olivei’s picture

Status: Needs review » Needs work

This is looking great. Thanks for doing this! I left a minor comment on your PR, plus, it would be nice to have test coverage for this change.

hctom’s picture

Status: Needs work » Needs review
StatusFileSize
new2.66 KB
new1.29 KB

Attached is the new updated patch without the $context variable naming conflict. The PR is updated as well.

  • m4olivei committed 8504603 on 8.x-2.x authored by hctom
    Issue #2915661 by hctom: Improve FieldItemListNormalizer transformer...
m4olivei’s picture

Status: Needs review » Fixed

Thanks so much! Reviewed and merged to 8.x-2.x.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.