Cómo migrar ficheros e imágenes con Drupal 8 a un campo media

Al migrar a Drupal 8 contenido desde otra fuente es habitual que algunos campos sean imágenes o ficheros. Aquí veremos cómo importarlos a un campo media.

Ya desde hace un tiempo el módulo media se incorporó al core y lo han ido mejorando para que ahora ya sea más util poner los ficheros e imágenes en este tipo de campos, en lugar de un campo image o file.

El módulo media permite, en otras cosas, reusar los contenidos, tener una biblioteca multimedia y añadir los campos que necesites. Es casi un estándard para los proyectos en Drupal que realizamos en Omitsis.

Pero a la hora de importar, de momento es más complicado que hacerlo directamente a un campo de tipo file. Aquí veremos cómo hacerlo.

Este post es una continuación de un artículo anterior donde veíamos cómo usar el módulo migrate para importar desde un csv. Es recomendable leerse primero este artículo si no tienes experiencia con el módulo migrate.

Hay un módulo que permite migrar entidades file a entidades media, el migrate files to media. También permite importarlos desde Drupal 7. En este caso no lo vamos a usar, usaremos solo el módulo migrate y los que comentamos en el post anterior.

Primero instalamos esos módulos con composer:

composer require drupal/migrate_plus drupal/migrate_tools drupal/migrate_source_csv drupal/config_devel

Y los habilitamos:

drush -y en migrate migrate_plus migrate_tools migrate_source_csv config_devel

Todos estos módulos están en Drupal 8 y Drupal 9, excepto el config_devel que aún no está para la 9, pero parece que estará en breve. Así que todo lo que vamos a contar servirá también para Drupal 9.

Crearemos un yml primero para importar las imágenes a files. Este fichero lo llamaremos migrate_plus.migration.migrate_files.yml.

id: migrate_files
label: Migrate Files
source:
  plugin: 'csv'
  # Full path to the file.
  path: 'modules/custom/custom_migrate/data/products.csv'
  # Column delimiter. Comma (,) by default.
  delimiter: ','
  # Field enclosure. Double quotation marks (") by default.
  enclosure: '"'
  # The row to be used as the CSV header (indexed from 0),
  # or null if there is no header row.
  header_offset: 0
  # The column(s) to use as a key. Each column specified will
  # create an index in the migration table and too many columns
  # may throw an index size error.
  ids:
    - code
  # Here we identify the columns of interest in the source file.
  # Each numeric key is the 0-based index of the column.
  # For each column, the key below is the field name assigned to
  # the data on import, to be used in field mappings below.
  # The label value is a user-friendly string for display by the
  # migration UI.
  fields:
    0:
      name: cat
      label: 'Categoria'
    1:
      name: code
      label: 'Product code'
    2:
      name: title
      label: 'Title'
    3:
      name: image
      label: 'Image'
  constants:
    file_source_uri: 'public://import/source/images'
    file_dest_uri: 'public://imports/dest/images'
process:
  file_source:
    -
      plugin: concat
      delimiter: /
      source:
        - constants/file_source_uri
        - image
    -
      plugin: urlencode

  file_dest:
    -
      plugin: concat
      delimiter: /
      source:
        - constants/file_dest_uri
        - image
    -
      plugin: urlencode

  # We use the image file names as is.
  #
  # Alternatively, if we wish to name them after some other
  # column, we can do it here.
  uri:
    plugin: file_copy
    source:
      - '@file_source'
      - '@file_dest'

  uid:
    plugin: default_value
    default_value: 1

destination:
  plugin: entity:file

migration_dependencies: {}

La primera parte de este fichero es casi idéntica al que usamos para migrar el contenido, ya que usaremos la misma fuente, un csv.

Lo destacado a resaltar:

  • Uso de constantes para indicar dónde estarán las imágenes y dónde las pondremos.
  • En process usaremos dos entradas para definir el source y el destination. Como puedes ver es simplemente concatenar el directorio de origen/destino con el campo del csv imagen que tiene solo el nombre de la imagen.
  • En process en el campo uri de la imagen usamos los valores anteriores con el plugin file_copy. Este plugin tiene más opciones para definirle qué hacer si ya existe el fichero. Las puedes ver en su código fuente.

El siguiente paso es usar estos files para crear las entidades media. Al fichero lo llamaremos migrate_plus.migration.media_images

id: media_images
label: Media images
source:
  plugin: 'csv'
  # Full path to the file.
  path: 'modules/custom/custom_migrate/data/products.csv'
  # Column delimiter. Comma (,) by default.
  delimiter: ','
  # Field enclosure. Double quotation marks (") by default.
  enclosure: '"'
  # The row to be used as the CSV header (indexed from 0),
  # or null if there is no header row.
  header_offset: 0
  # The column(s) to use as a key. Each column specified will
  # create an index in the migration table and too many columns
  # may throw an index size error.
  ids:
    - code
  # Here we identify the columns of interest in the source file.
  # Each numeric key is the 0-based index of the column.
  # For each column, the key below is the field name assigned to
  # the data on import, to be used in field mappings below.
  # The label value is a user-friendly string for display by the
  # migration UI.
  fields:
    0:
      name: cat
      label: 'Categoria'
    1:
      name: code
      label: 'Product code'
    2:
      name: title
      label: 'Title'
    3:
      name: image
      label: 'Image'
process:
  # Skip if already existent
  skip:
    -
      plugin: entity_lookup
      value_key: name
      source: image
      bundle_key: bundle
      bundle: image
      entity_type: media
      ignore_case: 1
      access_check: 0
    -
      plugin: skip_on_not_empty
      method: row
      message: 'Skipping already existent node'

  field_media_image/target_id:
    plugin: migration_lookup
    migration: calier_files
    source: code
    no_stub: true
  uid:
    plugin: default_value
    default_value: 1

destination:
  plugin: entity:media
  default_bundle: image

migration_dependencies:
  optional:
    - migrate_files

A destacar:

  • La parte inicial es la misma
  • El skip es para no crear un media para cada entrada en el csv si el fichero ya existe, ya que lo que queremos con el media es reusar las imágenes.
  • Para esto usaremos el plugin cutom skip_on_not_empty que copié de aquí.
  • Lo que hace el skip es usar el plugin entity_lookup para buscar una entidad media que su nombre sea el nombre del fichero. Si la encuentra con el plugin skip_on_not_empty conseguiremos que no la importe y así evitar duplicados.
  • Luego con el migration_lookup le indicaremos el fid del fichero que importamos anteriormente.
  • Solo falta indicar un destination que será un entity_media. El bundle se podría poner en el process si fuera cambiando por cada fila pero en este caso son siempre imágenes, por lo que podemos definir un default_bundle directamente.
  • Finalmente indicamos que esta migración depende de la anterior. Está puesta como «optional» porque con required daba problemas. Hay un issue sobre esto que se está resolviendo.

Y ahora solo queda la importación del contenido en sí, haciendo referencia a las entidades media creadas.

id: products
label: Import products

source:
  plugin: 'csv'
  # Full path to the file.
  path: 'modules/custom/custom_migration/data/products.csv'
  # Column delimiter. Comma (,) by default.
  delimiter: ','
  # Field enclosure. Double quotation marks (") by default.
  enclosure: '"'
  # The row to be used as the CSV header (indexed from 0),
  # or null if there is no header row.
  header_offset: 0
  # The column(s) to use as a key. Each column specified will
  # create an index in the migration table and too many columns
  # may throw an index size error.
  ids:
    - code
  # Here we identify the columns of interest in the source file.
  # Each numeric key is the 0-based index of the column.
  # For each column, the key below is the field name assigned to
  # the data on import, to be used in field mappings below.
  # The label value is a user-friendly string for display by the
  # migration UI.
  fields:
    0:
      name: cat
      label: 'Categoria'
    1:
      name: code
      label: 'Product code'
    2:
      name: title
      label: 'Title'
    3:
      name: image
      label: 'Image'

process:
  field_cat:
    plugin: entity_lookup
    source: division
    value_key: name
    bundle_key: vid
    bundle: cat_producto
    entity_type: taxonomy_term
    ignore_case: true
  field_producto_codigo: code
  title:
    -
      source: title
      plugin: callback
      callable: mb_strtolower
    -
      plugin: callback
      callable: ucfirst
  type:
    plugin: default_value
    default_value: producto
  field_imagen/target_id:
    plugin: entity_lookup
    value_key: name
    source: image
    bundle_key: bundle
    bundle: image
    entity_type: media
    ignore_case: 1
    access_check: 0

destination:
  plugin: entity:node

migration_dependencies:
  optional:
    - migrate_files
    - media_images

A destacar:

  • Para referenciar las entidades media usamos el plugin entity_lookup que busca por el nombre de la imagen.
  • Todo lo otro ya lo explicamos en el post anterior.

Indicamos estos ficheros en el yml del módulo:

config_devel:
  install:
    - migrate_plus.migration.products
    - migrate_plus.migration.migrate_files    
    - migrate_plus.migration.media_images

Y ya solo queda ejecutar unos comandos:

Primero decirle a Drupal que tenga en cuenta los ymls de importación creados.

drush cdi custom_migrate

Y luego las importaciones de cada uno de los ficheros:

drush mim migrate_files
drush mim media_images
drush mim products

Y con todo esto ya tendríamos lista la importación de imágenes a entidades media y luego creado los nodos que contienen estos campos media.

by Carlos Rincón
Programador Drupal Senior con experiencia en SEO, SEM, analíticas web, aplicaciones móbil y usabilidad.