Hero

Exportando contenido de Drupal 7 a Markdown

Enero 26, 2022

mvessuri
Drupal
Markdown

Recientemente se decidió rehacer el sitio web de 7Sabores en Gatsby, un framework de front end y generador de sitios estáticos basado en React. Una vez tomada esa decisión el siguiente paso fue definir cómo mover el contenido almacenado en la base de datos de Drupal 7 utilizada por el sitio original. Si bien es posible utilizar Drupal como fuente de contenido de Gatsby, se decidió dejar de utilizar un CMS y exportar todo el contenido de Drupal a Markdown.

Con la ayuda de un par de plugins, Gatsby puede leer archivos Markdown desde el sistema de archivos y generar páginas y listados a partir de ellos. Por esta razón Markdown fue la mejor opción al momento de exportar el contenido de Drupal y reducir la complejidad del nuevo sitio removiendo la necesidad de un CMS o una base de datos.

Exportando el contenido

La solución utilizada para exportar el contenido de Drupal consistió en dos scripts de PHP, uno para las entidades tipo nodo y otro para entidades usuario. Estos scripts PHP se ejecutan desde línea de comando y crean archivos en formato markdown por cada nodo y usuario que se almacenan en diferentes carpetas para facilitar la organización del contenido en Gatsby.

Para tener acceso a los datos de la base de datos del sitio y poder aprovechar las funciones y APIs de Drupal al comienzo de los scripts de exportación se hace un bootstrap completo de Drupal utilizando la función drupal_bootstrap().

define('DRUPAL_ROOT', getcwd());

require_once DRUPAL_ROOT . '/includes/bootstrap.inc';

drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

Esto nos permite acceder fácilmente a todos los datos que necesitamos de Drupal a través de dynamic queries usando la función db_select() y luego haciendo un load de todos los nodos con node_load_multiple().

Por ejemplo en el caso de los nodos tenemos lo siguiente:

$nids = db_select('node', 'n')
  ->fields('n', array('nid'))
  ->fields('n', array('type'))
  ->condition('n.status', 1)
  ->condition('n.type', $content_type)
  ->orderBy('n.created', 'ASC')
  ->execute()
  ->fetchCol();

$nodes = node_load_multiple($nids);

El script PHP para exportar contenido tiene como parámetro el tipo de contenido, es decir podemos exportar los nodos de un tipo en particular, por ejemplo “blog” o “page”, este parámetro se asigna a la variable $content_type. En el ejemplo anterior con la función db_select() se seleccionan todos los nodos de un tipo en particular que estén publicados y se devuelven sus IDs. Luego con node_load_multiple() se cargan todas las entidades de la base de datos que coincidan con los IDs seleccionados.

Una vez que tenemos las entidades el siguiente paso fue convertir el cuerpo de cada una de ellas a Markdown y guardarlo en un archivo. Para hacer esta conversión en este caso en particular utilizamos la librería HTML To Markdown for PHP.

La librería está disponible como un paquete de composer por lo que para utilizarla debemos primero requerir el paquete con el comando.

composer require league/html-to-markdown

Y luego añadir lo siguiente al inicio del script PHP para hacer el autoload de las dependencias.

require 'vendor/autoload.php';

Finalmente podemos crear un nuevo converter y utilizarlo para transformar el HTML del nodo.

$converter = new League\HTMLToMarkdown\HtmlConverter();

$converter->getConfig()->setOption('strip_tags', true);
$converter->convert($clean_html)

Complicaciones

Lamentablemente las cosas no siempre salen como uno las planea y hubo algunas complicaciones relacionadas a la conversión del contenido de nuestros nodos a markdown.

En nuestro caso en particular al revisar el contenido exportado encontramos problemas en los archivos de Markdown generados para muchos de los nodos que tenían bloques de código con tags <pre>. La razón no era el tag en sí, sino que ese tag tenía como atributo una clase.

La solución para evitar estos problemas fue eliminar los atributos antes de hacer la conversión de HTML a Markdown con la ayuda de las clases DOMDocument y DOMXPath de PHP.

// Crea un objeto DOMDocument.
$dom = new  DOMDocument();

// Carga el contenido del body y soluciona problemas de encoding.
$dom->loadHTML(mb_convert_encoding($node->body['und'][0]['value'], 'HTML-ENTITIES', 'UTF-8'));

Una vez que tenemos el contenido HTML del body de nuestro nodo parseado y representado por DOMDocument, podemos empezar a manipularlo con la ayuda de DOMXPath.

// Crea un objeto DOMXPath.
$xpath = new DOMXPath($dom);
// Buscamos todos los elementos <pre> y eliminamos el atributo class.
foreach ($xpath->evaluate("//pre") as $code_block) {
  $code_block->removeAttribute('class');
}

El último de los problemas está relacionado a los image style tokens que Drupal le añade a las imágenes embebidas dentro del contenido. Desde la versión 7.20 Drupal comenzó a añadir unos tokens en forma de un parámetro “itok” en el query string de las URLs de todas las imágenes derivadas para evitar ataques DDoS. Como este parámetro “itok” no debe estar en los archivos de Markdown, otra vez recurrimos a XPath para manipular el HTML.

// Buscamos todos los elementos <img>.
foreach ($xpath->evaluate("//img") as $image) {

  // Obtenemos el atributo src.
  $src = $image->getAttribute('src');

  // Dividimos la url en sus diferentes componentes.
  $parsed = parse_url($src);

  // Obtenemos los parámetros del query string.
  $query = $parsed['query'];
  parse_str($query, $params);

  // Eliminamos el parámetro "itok".
  unset($params['itok']);

  // Reconstruimos el query string de la URL.
  $parsed['query'] = http_build_query($params);

  // Reconstruimos la URL.
  $new_scr = unparse_url($parsed);

  // Reemplazamos la url de la imagen.
  $src = $image->setAttribute('src', $new_scr);

}

En el ejemplo utilizamos una función para revertir parse_url y regenerar la url en forma de string. Lamentablemente no existe una función en PHP para revertir el efecto de pase_url, pero hay muchos ejemplos de funciones unparse_url().

Finalmente salvamos todas las modificaciones realizadas al HTML en un string, y este es el string que luego utilizamos en la conversión de HTML a Markdown.

$clean_html = $dom->saveHtml();

Frontmatter

Hasta aquí obtuvimos el cuerpo de nuestras entidades en formato Markup, pero el contenido que quisiéramos migrar de un nodo no es solo su body, también hay otros datos que nos interesan como por ejemplo el autor, la fecha de creación, el path, y otros campos adicionales que se hayan creado en ese tipo de contenido.

Por suerte Gatsby nos provee una forma de representar toda esta información adicional en un archivo Markup. Con la ayuda de un Frontmatter podemos proporcionar datos adicionales relevantes a nuestro contenido para ser usados en la capa de datos de GraphQL.

En un libro el Front Matter es todo lo que va al comienzo del mismo, antes de su contenido principal. Por ejemplo la página del título, el copyright, el índice. En el caso de un archivo Markdown, el Frontmatter es un bloque de pares clave-valor en formato YAML que se encuentra al principio del documento, antes del contenido Markdown.

Para que Gatsby reconozca el Frontmatter, este debe ser la primera cosa que se encuentre en el archivo, tiene que estar en formato YAML válido, y además debe comenzar y terminar con una línea con tres --- guiones.

A continuación vemos un ejemplo del Frontmatter de uno de los artículos.

---
title: 'Como utilizar tablas personalizadas en Views con Drupal 7'
date: 2014-05-09
author: enzo
path: blog/utilizar-tablas-personalizadas-views-drupal-7
nid: 489
topics:
  - Drupal
  - 'Desarrollo de Modulos'
  - Vistas
cover: ../assets/custom_field_formatter_1.png
summary: "Como ya hemos visto en las entrada de blog Como crear tablas para nuestros módulos personalizados en Drupal 7 y Como agregar una nueva tabla a modulo ya activado en Drupal 7 es posible la creación de un modelo de datos personalizado en la base de datos que guarde cierta relación con el modelo de datos de Drupal."
---

# Contenido Markdown ...

Como se puede ver en el ejemplo anterior el Frontmatter nos permite tener una gran cantidad de información adicional al contenido Markdown. Los valores de cada uno de los pares pueden ser de diferentes tipos de datos, en este caso podemos ver strings, fechas, números, arrays/listas e incluso paths a imágenes. Toda la metadata contenida en el Frontmatter de nuestros archivos Markdown estará disponible en Gatsby en el GraphQL data layer.

Espero que les haya parecido interesante y que sirva de ayuda si alguna vez tienen que exportar datos de Drupal a Markdown.

Recibe consejos y oportunidades de trabajo 100% remotas y en dólares de weKnow Inc.