Importando metatags con Drupal 8 y Migrate

Estoy en mitad de un proyecto en el que estamos importando a Drupal 8 contenidos que vienen de un repositorio central, y se consumen usando una API REST en formato JSON.

Hasta ahora esta funcionando bastante bien y sin muchos dolores de cabeza, pero tratando de importar los metadatos que vienen con nuestros contenidos a los campos que trae el módulo Metatag he tenido que pegarme un poco hasta dar con la solución.

Actualmente (octubre de 2017), el módulo Metatag no tiene soporte para Migrate en forma de plugins. Hay un ticket abierto desde hace tiempo, con varios parches disponibles, pero aún no está completo. Lo bueno es que en ese mismo hilo hay un par de personas que cuentan como lo han hecho sin necesitar el parche, que era justo lo que yo intentaba, pero se me estaba escapando un detalle: serializar.

Para esta importación estamos usando los siguientes módulos:

Para importar contenido usando migrate_plus vamos a necesitar una serie de componentes:

  • Un archivo YML de control para la migración, donde especificaremos el tipo de proceso, los campos entrada y salida
  • Un plugin de importación de contenido, que será una clase que extienda de SourcePluginBase, y donde podremos hacer algunas transformaciones.
  • Lo normal será que hayamos creado un módulo custom para controlar toda nuestra importación.

No me quiero meter mucho en detalle de cómo se hace la importación usando migrate_plus, porque quiero escribir un artículo entero para ello, así que me voy a centrar en importar los metadatos.

Primero el archivo YML que controla la importación en mi módulo, migrate_plus.migration.json_page.yml:

id: json_page
label: 'JSON migration for content type: Page'
migration_group: my_group
source:
  plugin: json_page
  cache_counts: true
  high_water_property:
    name: changed
destination:
  plugin: entity:node
  bundle: page
process:
  uuid: uuid
  langcode: language
  type:
    plugin: default_value
    default_value: page
  title: title
  created: changed
  changed: changed
  'body/format': 'body/format'
  'body/value': 'body/value'
  'body/summary': 'body/summary'
  'field_page_teaser/format': 'teaser/format'
  'field_page_teaser/value': 'teaser/value'
  'path/alias': alias
  'promote/value':
    plugin: default_value
    default_value: 0
  field_meta_tags: meta_tags

# use forced module dependency so uninstall/reinstall works properly
dependencies:
  enforced:
    module:
      - my_module_name

Lo pongo entero por si ayuda para ver como estoy importando otros campos, pero lo importante es esta línea para los metadatos:

field_meta_tags: meta_tags

Esta línea va a intentar importar los metadatos desde un campo 'meta_tags'  en el origen (el source), a un campo llamado field_meta_tags, que ya hemos creado en nuestro tipo de contenido, en este caso page, y que es de tipo Meta tags.

En mi caso, los metadatos vienen en JSON que representa un nodo en este formato. Atención al detalle de que es 'metatags', no 'meta_tags':

    "metatags":{  
      "title":"a simple node title | [site:name]",
      "og:title":"a simple node title",
      "twitter:title":"a simple node title",
      "description":"a simple description",
      "og:description":"a simple description",
      "og:updated_time":"2018-08-27T08:40:34-04:00",
      "article:published_time":"2017-04-13T04:26:00-04:00",
      "article:modified_time":"2018-08-27T08:40:34-04:00",
      "twitter:description":"a simple description",
      "canonical":"[site:url][current-page:url:path]",
      "og:site_name":"[site:name]",
      "og:type":"article",
      "og:url":"[site:url][current-page:url:path]",
      "twitter:card":"summary",
      "twitter:url":"[site:url][current-page:url:path]"
    },

Como veis ya vienen con tokens que Drupal va a entender, asi que genial. Lo que vamos a hacer es utilizar el plugin que hemos definido para esta importación (json_page) para coger estos datos que vienen en el source de la importación como 'metatags', darles el formato adecuado, y colocarlos en 'meta_tags', que es lo que espera el proceso de migración, definido en el archivo YML de antes.

Este es el código de mi plugin:

class MyJsonPlugin extends SourcePluginBase {

  public function prepareRow(Row $row) {

    // Set URL alias.
    $alias = $row->getSourceProperty('alias');
    if (!empty($alias)) {
      $row->setSourceProperty('alias', '/' . $alias);
    }

    // Set meta tags.
    $row->setSourceProperty('meta_tags', $row->getSourceProperty('metatags'));

    return parent::prepareRow($row);
  }

  protected function processMetaTags($data) {
    $metatags = [];

    if ($data) {
      foreach ($data as $key => $value) {
        $transforms = [
          'twitter:card' => 'twitter_cards_type',
          'twitter:url' => 'twitter_cards_page_url',
          'twitter:description' => 'twitter_cards_description',
          'twitter:site' => 'twitter_cards_site',
          'twitter:site:id' => 'twitter_cards_site_id',
          'twitter:creator' => 'twitter_cards_creator',
          'twitter:creator:id' => 'twitter_cards_creator_id',
          'twitter:title' => 'twitter_cards_title',
          'canonical' => 'canonical_url',
          'og:url' => 'og_url',
          'og:title' => 'og_title',
          'og:site_name' => 'og_site_name',
          'og:type' => 'og_type',
          'og:description' => 'og_description',
          'og:image' => 'og_image',
          'og:image:url' => 'og_image_url',
          'og:image:secure_url' => 'og_image_secure_url',
          'og:image:type' => 'og_image_type',
          'article:published_time' => 'article_published_time',
          'article:modified_time' => 'article_modified_time',
        ];

        if (array_key_exists($key, $transforms)) {
          $key = $transforms[$key];
        }

        if ($value) {
          $metatags[$key] = $value;
        }
      }
    }

    if (!$metatags) {
      return NULL;
    }

    return serialize($metatags);
  }

...

}

Básicamente hay dos funciones importantes: en prepareRow, cogemos los metadatos desde 'metatags' que tiene el JSON, los formateamos y los volvemos a dejar bien bonitos en 'meta_tags', y en la función processMetaTags los procesamos y serializamos.

Aquí hay dos cosas a tener en cuenta:

  • Mis datos de origen en el JSON, vienen con los nombres de campo de Drupal 7, por ejemplo twitter:card, y los nombres de los campos de Drupal 8 son diferentes, por eso en la función processMetaTags revisamos las claves las cambiamos por las que corresponden. En esta lista no están todas las posibles pero sí las más importantes. Intentaré actualizarla en el futuro cuando la tenga completa.
  • Una vez construido el array con los meta datos, tenemos que serializarlo (función serialize de PHP) antes de llamar a setSourceProperty, porque si no lo hacemos, los datos no se guardarán correctamente.

Y esto es todo amigos, si teneis alguna idea mejor o algún problema podéis dejar una nota en los comentarios.

 

Referencias