Integrar con Drupal

Drupal es un CMS (Gestor de contenidos) de los más utilizado, sobre todo en empresas de la Administración Pública y sitios oficiales, porque en teoría tiene menos agujeros de seguridad que el resto (WordPress, Joomla, etc.).

En concreto, se utiliza masivamente en la organización donde yo trabajaba y en la empresa donde trabaja mi hijo.

En la versión 10, que es la que he utilizado en este ejemplo, es un producto que ha madurado mucho. La mejora de estos últimos años ha sido «enorme».

Desde mi punto de vista, es un excelente producto para mostrar contenido (biblioteca de libros, imágenes, actividades de colegios o club deportivo, etc.), con mejor calidad que los desarrollos de PHPRunner y, además, es gratuito.

Si necesitáis un manual de usuario para la instalación y gestión de Drupal 10, podéis ir a esta empresa https://www.forcontu.com/user/downloads, disponen de muy buenos manuales (español e Inglés) y el básico «Site Building Introducción» os lo facilitan por registraros ( PDF 433 páginas) .

Objetivo

Esta nueva versión de Drupal incorpora un API REST Full para acceder y modificar todo su contenido y el «reto» es utilizando este API, mover contenidos (datos y ficheros) desde Drupal a PHPRunner y desde PHPRunner a Drupal.

En este caso no voy a poner la aplicación a vuestra disposición porque en general, hay usuarios que se dedican a destruir los juegos de pruebas y otros (no muchos) a utilizar mi identidad para envío de correos, etc.

(1).- Botón para traer todos los Artículos de Drupal a PHPRunner.
(2).- Botón para cargar  los registros seleccionados de PHPRunner a Drupal.
(3) y (4).- Tanto de la información se ha traído de Drupal, como si se ha cargado desde PHPRunner, estos datos identifican la información en Drupal, para que si sincronizamos la información, el aplicativo elimina la versión anterior.

Solución Técnica

Como he indicado, en Drupal voy a utilizar el API Rest Full, que trae la versión 10 y explicaré cómo se configura, y en PHPRunner voy a utilizar la biblioteca de Unirest for PHP.

La información de configuración y codificación la dividiré en 3 partes:

  • Configuración del API Rest Full de Drupal 10.
  • Información de los métodos de acceso a la gestión de datos.
  • Codificación en PHPRunner.
Configuración DrupalMétodos de acciones del Api Rest FullCodificación PHPRunner
Al no ser un usuario habitual de Drupal, me ha sido difícil encontrar los pasos que debía de hacer para configurar el API y dar de alta un usuario que permitiera hacer las peticiones.

Para gestionar los permisos hay que instalar el módulo «rest ui».

Después hay que activar los métodos que vamos a utilizar. Por seguridad, sólo autorizar los que se utilizan.

Podemos apreciar que están los métodos (Get, Post, Push y Delete) para hacer (View, Add, Update y Delete). Esta nomenclatura se va a utilizar en todas las entidades.

Para cada una de las Entidades (Nodes, Files) vamos a establecer los métodos de acceso y la autenticación que se va a utilizar.

Vamos a crear un Rol a que daremos los permisos de loa accesos del API y después daremos el usuario/s que dispongan de este Rol.

En Drupal para obtener el Token de sesión que se tendrá que utilizar en las peticiones se tiene que utilizar la URL http://localhost/drupal10/session/token , En mi caso, tengo instalado el Drupal en http://localhost/drupal10/.

Bien con Postman o algo tan sencillo como el complemento «RESTClient» en Firefox, se debe de probar los métodos y verificar qué parámetros.

Vuelvo a indicar lo que ya comenté, los métodos (Get, Post, Push y Delete) sirven para hacer (View, Add, Update y Delete). Esta nomenclatura se va a utilizar en todas las entidades.

En este caso se utiliza el método POST para la entidad de NODES, por lo que estamos dando ADD de Artículos.

Fijaros que tenemos autenticación básica con el usuario «usuarioemos»/»usuariodemo». También hay que tener en cuenta las variables de la cabecera en donde está el TOKEN de la sesión.

En este caso, en el «body» se define un contenido JSON en donde se especifica los campos definidos en Drupal a los que queremos dar contenido. Para mayor detalle mirad el código definido en PHPRunner.

En este caso estamos recuperando la información de un NODE, en concreto el del ID 54 (ver la URL).

Este es el caso de la carga de un fichero (imagen o lo que corresponda). En el «body» en binario va el contenido del fichero. Debes de probar para ver la respuesta de cada uno de los método o revisar el código del programa PHPRunner, que ahí lo utilizo para traspasar información.

En este caso consultamos los datos de un fichero, en concreto el fichero con ID igual 1. Con el mismo formato podemos ejecuta «Delete». No he conseguido utilizar el método «Post».

Este método y URL se utiliza para recuperar la lista de los artículo que definan los filtros indicados en la vista que se ha construido. Vista «rest» y se ha indicado el «path». Se puede recuperar toda la información del Artículo o sólo los campos que se definan en la vista.

La vista está definida:

No es mucho lo que he codificado, pues lo que he pretendido es una prueba de concepto para verificar que es posible el traspaso de información entre los 2 sistemas.

El código, casi todo él,  está en este apartado:(1).- Biblioteca de Unirest. Es muy pequeña y encapsula las llamadas Curl.
(2).- Es el fichero de parámetros de las conexiones al API Rest Full de Drupal.
(3).- Es el código para leer la VIEW definida en Drupal que accede a todos los registros que cumplan las condiciones definidas en la VIEW.
(4).- Es el código para llevar información de PHPRunner a Drupal.
(5).- Es el fichero que se utiliza para almacenar las trazas de depuración que se define en este artículo.

Contenido del fichero «read_all_article.php«:

<?php
// @ini_set("display_errors","1");
// require_once("include/dbcommon.php");

require_once(__DIR__.'/unirest_3.0.4/autoload.php');

$read_drupal = read_all_record();
if ($read_drupal['code'] == '200' ){ // Tratamiento de Artículos recibidos
    $data_arr = json_decode($read_drupal['raw_body'], true);
    foreach ($data_arr as &$row) {
        // Insert a record into the 'drupal_articulo' table 
        // SELECT d.`titulo`, d.`cuerpo`, d.`etiquetas`, d.`nid_articulo`, d.`fid_imagen`, d.`fecha_ultima_modificacion`, d.`imagen` FROM drupal_articulo d;
  $file = access_file_remote($row['field_image'][0]['url']);
        $filejson = '';
        if ( $file[0] == 0 ) { // Hay un fichero asociado al Artículo
            $id_file = $row['field_image'][0]['target_id'];
            $data_file = read_file_drupal($id_file);
            if ($data_file['code'] == 200){ // Se han recuperado los datos del fichero
                $row_f = json_decode($data_file['raw_body'], true);
                $usrName = $row_f['filename'][0]['value'];
                $size = $row_f['filesize'][0]['value'];
                $type = $row_f['filemime'][0]['value'];
                $searchStr = $row_f['filename'][0]['value'];
                $random = '_'.bin2hex(random_bytes(5));
                $file_arr = explode(".", $usrName);
/*
                $file_name_arr = explode("_", $file_arr[0]);
                $count = count($file_name_arr);
                if ( !( $count > 1 || strlen($file_name_arr[$count-1]) > 7 ) ) // Para que no crezca el nombre en las cargas anidadas
                { 
                    $random = ''; 
                }
*/
                $name = 'files/'.$file_arr[0].$random.'.'.$file_arr[1];
                $file_w = fopen(__DIR__."/../".$name, 'w+');
                fwrite($file_w, $file[1]);
                fclose($file_w);

                $filejson = <<<EOT
 [
  {
    "name": "$name",
    "usrName": "$usrName",
    "size": "$size",
    "type": "$type",
    "searchStr": "$usrName,!:sStrEnd"
  }
]
EOT;
                }
         
        }
        $data = array();
        $data["titulo"] = $row['title'][0]['value'];
        $data["cuerpo"]  = $row['body'][0]['value'];
        $data["etiquetas"] = $row['field_tags'][0]['target_id'];
        $data["nid_articulo"] = $row['nid'][0]['value'];
        $data["fid_imagen"] = $row['field_image'][0]['target_id'];
        $data["imagen"] = $filejson;
        
        DB::Insert("drupal_articulo", $data );     
    }
    
 }

 
// To access file in other server
function access_file_remote($url){ // 
 $ch = curl_init();
 curl_setopt($ch, CURLOPT_URL, $url);
 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

 $file = curl_exec($ch);
 if($file === FALSE) {
    custom_error(1,"Access file: ".$url.' Error CURL: '.curl_error($ch)); // To debug
    $error = 1;
 } else {
    $error = 0;
 }
 curl_close($ch);
 $return_arr = array();
 $return_arr[0] = $error;
 $return_arr[1] = $file;
 return($return_arr);
} 
 
// Recuperar todos los Artículos de Drupal
 function read_all_record(){
    require(__DIR__."/config_drupal.php");
    
    $headers = array('Content-Type' => $content_type,'X-CSRF-Token' => $x_csrf_token );
    $query = array('_format' => 'json');
    // basic auth
    Unirest\Request::auth($user_drupal, $password_drupal);

    $response = Unirest\Request::get($path_url.$path_access_list, $headers, $query);
    // $response->code;        // HTTP Status code
    // $response->headers;     // Headers
    // $response->body;        // Parsed body
    // $response->raw_body;    // Unparsed body 
   $return_arr = array();
   $return_arr['code'] = $response->code;
   $return_arr['raw_body'] = $response->raw_body;

   custom_error(1001,"Read All Record, respose Code: ".$return_arr['code'] ); // To debug
 
   return($return_arr); 
 }
 
 // Recuperar todos los datos de un fichero
 function read_file_drupal($id_file){
    require(__DIR__."/config_drupal.php");
    
    $headers = array('Content-Type' => $content_type,'X-CSRF-Token' => $x_csrf_token );
    $query = array('_format' => 'json');
    // basic auth
    Unirest\Request::auth($user_drupal, $password_drupal);

    $response = Unirest\Request::get($path_url.$path_access_file.$id_file, $headers, $query);
    // $response->code;        // HTTP Status code
    // $response->headers;     // Headers
    // $response->body;        // Parsed body
    // $response->raw_body;    // Unparsed body 
   $return_arr = array();
   $return_arr['code'] = $response->code;
   $return_arr['raw_body'] = $response->raw_body; 

   custom_error(1002,"Read File Drupal, respose Code: ".$return_arr['code'] ); // To debug

   return($return_arr); 
 }

Contenido del fichero «update_drupal.php«:

<?php
// @ini_set("display_errors","1");
// require_once("include/dbcommon.php");

require_once(__DIR__.'/unirest_3.0.4/autoload.php');

/*
Criterio general que se utiliza en este ejemplo de actualización.
- En caso de que exista el artículo, se elimina y se crea uno nuevo
- Los ficheros de imágenes no se eliminan, siempre se crea la imagen para el nuevo artículo.

*/

$record_update = $_SESSION['record_update'];
foreach ($record_update as &$row) {
    $rs = DB::Query("SELECT * FROM drupal_articulo WHERE id_drupal_articulo = $row");
    $data_row = $rs->fetchAssoc();
    if ( $data_row['nid_articulo'] <> Null ) { // Existe Article en Drupal
       $result1_arr = delete_node($data_row['nid_articulo']); // Eliminar Node de Drupal
    }
    if ( $data_row['fid_imagen'] <> Null ) { // Existe File en Drupal
       $result1_arr = delete_file_drupal($data_row['fid_imagen']); // Eliminar File de Drupal
    }
    $json_file = '';
    if ($data_row['imagen'] <> '' ) { // Hay fichero para cargar
         $fileArray = my_json_decode($data_row['imagen']);
         $path_file = __DIR__.'/../'.$fileArray[0]['name'];
         $name_file = $fileArray[0]['usrName'];

         $result2_arr = upload_file_drupal($name_file,$path_file); // Subir fichero
         if ($result2_arr['code'] == 201 ) { // Imagen sibida a Drupal
             $data_file = json_decode($result2_arr['raw_body'], true);
             $fid = $data_file['fid'][0]['value'];
             $title = $data_file['filename'][0]['value'];
             $uuid = $data_file['uuid'][0]['value'];
             $url = $data_file['uri'][0]['url'];

$json_file = <<<EOT
"field_image": [{
 "target_id": $fid,
 "alt": "$title",
 "title": "$title",
 "target_type": "file",
 "target_uuid": "$uuid",
 "url": "$url"
}],
EOT;              
         }
     }
     $title = $data_row['titulo'];
     $body =   addslashes($data_row['cuerpo']);
     $body = preg_replace("/[\r\n|\n|\r]+/", " ", $body);  // Elimna saltos de línea
     $json_tag = '';
     if ( $data_row['etiquetas']<> '' ){ // Hay Tags definidas
         $json_tag = '"field_tags": [';
         $tags_arr = explode(",", $data_row['etiquetas']);
         for ($i = 0; $i < count($tags_arr); $i++) {
             if ($i > 0 ) { $json_tag .= ','; }
             $json_tag .= '{"target_id": "'.$tags_arr[$i].'", "target_type": "taxonomy_term"}';
         }
         $json_tag .='],';
     }

$json_article = <<<EOT
{
$json_file
$json_tag
"title": [{"value": "$title"}],
"body": [{
"value": "$body",
"format": "basic_html"
}],
"type": [{"target_id": "article"}]}
EOT;
    // Crear Artículo en Drupal
    $result3_arr = create_node($json_article);
    if ( $result3_arr['code'] == 201 ) { // Artículo creado
        $row_d = json_decode($result3_arr['raw_body'], true);

        // Update the record with id=50 in the 'Cars' table

        $data = array();
        $keyvalues = array();
        $data["nid_articulo"] = $row_d['nid'][0]['value'];
        if ( isset($row_d['field_image'][0]['target_id'])) { // Existe imagen?
            $data["fid_imagen"]  = $row_d['field_image'][0]['target_id'];
        }
        $keyvalues["id_drupal_articulo"] = $row;
        DB::Update("drupal_articulo", $data, $keyvalues );
    }


 }







// Recuperar todos los  datos de un Artículo de Drupal
 function read_node($id_node){
    include __DIR__."/config_drupal.php";
    
    $headers = array('Content-Type' => $content_type,'X-CSRF-Token' => $x_csrf_token );
    $query = array('_format' => 'json');
    // basic auth
    Unirest\Request::auth($user_drupal, $password_drupal);

    $response = Unirest\Request::get($path_url.$path_access_node.$id_node, $headers, $query);
    // $response->code;        // HTTP Status code
    // $response->headers;     // Headers
    // $response->body;        // Parsed body
    // $response->raw_body;    // Unparsed body 
   $return_arr = array();
   $return_arr['code'] = $response->code;
   $return_arr['raw_body'] = $response->raw_body; 
   
   custom_error(1011,"Read Node, respose Code: ".$return_arr['code'] ); // To debug
   
   return($return_arr); 
 }

// Eliminar un Artículo de Drupal
 function delete_node($id_node){
   include __DIR__."/config_drupal.php";
    
    $headers = array('Content-Type' => $content_type,'X-CSRF-Token' => $x_csrf_token );
    // $query = array('_format' => 'json');
    // basic auth
    Unirest\Request::auth($user_drupal, $password_drupal);

    $response = Unirest\Request::delete($path_url . $path_access_node . $id_node .'?_format=json', $headers);
    // $response->code;        // HTTP Status code
    // $response->headers;     // Headers
    // $response->body;        // Parsed body
    // $response->raw_body;    // Unparsed body 
   $return_arr = array();
   $return_arr['code'] = $response->code;
   $return_arr['raw_body'] = $response->raw_body; 
   
   custom_error(1012,"Delete Node, respose Code: ".$return_arr['code'] ); // To debug
    
   return($return_arr); 
 }

 // Crear un Artículo de Drupal
 function create_node($json){
    include __DIR__."/config_drupal.php";
    
    $headers = array('Content-Type' => $content_type,'X-CSRF-Token' => $x_csrf_token );
     // $query = array('_format' => 'json');
    // basic auth
    Unirest\Request::auth($user_drupal, $password_drupal);

    $response = Unirest\Request::post($path_url . $path_access_node . '?_format=json', $headers, $json);
    // $response->code;        // HTTP Status code
    // $response->headers;     // Headers
    // $response->body;        // Parsed body
    // $response->raw_body;    // Unparsed body 
   $return_arr = array();
   $return_arr['code'] = $response->code;
   $return_arr['raw_body'] = $response->raw_body;
   
    custom_error(1013,"Create Node, respose Code: ".$return_arr['code'] ); // To debug
   
   return($return_arr); 
 }

// Actualización de un Artículo de Drupal
 function update_node($id_node, $json){
    include __DIR__."/config_drupal.php";
    
    $headers = array('Content-Type' => $content_type,'X-CSRF-Token' => $x_csrf_token );
     // $query = array('_format' => 'json');
    // basic auth
    Unirest\Request::auth($user_drupal, $password_drupal);

    $response = Unirest\Request::patch($path_url.$path_access_node.$id_node.'?_format=json', $headers, $json);
    // $response->code;        // HTTP Status code
    // $response->headers;     // Headers
    // $response->body;        // Parsed body
    // $response->raw_body;    // Unparsed body 
   $return_arr = array();
   $return_arr['code'] = $response->code;
   $return_arr['raw_body'] = $response->raw_body; 
   
   custom_error(1014,"Update Node, respose Code: ".$return_arr['code'] ); // To debug
   
   return($return_arr); 
 }
 
 // Recuperar todos los datos de un fichero
 function read_file_drupal($id_file){
    include __DIR__."/config_drupal.php";
    
    $headers = array('Content-Type' => $content_type,'X-CSRF-Token' => $x_csrf_token );
    $query = array('_format' => 'json');
    // basic auth
    Unirest\Request::auth($user_drupal, $password_drupal);

    $response = Unirest\Request::get($path_url.$path_access_file.$id_file, $headers, $query);
    // $response->code;        // HTTP Status code
    // $response->headers;     // Headers
    // $response->body;        // Parsed body
    // $response->raw_body;    // Unparsed body 
   $return_arr = array();
   $return_arr['code'] = $response->code;
   $return_arr['raw_body'] = $response->raw_body;
   
   custom_error(1015,"Read File Drupal, respose Code: ".$return_arr['code'] ); // To debug
   
   return($return_arr); 
 }
 // ELIMINAR un fichero
 function delete_file_drupal($id_file){
    include __DIR__."/config_drupal.php";
    
    $headers = array('Content-Type' => $content_type,'X-CSRF-Token' => $x_csrf_token );
    // $query = array('_format' => 'json');
    // basic auth
    Unirest\Request::auth($user_drupal, $password_drupal);

    $response = Unirest\Request::delete($path_url . $path_access_file . $id_file, $headers);
    // $response->code;        // HTTP Status code
    // $response->headers;     // Headers
    // $response->body;        // Parsed body
    // $response->raw_body;    // Unparsed body 
   $return_arr = array();
   $return_arr['code'] = $response->code;
   $return_arr['raw_body'] = $response->raw_body; 
   
   custom_error(1016,"Delete File Drupal, respose Code: ".$return_arr['code'] ); // To debug
   return($return_arr); 
 }
 
  // upload un fichero a DRUPAL
 function upload_file_drupal($name_file,$path_file) {
    include __DIR__."/config_drupal.php";
    
    $headers = array(
        'Content-Type' => 'application/octet-stream',
        'Content-Disposition' => 'file; filename="'.$name_file.'"',
        'X-CSRF-Token' => $x_csrf_token );
    
    $filename = $path_file;
    $handle = fopen($filename, "r");
    $body = fread($handle, filesize($filename));
    fclose($handle);
    
    // basic auth
    Unirest\Request::auth($user_drupal, $password_drupal);

    $response = Unirest\Request::post($path_url . $path_upload_file , $headers, $body);
    // $response->code;        // HTTP Status code
    // $response->headers;     // Headers
    // $response->body;        // Parsed body
    // $response->raw_body;    // Unparsed body 
   $return_arr = array();
   $return_arr['code'] = $response->code;
   $return_arr['raw_body'] = $response->raw_body; 
   
   custom_error(1017,"Upload File Drupal, respose Code: ".$return_arr['code'] ); // To debug
   
   return($return_arr); 

 }

Como siempre, podéis contactar conmigo a través de mi email: [email protected].

Os facilito el proyecto de PHPRunner, para que lo instaléis en vuestros PC’s y podáis probar, cambiar y estudiar, la fantástica integración que han definido el equipo de Drupal.

 

Adjuntos

Archivo Tamaño de archivo Descargas
zip PHPRunner 10.7 y backup de base de datos 77 KB 329

Blog personal para facilitar soporte gratuito a usuarios de React y PHPRunner