drupal_migra Drupal 6 nos provee de una API que nos permitirá trabajar directamente con el core, usando las funciones, módulos y configuraciones ya existentes. De esta forma, y gracias a los hooks, podemos realizar distintas funciones tales como agregar, eliminar y modificar usuarios y contenidos, manejar los comentarios, configuraciones, etc.
La idea de este artículo es ayudar a las personas que necesiten realizar una migración (importación/exportación) desde un sistema distinto hacia Drupal.

Las funciones que necesitamos de la API de Drupal son:

  1. user_save(): Permite la manipulación de los usuarios.
  2. node_save(): Manipulación de los nodos (contenidos).
  3. db_query(): Nos permite realizar consultas a la base de datos usando la configuración de Drupal.

Con estas funciones ya podemos pensar en realizar una migración total o partial de un sitio. La lógica del asunto es sencilla, tenemos que hacer un “wrapper” que conecte ambas plataformas (hablando de base de datos y ficheros), hacer que por un lado se conecte a la base de datos de la plataforma antigua y por otro lado a Drupal, que vaya leyendo los datos desde la plataforma antigua y luego insertandolos en la nueva plataforma usando las funciones de la API, por ejemplo, si tenemos la tabla usuarios con los tipicos campos nombre, email, usuario, password, en Drupal la estructura de la tabla {users} es similar, contiene los campos email, name y pass (los que nos sirven) entre otros. Entonces lo que debemos hacer es un pequeño script que haya un SELECT email, password, usuario FROM usuarios y hacer que cada registro que lea, lo inserte en la tabla {users} mediante la API (función user_save()).
Ejemplo:

  1. $var1 = mysql_query("SELECT email,password,usuario FROM usuarios", $link);
  2. while($data = mysql_fetch_array($var1)){
  3.    $userdata = array(
  4.                         ‘status’ => 1,
  5.                         ‘name’ => $data[‘usuario’],
  6.                         ‘pass’ => ‘123’,
  7.                         ‘picture’ => $_avatar,
  8.                         ‘mail’ => $data[‘email’],
  9.                 );
  10.                 user_save(, $userdata);
  11. }

El tema de la contraseña es más complicado, va a depender únicamente de que si la contraseña está en texto plano o encriptada en la base de datos de origen. Si esta en texto plano, entonces basta con agregar al arreglo userdata el valor $data[‘password’] a la variable pass. De lo contrario, tendremos que hacer un pequeño truco. Creamos el usuario asignandole una clave fake, por ejemplo 123456 y posteriormente, usando la función db_query, hacemos el UPDATE necesario para cambiarle el password al usuario creado, ingresando directamente el HASH original (pensando que esta en md5).

Para realizar la migración de contenido, el procedimiento es de la misma forma, seleccionar la información de un lado y llevarla a otra usando la función node_save.
Si algo se nos escapa de las manos y no sabemos como hacerlo, basta con que entendamos el modelo de la base de datos e ingresemos directamente las consultas a la base de datos (no lo recomiendo).

Hace unos días me tocó la tarea de realizar la migración de una plataforma X a Drupal, para esto diseñe una clase que me permite hacer la migración paso por paso.

  1. < ?php
  2.  
  3. chdir("api/");
  4. include_once ‘api/includes/bootstrap.inc’;
  5. drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
  6.  
  7. class Drupal{
  8.         private $user;
  9.         private $id_uid;
  10.  
  11.         public $counter;
  12.  
  13.         public function __construct(){
  14.                 $this->id_uid = array();
  15.                 $this->counter = array(‘users’ => 0, ‘news’ => 0, ‘comments’ => 0);
  16.                 $this->clearDatabase();
  17.         }
  18.         public function clearDatabase(){
  19.                 $_queries = array(
  20.                         ‘users’ => ‘DELETE FROM dp_users WHERE name != \’admin\’ AND name != \’asdf\’,
  21.                         ‘nodes’ => ‘DELETE FROM dp_node WHERE type NOT IN (\’intro\’, \’adminpage\’)’,
  22.                         ‘comments’ => ‘DELETE FROM dp_comments’,
  23. //                      ‘terms_rel’ => ‘DELETE FROM dp_term_node’,
  24. //                      ‘terms’ => ‘DELETE FROM dp_term_data WHERE tid NOT IN (3, 6)’,
  25.                         ‘groups’ => ‘DELETE FROM dp_og’,
  26.                         ‘groups_uid’ => ‘DELETE FROM dp_og_uid’,
  27.                         ‘groups_ancestry’ => ‘DELETE FROM dp_og_ancestry’,
  28.                         ‘groups_notifications’ => ‘DELETE FROM dp_og_notifications’,
  29.                         ‘dp_job_posting’ => ‘DELETE FROM dp_job_posting’,
  30.                         ‘dp_job_posting_country’ => ‘DELETE FROM dp_job_posting_country’,
  31.                 );
  32.                 foreach($_queries as $query){
  33.                         print ">>[DP_SQL] ".$query."\n";
  34.                         db_query($query);
  35.                 }
  36.         }
  37.         public function createUser($data){
  38.                 $_avatar_base_path = "sites/default/files/pictures/";
  39.                 $_avatar = ($data[‘avatar’] == "usuarioSinAV.jpg")?NULL:$_avatar_base_path.$data[‘avatar’];
  40.                 $_user = array(
  41.                         ‘status’ => 1,
  42.                         ‘access’ => time(),
  43.                         ‘name’ => $data[‘username’],
  44.                         ‘pass’ => ‘123’,
  45.                         ‘picture’ => $_avatar,
  46.                         ‘profile_nombres’ => $data[‘nombres’],
  47.                         ‘profile_apellido’ => $data[‘apellidos’],
  48.                         ‘profile_intereses’ => $data[‘about’],
  49.                         ‘profesion’ => $data[‘profesion’],
  50.                         ‘mail’ => $data[‘email’],
  51.                 );
  52.                 $user = user_save(, $_user);
  53.                 self::_setPassword($user->uid, $data[‘password’]);
  54. //              self::_setUserRole($user->uid);
  55.                 self::_configureProfile($data, $user->uid);
  56.                 if($data[‘admin’]) self::_setAdmin($user->uid);
  57.                 $this->user = &$user;
  58.                 $this->id_uid[$data[‘id’]] = $user->uid;
  59.                 $this->counter[‘users’]++;
  60.         }
  61.         public function createContent($data, $type){
  62.                 $node = new stdClass();
  63.                 $node->type = $type;
  64.                 $node->uid = $this->user->uid;
  65.                 $node->name = $user->user->name;
  66.                 $node->promote = 1;
  67.                 $node->format = FILTER_FORMAT_DEFAULT;
  68.                 $node->status = 1;
  69.                 $node->title = html_entity_decode($data[‘titulo’]);
  70.                 $node->body = $data[‘cuerpo’];
  71.                 $node->taxonomy = self::_convertTaxonomy();
  72.                 $node->comment = 2;
  73.                 $node->created = strtotime($data[‘fecha’]);
  74.                 print ">>[node:".$type."] ".substr($node->title, 0, 30)."… (".substr($node->body, 0, 20)." …) [tax:{".implode(";", $node->taxonomy)."},uid:".$node->uid."]\n";
  75.                 $this->counter[‘news’]++;
  76.                 node_save($node);
  77.                 $q = "INSERT INTO dp_node_access(nid, grant_view, realm) VALUES((SELECT MAX(nid) FROM dp_node),1, ‘all’)";
  78.                 db_query( $q );
  79.         }
  80.         public function insertCommentProfile($data){
  81.                 print ">>[comment;autor:".$this->id_uid[$data[‘autor’]].",uid:".$data[‘userid’]."] (".rtrim(substr($data[‘comentario’], 0, 30))." …)\n";
  82.                 db_query("INSERT INTO {comments} (pid, uid, hostname, comment, status, name, mail, homepage, timestamp) VALUES(%d, %d, ‘%s’, ‘%s’, %d, ‘%s’, ‘%s’, ‘%s’, %d)", $data[‘userid’], $this->id_uid[$data[‘autor’]], ‘127.0.0.1’, $data[‘comentario’], 1, , , , time());
  83.                 $this->counter[‘comments’]++;
  84.         }
  85.  
  86.         private function _convertTaxonomy($taxonomy = NULL){
  87.                 $_categories = array();
  88.                 $_categories[] = 29;
  89.                 $_categories[] = 30;
  90.                 $_categories[] = 31;
  91.                 $_categories[] = 32;
  92.                 $_categories[] = 33;
  93.                 $_categories[] = 34;
  94.                 $_categories[] = 35;
  95.                 return array( ($taxonomy)?$_categories[$taxonomy]:35 );
  96.         }
  97.         private function _setPassword($id, $md5){
  98.                 $query = "UPDATE dp_users SET pass = ‘".$md5."’ WHERE (name != ‘asdf’ AND name != ‘admin’) AND uid = ".$id;
  99.                 print ">>[DP_SQL] ".$query."\n";
  100.                 db_query($query);
  101.         }
  102.         private function _setAdmin($id){
  103.                 $table = "dp_users_roles";
  104.                 //$query = "UPDATE ".$table." SET rid = 4 WHERE uid = ".$id; # 4 es el que corresponde segun SELECT * FROM dp_role
  105.                 $query = "INSERT INTO ".$table."(rid, uid) VALUES(4, ".$id.")"; # 2 es el que corresponde segun SELECT * FROM dp_role
  106.                 print ">>[DP_SQL] ".$query."\n";
  107.                 db_query($query);
  108.         }
  109.         private function _setUserRole($id){
  110.                 $table = "dp_users_roles";
  111.                 $query = "INSERT INTO ".$table."(rid, uid) VALUES(2, ".$id.")"; # 2 es el que corresponde segun SELECT * FROM dp_role
  112.                 print ">>[DP_SQL] ".$query."\n";
  113.                 db_query($query);
  114.         }
  115.         private function _configureProfile($info, $_uid){
  116.                 $uid = $info[‘id’];
  117.                 $query = "SELECT a.nombre_esp FROM cat_paises a LEFT JOIN usr_users b ON b.pais = a.id WHERE b.id = ".$uid;
  118.                 $_r = mysql_result(sqlExec($query, ‘query’), 0, ‘nombre_esp’);
  119.                 db_query("INSERT INTO dp_profile_values(fid, uid, value) VALUES(8, ".$_uid.", ‘".$_r."’)");
  120.  
  121.                 $query = "SELECT a.nombre_esp FROM paises a LEFT JOIN users b ON b.pais_residencia = a.id WHERE b.id = ".$uid;
  122.                 $_r = mysql_result(sqlExec($query, ‘query’), 0, ‘nombre_esp’);
  123.                 db_query("INSERT INTO dp_profile_values(fid, uid, value) VALUES(9, ".$_uid.", ‘".$_r."’)");
  124.  
  125. /*                $query = "SELECT a.profesion FROM users a WHERE a.id = ".$uid;
  126.                 $_r = mysql_result(sqlExec($query, ‘query’), 0, ‘profesion’);
  127.                 db_query("INSERT INTO dp_profile_values(fid, uid, value) VALUES(3, ".$_uid.", ‘".$_r."’)");*/
  128.  
  129.                 $query = "SELECT a.nombre FROM cat_usr a LEFT JOIN usr_users b ON b.categoria = a.id WHERE b.id = ".$uid;
  130.                 $_r = mysql_result(sqlExec($query, ‘query’), 0, ‘nombre’);
  131.                 db_query("INSERT INTO dp_profile_values(fid, uid, value) VALUES(4, ".$_uid.", ‘".$_r."’)");
  132.  
  133.                 $query = "SELECT a.subcategoria_es FROM areas_ciencia_subcat a, usr_users b WHERE b.area_ciencia_subcat != 0 AND b.area_ciencia_subcat = a.id_subcat AND b.id = ".$_uid;
  134.                 if($_r = mysql_result(sqlExec($query, ‘query’), 0, ‘subcategoria_es’)){
  135.                         db_query("UPDATE dp_profile_values SET value = ‘".$_r."’ WHERE fid = 7 AND uid = ".$_uid);
  136. //                      db_query("INSERT INTO dp_profile_values(fid, uid, value) VALUES(7, ".$_uid.", ‘".$_r."’)");
  137.                 }
  138.         }
  139. }

Esta clase nos permite crear una instancia para trabajar con Drupal y de esta forma poder migrar usuarios, contenidos, asignar taxonomias/categorias, resetear claves, insertar comentarios, etc. Cada método público debe ser llamado pasando como argumento un array con la información que desea ser insertada. Por ejemplo, una llamda al método createUser:

  1. $_users = mysql_query("SELECT * FROM usuarios", $link);
  2. while($_users_row = mysql_fetch_array($_users)){
  3.         // Llamada a API drupal para crear usuario
  4.         $drupal->createUser($_users_row);
  5. }

Sencillamente, lo que hace este pedazo de código es consultar en una base de datos por todos los usuarios y posteriormente, por cada registro encontrado, llamar al método “createUsers” pasándole como argumento el row correspondiente al ciclo.

Les dejo el script “Migrar.php” que instancia la clase Drupal y hace uso de sus métodos:

  1. < ?php
  2. /*
  3.  * Lunes, 21 de Septiembre del 2009.
  4.  *
  5.  * Migrador de plataformas para
  6.  * Drupal.
  7.  *
  8.  */
  9.  
  10. // Config
  11. require_once("Config.php");
  12.  
  13. $conexion = mysql_connect($host, $username, $password) OR die("Error al conectar a la base de datos.\n");
  14.  
  15. // Llamada a nuestro wrapper
  16. require_once("Drupal.class.php");
  17.  
  18. if(!mysql_select_db($database, $conexion)){
  19.         print "Error al seleccionar la base de datos.\n";
  20. }
  21. else{
  22.         $start = time();
  23.         $drupal = new Drupal();
  24.        
  25.         // Usuarios
  26.         $table = "usr_users";
  27.         $query = "SELECT * FROM ".$table;
  28.         $_users = sqlExec($query, "print_query");
  29.         getRows($_users);
  30.         while($_users_row = mysql_fetch_array($_users)){
  31.                 // Llamada a API drupal para crear usuario
  32.                 $drupal->createUser($_users_row);
  33.                 // Fin
  34.                 $table = "infoc_news";
  35.                 $query = "SELECT * FROM ".$table." WHERE autor = ".$_users_row[‘id’]." ORDER BY fecha";
  36.                 $_news = sqlExec($query, "print_query");
  37.                 getRows($_news);
  38.                 while($_news_row = mysql_fetch_array($_news)){
  39.                         // Llamada a API drupal para crear contenido
  40.                         $drupal->createContent($_news_row, "news");
  41.                         // Fin
  42.                 }
  43.         }
  44.        
  45.         // Comentarios en perfiles
  46.         $table = "usr_comments";
  47.         $query = "SELECT * FROM usr_coments WHERE responseTo = 0";
  48.         $_comments = sqlExec($query, "print_query");
  49.         getRows($_comments);
  50.         while($_comments_row = mysql_fetch_array($_comments)){
  51.                 // Llamada a API drupal para insertar comentarios en perfiles
  52.                 $drupal->insertCommentProfile($_comments_row);
  53.                 // FIn
  54.         }
  55.         $stop = time();
  56.         $time = $stop$start;
  57.         print "Migration done.\n";
  58.         print "+ Summary:\n";
  59.         print "|- Users:\t".$drupal->counter[‘users’]."\n";
  60.         print "|- Nodes:\t".$drupal->counter[‘news’]."\n";
  61.         print "|- Comments:\t".$drupal->counter[‘comments’]."\n";
  62.         print "|_ Time:\t".date("s", $time)." secs.\n";
  63. }
  64. /* sqlExec()
  65.  * Ejecuta y/o imprime una consulta SQL
  66.  */
  67. function sqlExec($query, $type){
  68.         global $conexion;
  69.         switch($type){
  70.                 case ‘print’:
  71.                         print ">>[SQL] ".$query."\n";
  72.                         break;
  73.                 case ‘query’:
  74.                         return mysql_query($query, $conexion);
  75.                         break;
  76.                 case ‘print_query’:
  77.                         print ">>[SQL] ".$query."\n";
  78.                         return mysql_query($query, $conexion);
  79.                         break;
  80.                 case ‘default’:
  81.                         print "Error en el modo de trabajo.\n";
  82.                         break;
  83.         }
  84.         return NULL;
  85. }
  86.  
  87. /* getRows()
  88.  * Imprime la cantidad de resultados obtenidas desde una consulta
  89.  */
  90. function getRows($resource){
  91.         print "< < ".mysql_num_rows($resource)." rows.\n";
  92. }
  93. ?>

NOTA Estos scripts son los originales que he usado para migrar una plataforma, si alguien quiere darle un uso personal debe hacer las modificaciones correspondientes que se adapten a su propio modelo de datos y a sus propias necesidades.