Archivo de la categoría: Drupal

Drupal 7: Quitar la descripción “E.g., 19/11/2013″ del field date_popup

El FAPI date_popup (del módulo Date) tiene un poco de mala leche y no se deja quitar la descripción por la vía tradicional. Dicho lo anterior, acá está el hook que nos salva el día:

<?php
/**
 * Implements hook_date_popup_process_alter().
 */
function MODULO_O_THEME_date_popup_process_alter(&$element, &$form_state, $context) {
  unset($element['date']['#description']);
  unset($element['time']['#description']);
}

Chau!

Druplicon

Drupal 7: Integrar Inline entity form perfectamente en el formulario en el que está anidado.

Cuando Inline entity form renderiza la entidad dentro de otro formulario tiende a meterle fieldsets y títulos extra que no son estrictamente parte del formulario de la entidad referida. Como resultado, el formulario anidado queda como tal, como si estuviera anidado, y eso no queda bonito. Estaria mejor que el formulario visualmente se integrase mejor al formulario del nodo al que está referido.

Para lograr esto agarra tu módulo o theme (template.php) y pon esto:

<?php
function TUMODULO_o_THEME_field_widget_inline_entity_form_single_form_alter(&$entity_form, &$form_state) {
  $entity_form['#type'] = 'container';
  $entity_form['form']['product_details']['#type'] = 'container';
  unset(
  $entity_form['#title'],
  $entity_form['#tree'],
  $entity_form['#description'],
  $entity_form['#prefix'],
  $entity_form['#suffix']
  );
}
?>

Básicamente se deshace de los fieldset que aíslan al formulario de la entidad referida del formulario que lo contiene.

El antes y el después:
Entity inline clean
Chau!

Druplicon

Drupal 7: Renderizar una pagina de panels programáticamente

Si tenemos un panel que tiene su propia URL, digamos contactar/!extras y querés renderizar este panel por medio de código. Bueno si ese fuera el caso, hacerlo es bastante simple:

<?php
  module_load_include('inc', 'ctools', 'page_manager/plugins/tasks/page');
  return page_manager_page_execute("contacto", "foo");
?>

En el snippet anterior solo aclarar que page_manager_page_execute([[1]], [[2,3,4,etc]]);

[[1]] este es el nombre del panel sin el prefijo “page-“. Para saber cual es el nombre del panel que quieres, podes ir a admin/structure/pages

[[2,3,4,etc]] este argumento machea con !extras y cualquier otro argumento extra va a ir macheando con el siguiente argumento de la URL.

Chau!

Druplicon

Combo: Importar y sincronizar productos con Migrate + Drupal commerce + product display desde CSV/XML/JSON

Hola!. Todos los post que escribí hasta ahora sobre Migrate se debieron a que quería hacer esto.

Hoy les presento una clase basada en Migrate para importar productos a Drupal Commerce y crear al mismo tiempo un product display.
NOTA: los múltiples productos para un solo product display lo dejamos para otro día :(

Un poco de intro

  • Necesitamos el modulo Commerce Migrate para poder crear productos con Migrate.
  • El machine name del tipo de producto que vamos a importar se llama “gorras
  • El machine name del product display que vamos a importar se llama “display_gorra
  • Indistintamente del formato a importar (xml, json, csv) existen dos clases linkeadas: la que crea el producto y la que crea el porduct display y asigna el producto creado.
  • La fuente de información para cada una de las clases es la misma siempre, solo que cada clase toma lo que necesita.
  • El linkeo entre clases se hace referenciando el machine name que usaste para registrar la clase (machine-name != class name). Si no lo tenés claro lee este post

Bueno vamos a hacer una cosa, en lugar de repetir las dos clases necesarias para la importación por cada tipo de source, voy a dejar aqui solo la que importa CSV, porque el resto de las clases funcionan practicamente igual, y seria repetir en vano.

LAS CLASES

<?php
/**
 * Class ProductFromCSV
 * Crea el producto
 * machine-name: product-from-csv
 */
class ProductFromCSV extends Migration {
  public function __construct($arguments) {
    parent::__construct($arguments);
    $csv_path = "/path/hasta/el/csv/exported.csv";
    $columns = array(
      0 => array('identificador', 'Identificador'),
      1 => array('titulo', 'Post date'),
      2 => array('precio', 'Precio'),
      3 => array('moneda', 'Moneda'),
      4 => array('categoria', 'Categoría'),
    );
    $this->source = new MigrateSourceCSV($csv_path, $columns, array('embedded_newlines' => TRUE, "track_changes" => TRUE));
    $this->destination = new MigrateDestinationEntityAPI('commerce_product', 'gorras');
    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'identificador' => array(
          'type' => 'varchar',
          'length' => 20,
          'not null' => TRUE,
        ),
      ),
      $this->destination->getKeySchema()
    );
    global $user;
    $this->addFieldMapping('title', 'titulo');
    $this->addFieldMapping('commerce_price', 'precio')->defaultValue(0);
    $this->addFieldMapping('commerce_price:currency_code', 'moneda')->defaultValue(commerce_default_currency());
    $this->addFieldMapping('sku', 'identificador');
    $this->addFieldMapping('status')->defaultValue(1);
    $this->addFieldMapping('uid')->defaultValue($user->uid);
    $this->addFieldMapping('field_categoria', 'categoria')->defaultValue("General");
  }
}
/**
 * Class ProductDisplayFromCSV
 * Crea product displays (de momento solo pueden crear relaciones 1-1)
 * machine-name: product-display-from-csv
 */
class ProductDisplayFromCSV extends Migration {
  public function __construct($arguments) {
    parent::__construct($arguments);
    $csv_path = "/path/hasta/el/csv/exported.csv";
    //Suponiendo que la clase la has registrado bajo este nombre :D
    $product_machine_name = 'product-from-csv';
    $columns = array(
      0 => array('identificador', 'Identificador'),
      1 => array('titulo', 'Post date'),
    );
    $this->source = new MigrateSourceCSV($csv_path, $columns, array('embedded_newlines' => TRUE, "track_changes" => TRUE));
    $this->dependencies = array($product_machine_name);
    $this->destination = new MigrateDestinationNode('display_gorra');
    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'identificador' => array(
          'type' => 'varchar',
          'length' => 20,
          'not null' => TRUE,
        ),
      ),
      $this->destination->getKeySchema()
    );
    global $user;
    $this->addFieldMapping('uid')->defaultValue($user->uid);
    $this->addFieldMapping('title', 'titulo');
    $this->addFieldMapping('field_product', "identificador")->sourceMigration($product_machine_name);
  }
}

Lo importante aquí es notar que la segunda clase “dice” que depende de la primera al indicar $this->dependencies = array($product_machine_name);. De esta forma, Migrate sabe que la segunda clase no se puede poner a importar hasta que el primer migrador haya sido procesado.
Y para establecer la relación con el producto creado asociamos el field “field_product” al identificador que a su vez lo obtiene de la relación que Migrate estableció en una tabla en la DB (relación identificador del CSV < -> commerce product ID) por medio de ->sourceMigration()

Uso:

La verdad que fui un iluso cuando pensé que por estar linkeadas, las clases se iban a ejecutar en cadena :S La realidad es que siguen siendo migrators independientes con la salvedad de que hay que ejecutarlos en orden:

drush mi product-from-csv

Y recién ahora podemos invocar al migrator que crea los product display:

drush mi product-display-from-csv

That’s all.
Chau!

Druplicon

Drupal: Obtener un árbol de términos de forma anidada

Este problema es mas viejo que la injusticia. taxonomy_get_tree() devuelve un array de terms plano en lugar de hacerlo de forma anidada. La solución es esta pequeña función:

<?php
function taxonomy_get_nested_tree($vid_or_terms = array(), $max_depth = NULL, $parent = 0, $parents_index = array(), $depth = 0) {
  if (!is_array($vid_or_terms)) {
    $vid_or_terms = taxonomy_get_tree($vid_or_terms);
  }
  foreach ($vid_or_terms as $term) {
    foreach ($term->parents as $term_parent) {
      if ($term_parent == $parent) {
        $return[$term->tid] = $term;
      }
      else {
        $parents_index[$term_parent][$term->tid] = $term;
      }
    }
  }
  foreach ($return as &$term) {
    if (isset($parents_index[$term->tid]) && (is_null($max_depth) || $depth < $max_depth)) {
      $term->children = taxonomy_get_nested_tree($parents_index[$term->tid], $max_depth, $term->tid, $parents_index, $depth + 1);
    }
  }
  return $return;
}

Y listo, ahora en lugar de usar taxonomy_get_tree(), usa taxonomy_get_nested_tree():

<?php
$voc = taxonomy_vocabulary_machine_name_load('categoria');
$tree = taxonomy_get_nested_tree($voc->vid);

Ya está, el resultado ahora está anidado tal como tengas anidados los terms.

Nota: Me he basado en ésta función, que a su vez se basa en otras.

Renderizar el tree

Acá dejo tambien una función para renderizar el tree:

<?php
function taxonomy_nested_tree_render($tree, $recurring = FALSE) {
  $items = array();
  if (count($tree)) {
    foreach ($tree as $term) {
      $path = taxonomy_term_uri($term);
      $item = array('data' => l($term->name, $path["path"]));
      if (isset($term->children)) {
        $item["children"] = taxonomy_nested_tree_render($term->children, TRUE);
      }
      $items[] = $item;
    }
  }
  if ($recurring) {
    return $items;
  }
  return array(
    '#theme' => 'item_list',
    '#items' => $items,
    '#attributes' => array('class' => 'taxonomy-tree'),
  );
}

Uso:

<?php
$voc = taxonomy_vocabulary_machine_name_load('categoria');
$tree = taxonomy_get_nested_tree($voc->vid);
return taxonomy_nested_tree_render($tree);
Druplicon

Drupal: Importar y sincronizar desde un JSON local o remoto con Migrate

Con archivos JSON tenemos dos posibles casos:

  1. Un JSON simple
  2. Y proporcionando dos archivos: uno que provea los ID y otro archivo que contenga la información de un item específico. Esta última opcion es especialmente util para atacar API’s que por ejemplo en /posts entrega un listado de ID’s y en /posts/:id proporciona un item especifico.

Primera forma: un JSON simple

Digamos que nuestro archivo json tiene esta pinta:

[
    {
        "post_id": "1000",
        "title": "Post de blog 1",
        "content": "<p>Este es el contenido del post 1</p>",
        "createdDate": "2013-06-15T23:39:27.433Z"
    },
    {
        "post_id": "1001",
        "title": "Post de blog 2",
        "content": "<p>Este es el contenido del post 2</p>",
        "createdDate": "2013-06-16T23:39:27.433Z"
    },
    {
        "post_id": "1002",
        "title": "Post de blog 3",
        "content": "<p>Este es el contenido del post 3</p>",
        "createdDate": "2013-06-17T23:39:27.433Z"
    },
    {
        "post_id": "1003",
        "title": "Post de blog 4",
        "content": "<p>Este es el contenido del post 4</p>",
        "createdDate": "2013-06-18T23:39:27.433Z"
    }
]

Como se ve no es mas que un array de objetos. De hecho hacer un migrator que lo procese no difiere mucho de lo que hicimos con los XML, CSV y DB’s:

<?php
class MigrationFromJSON extends Migration {
  public function __construct($arguments) {
    parent::__construct($arguments);
    $module_path = DRUPAL_ROOT . '/' . drupal_get_path('module', 'mimodulo');
    $json_file = $module_path . '/json/posts.json';
    $this->source = new MigrateSourceJSON($json_file, 'post_id', array(), array("track_changes" => TRUE));
    $this->destination = new MigrateDestinationNode('page');
    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'post_id' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
        ),
      ),
      MigrateDestinationNode::getKeySchema()
    );
    $this->addFieldMapping('uid')->defaultValue(1);
    $this->addFieldMapping('title', 'title');
    $this->addFieldMapping('body', 'content');
    $this->addFieldMapping('created', 'createdDate');
  }
}

Lo único que cambia en este migrator es obviamente el $this->source (MigrateSourceJSON). El resto se mantiene como en las anteriores implementaciones.

Segunda forma: API style

Para este ejemplo he colgado las siguientes URL:

Obtener el listado de post a importar:
http://ecapy.com/Archivos/API-style/ids.json

Posts individuales:

Mientras este blog exista ahí las voy a dejar para que hagamos pruebas de este tipo de llamadas cuando haga falta.

Bien, entonces tenemos este caso en el que en una URL recibimos todos los ID de los post que queremos importar, y luego tenemos que ir llamando uno a uno los archivos JSON con el contenido de estos.

<?php
class MigrationFromJSON_API_Style extends Migration {
  public function __construct($arguments) {
    parent::__construct($arguments);
    $list_url = 'http://ecapy.com/Archivos/API-style/ids.json';
    $item_url = 'http://ecapy.com/Archivos/API-style/posts/:id.json';
    $this->source = new MigrateSourceList(
      new MigrateListJSON($list_url),
      new MigrateItemJSON($item_url, array()),
      array(),
      array("track_changes" => TRUE)
    );
    $this->destination = new MigrateDestinationNode('page');
    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'id' => array(
          'type' => 'varchar',
          'length' => 20,
          'not null' => TRUE,
        ),
      ),
      MigrateDestinationNode::getKeySchema()
    );
    $this->addFieldMapping('uid')->defaultValue(1);
    $this->addFieldMapping('title', 'title');
    $this->addFieldMapping('body', 'content');
  }
}

La gran diferencia con respecto a todos los migrators que hemos creado hasta ahora está en $this>source:

<?php
$this->source = new MigrateSourceList(
      new MigrateListJSON($list_url),
      new MigrateItemJSON($item_url, array()),
      array(),
      array("track_changes" => TRUE)
    );

Estamos usando MigrateSourceList() que está justamente indicada para este tipo de casos. Como parametros estan bastante claros asi que no los explico.
Lo unico que si que quiero que presten atención es en $item_url. En este string hay un token (:id) y es porque el migrador a medida que vaya iterando los id que haya encontrado, va a generar la url desde la que se supone que tiene que obtener el contenido, y va a aplicar el ID sobre esa URL base que le hemos dado usando el token :id como placeholder.

La importación de JSON tiene mucho juego. Pueden ver como hacer cosas mas locas en https://drupal.org/node/1152160.

Chau!

Druplicon

Drupal: Importar y sincronizar desde un CSV con Migrate

Hoy nos toca importar información desde archivos CSV. Como la implementacion de las clases no varian mucho con respecto a la de BD y XML, voy a obviar la explicacion de algunas partes y me voy a centrar en el “source” de la clase.

Los archivos CSV de ejemplo están al final del post.

Para mostrar las posibles configuraciones que lleva un CSV, he creado 3 ejemplos, y en cada uno atendemos algún posible caso a la hora de importar CSV’s:

El ejemplo más básico: un CSV sin headers y de múltiples líneas por fila

<?php
class WPMigrationFromCSV extends Migration {
  public function __construct($arguments) {
    parent::__construct($arguments);
    $module_path = DRUPAL_ROOT . '/' . drupal_get_path('module', 'mimodulo');
    $csv_path = $module_path . 'wp_posts-normal.csv';
    $columns = array(
      0 => array('id', 'Post ID'),
      2 => array('post_date', 'Post date'),
      4 => array('post_content', 'Post content'),
      5 => array('post_title', 'Post title'),
    );
    $this->source = new MigrateSourceCSV($csv_path, $columns, array('embedded_newlines' => TRUE));
    $this->destination = new MigrateDestinationNode('page');
    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'id' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
        ),
      ),
      MigrateDestinationNode::getKeySchema()
    );
    $this->addFieldMapping('uid')->defaultValue(1);
    $this->addFieldMapping('created', 'post_date');
    $this->addFieldMapping('title', 'post_title');
    $this->addFieldMapping('body', 'post_content');
  }
}

Resumiendo el ejemplo anterior:

  • La importación de CSV extienden siempre de Migration().
  • $this->source hace uso de la clase MigrateSourceCSV() que se nutre de:
    • $csv_path que es el path hasta el archivo.
    • $columns que es un array que indica en qué posición de la fila se encuentra el item, el nombre maquina que queremos usar para ese item, y un label (Util para UI’s). Noten que el key de cada elemento del array no es secuencial. Esto es porque en el CSV no se encuentran uno detrás del otro los elementos que queremos importar. vean el CSV de ejemplo si no le ven sentido :)
    • El tercer parámetro de la clase es el array de opciones. en nuestro ejemplo usamos “embedded_newlines” ya que el CSV de ejemplo tiene items que estan repartidos en varias líneas. Si van a importar un CSV cuyas filas no tienen varias líneas, pueden obviar este parámetro.
  • El resto de la clase mantiene los mismos principios explicados en el articulo de introduccion

CSV con headers

Cuando el CSV a importar trae headers podemos usarlo directamente en lugar de mapear los campos.

<?php
class WPMigrationFromCSVconHeaders extends Migration {
  public function __construct($arguments) {
    parent::__construct($arguments);
    $module_path = DRUPAL_ROOT . '/' . drupal_get_path('module', 'mimodulo');
    $csv_path = $module_path . 'wp_posts-con-cabeceras.csv';
    $this->source = new MigrateSourceCSV($csv_path, array(), array('embedded_newlines' => TRUE, 'header_rows' => 1));
    $this->destination = new MigrateDestinationNode('page');
    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'ID' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
        ),
      ),
      MigrateDestinationNode::getKeySchema()
    );
    $this->addFieldMapping('uid')->defaultValue(1);
    $this->addFieldMapping('created', 'post_date');
    $this->addFieldMapping('title', 'post_title');
    $this->addFieldMapping('body', 'post_content');
  }
}

Fijense que en lugar de hacer un array mapeando los campos con su respectiva posición en el CSV, hemos pasado un array vacío, y en las opciones de MigrateSourceCSV() le hemos pasado “header_rows” para indicarle que la cabecera del CSV es hasta la línea 1 (lo que haría que lea solo la primer línea como header).
En este ejemplo, resulta que casi todas las columnas del CSV se llaman igual que el mapeo del ejemplo anterior, salvo el “id” que en el CSV viene representado como “ID”.

CSV con delimitadores cambiados

Hay veces que el CSV tiene delimitadores distintos a los normales. Por ejemplo, para separar los item de una fila en lugar de usar , (coma), puede haber un ; (punto y coma) o para encerrar el contenido de un ítem en lugar de ” (comillas dobles) se usa ‘ (comillas simples).
para ese tipo de casos podemos especificar a MigrateSourceCSV() estos separadores:

<?php
class WPMigrationFromCSVdistintosDelimiters extends Migration {
  public function __construct($arguments) {
    parent::__construct($arguments);
    $module_path = DRUPAL_ROOT . '/' . drupal_get_path('module', 'mimodulo');
    $csv_path = $module_path . 'wp_posts-con-delimitadores.csv';
    $columns = array(
      0 => array('id', 'Post ID'),
      2 => array('post_date', 'Post date'),
      4 => array('post_content', 'Post content'),
      5 => array('post_title', 'Post title'),
    );
    $this->source = new MigrateSourceCSV($csv_path, $columns, array(
      'embedded_newlines' => TRUE,
      'length' => NULL,
      'delimiter' => ';',
      'enclosure' => "'",
      'escape' => '|',
    ));
    $this->destination = new MigrateDestinationNode('page');
    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'id' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
        ),
      ),
      MigrateDestinationNode::getKeySchema()
    );
    $this->addFieldMapping('uid')->defaultValue(1);
    $this->addFieldMapping('created', 'post_date');
    $this->addFieldMapping('title', 'post_title');
    $this->addFieldMapping('body', 'post_content');
  }
}

‘length’, ‘delimiter’, ‘enclosure’, y ‘escape’ van directamente a parar a fgetcsv.

Archivos de ejemplo:
wp_posts-con-cabeceras.csv

wp_posts-con-delimitadores.csv

wp_posts-normal.csv

wp_posts-sin-saltos-de-linea.csv

Druplicon

Drupal: Importar y sincronizar desde un XML local o remoto con Migrate

Continuando con Migrate, hoy toca importar XML’s de forma local y remota. Para la forma remota he colocado un XML de ejemplo con un solo item que pueden usar para probar que todo funcione.

Ok sin más dilación esta es la clase para importar de forma remota:

<?php
class WPMigrationFromXML extends XMLMigration {
  public function __construct($arguments) {
    parent::__construct($arguments);
    $items_url = 'http://ecapy.com/wp-content/uploads/2013/06/wordpress.xml_.txt';
    $item_xpath = '/rss/channel/item';
    $item_ID_xpath = 'wp:post_id';
    $this->source = new MigrateSourceXML($items_url, $item_xpath, $item_ID_xpath);
    $this->destination = new MigrateDestinationNode('page');
    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'wp:post_id' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
        )
      ),
      MigrateDestinationNode::getKeySchema()
    );
    $this->addFieldMapping('uid')->defaultValue(1);
    $this->addFieldMapping('created', 'wp:post_date')->xpath('wp:post_date');
    $this->addFieldMapping('title', 'title')->xpath('title');
    $this->addFieldMapping('body', 'content:encoded')->xpath('content:encoded');
  }
}

La primer diferencia y que es única para la importación de XML es que no extiende de la clase Migration como vimos en la importación desde DB’s, sino que lo hace de XMLMigration() ya que los XML son un poco especiales a la hora de tratarlos.

Otra cosa que cambia obviamente es el source, que esta vez es MigrateSourceXML() y que requiere 3 cosas como mínimo: la ubicación del archivo ya sea local o remota la ruta, el path que representa un item y por ultimo el identificador único del item. Si revisan el xml que adjunté van a poder verle mas sentido.

Y la última cosa que es única de los XML, es el mapeo de los fields. Pueden ver que MigrateSourceXML() añade un método ->xpath() para que le digamos de donde sacar la información.

Y para la importación local solo hay que cambiar $items_url por algo como:

<? $items_url = DRUPAL_ROOT . '/' . drupal_get_path('module', 'mimodulo') . '/sources/wordpress.xml';

Chau!

Druplicon

Drupal: Importar y sincronizar desde una DB con Migrate

En el post anterior vimos por encima como implementar un migrator, que no es mas que una clase que extiende de Migration. La clase Migration nos quita prácticamente todo el trabajo de encima (en lo que se refiere a procesar la importación y creación de contenidos). Lo único que se necesita para crear un migrator es implementar el método __construct() y decirle donde esta la info que entra, que hace con esa info y la relación de cada fila de un item entrante con respecto a la fila de una entidad.

Dicho lo anterior, el ejemplo de uso para migrar desde DB externas sería:

<?php
class WPMigrationFromDB extends Migration {
  public function __construct($arguments) {
    // 1
    parent::__construct($arguments);
    // 2
    Database::addConnectionInfo('temporal', 'MI_DB', array(
      'driver' => 'mysql',
      'database' => 'MI_DB',
      'username' => 'USUARIO',
      'password' => 'CLAVE',
      'host' => 'localhost',
    ));
    // 3
    $query = Database::getConnection('MI_DB', 'temporal')
      ->select('wp_posts', 'posts')
      ->fields('posts', array(
        'id',
        'post_title',
        'post_content',
      ))
      ->condition("post_type", "post", "=")
      ->condition("post_status", "publish", "=");
    // 4
    $this->source = new MigrateSourceSQL($query);
    // 5
    $this->destination = new MigrateDestinationNode('page');
    // 6
    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'id' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
        )
      ),
      MigrateDestinationNode::getKeySchema()
    );
    // 7
    $this->addFieldMapping('uid')->defaultValue(1);
    $this->addFieldMapping('title', 'post_title');
    $this->addFieldMapping('body', 'post_content');
  }
}

Enumeré cada uno de los puntos mas importantes:

  1. Dejamos que Migration ejecute su constructor
  2. Conectamos con una DB externa
  3. Preparamos la query que va a devolvernos solo las columnas que necesitamos.
  4. Pasamos la query a $this->source para que sepa desde donde extraer la información.
  5. Especificamos el destino.
  6. Establecemos el mapeo entre los campos extraídos de la DB y los field de la entidad que se va a crear.

Y eso es todo lo que necesita una implementación de Migration para procesar la información desde una DB. como siempre, esto es solo un ejemplo rapido para que sepan por donde arrancar, pero si realmente quieren conocer mas sobre implementar un migrator usando de fuente una DB lean https://drupal.org/node/1152150

Chau!

Druplicon

Drupal: Migrate

Migrate es genial, a resumidas cuentas te permite importar información desde varias fuentes. Así de simple.
En este post voy tocar dos cosas muy por encima. Qué puede importar y como implementar Migrate (este modulo no importa nada por si mismo)

Primero lo primero:

¿Desde donde te deja importar información?

  • SQL (Cualquier DB que Drupal sea capaz de leer)
  • XML
  • List
  • CSV
  • JSON
  • MSSQL (Microsoft SQL Server)
  • Oracle
  • Multi items

Y esas son las fuentes que provee Migrate, pero cualquier módulo puede añadir nuevas fuentes sin mayor problema. pueden ver en detalle cada una de las clases que implementa los sources en https://drupal.org/node/1006986

¿Que puede crear con la información que importa?

  • Nodos
  • Comentarios
  • Archivos
  • Menus
  • Items de menu
  • Roles
  • Tablas en la DB
  • Terminos
  • Usuarios

Y una vez mas esas son las fuentes que provee Migrate, pero cualquier módulo puede añadir nuevos destinos. por ejemplo algun modulo puede proveer la implementación para crear profiles de Profile2. La información en detalle de cada una de las clases que implementan estos “destinos” la pueden ver en https://drupal.org/node/1006988

Ok ahora vamos a implementar Migrate en un módulo. Para hacerlo no puede ser más fácil. Solo necesitas 2 cosas: un hook y extender de una clase
Nuestro módulo de ejemplo se llama “migrate_posts” que va a copiar todos los posts que hay en mi blog (WordPress) usando como fuente la DB (El source es SQL en este caso)

La onda en la siguiente:
Primero tenemos que crear un archivo migrate_posts.migrate.inc y dentro de él definir el hook_migrate_api() que es el que le da la información necesaria a Migrate sobre nuestra implementación:

<?php
/**
 * Implements hook_migrate_api().
 */
function migrate_posts_migrate_api() {
  $api = array(
    'api' => 2,
    'groups' => array(
      'wordpress' => array(
        'title' => t('Importación de posts desde WordPress'),
      ),
    ),
    'migrations' => array(
      'WPFromDB' => array(
        'class_name' => 'WPMigrationFromDB',
        'group_name' => 'wordpress',
      ),
    ),
  );
  return $api;
}

El HOOK necesita 3 cosas:
Que le digas que API vas a usar (Siempre es la 2 salvo que cuando leas este artículo hayan sacado la versión 3.x del módulo). Que le indiques un grupo y por ultimo que le indiques cual es tu migrator

Desglosemos esto:
Un grupo es simplemente un “tag” donde vas a agrupar todos los migrators que vos créas conveniente. Por ejemplo, si quisieras crear un importador de posts y otro importador a parte para los comentarios, entonces meterías los dos migrators dentro del grupo de “wordpress”.
En “migrations” definimos el nombre máquina de nuestro importador (WPFromDB), el nombre de la clase que va a realizar la acción (WPMigrationFromDB) y el grupo al que pertenece.

Lo último que queda por aclarar es que “migrate_posts.migrate.inc” es la convención que usa Migrate, por lo que no hace falta hacer un require/include en tu .module

Ya hemos terminado con el primer paso, vamos al segundo. La clase de importacion.
Creamos un archivo .inc que es el que va a contener la clase. por ejemplo “posts-migrator-from-db.inc” y en el .info de tu modulo lo agregas (files[] = posts-migrator-from-db.inc)
Y dentro de él ponemos la clase:

<?php
class WPMigrationFromDB extends Migration {
  public function __construct($arguments) {
    parent::__construct($arguments);
    // #############################################
    // Definición del source (desde donde vamos a obtener la información)
    Database::addConnectionInfo('temporal', 'MI_DB', array(
      'driver' => 'mysql',
      'database' => 'MI_DB',
      'username' => 'USUARIO',
      'password' => 'CONTRASEÑA',
      'host' => 'localhost',
    ));
    $query = Database::getConnection('MI_DB', 'temporal')
      ->select('wp_posts', 'posts')
      ->fields('posts', array(
        'id',
        'post_date',
        'post_modified',
        'post_title',
        'post_content',
      ))
      ->condition("post_type", "post", "=")
      ->condition("post_status", "publish", "=");
    $this->source = new MigrateSourceSQL($query);
    // #############################################
    // Definición del destino (lo que se va a crear con los datos conseguidos)
    $this->destination = new MigrateDestinationNode('page');
    // #############################################
    // Indicamos cual es el identificador UNICO de cada elemento que se consiga desde la fuente
    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'id' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
        )
      ),
      MigrateDestinationNode::getKeySchema()
    );
    // #############################################
    // Mapeo de los valores del origen y destino
    $this->addFieldMapping('uid')->defaultValue(1);
    $this->addFieldMapping('created', 'post_date');
    $this->addFieldMapping('changed', 'post_modified');
    $this->addFieldMapping('title', 'post_title');
    $this->addFieldMapping('body', 'post_content');
  }
}

No voy a profundizar en la implementación de la clase porque este post es solo para demostrar lo fácil que es implementar Migrate. Ya vendrán post hablando de las clases.

Y básicamente te eso es todo lo que necesitas para implementar Migrate.

Bueno, ahora usamos drush
Limpiamos el cache para que Drupal lea el .info de nuestro módulo y sepa que “posts-migrator-from-db.inc” existe

drush cc all

Registramos nuestro migrator:

drush migrate-register

Revisamos que el migrator se haya registrado:

drush migrate-status

Que deberia devolver:

Group: wordpress  Total  Imported  Unprocessed  Status  Last imported
 WPFromDB          169    0         169          Idle    2013-06-12 17:59:17

Y si todo está ok, podemos importar nuestros posts:

drush migrate-import WPFromDB

Y para deshacer la importación:

drush migrate-rollback WPFromDB

Chau!