Drupal para programadores

Este libro está dirigido a todas aquellas personas que se dediquen a la programación con Drupal.

Creación de Formularios al modo Drupal

Drupal viene con un API para la creación de formularios.

La estructura del formulario se define en un array.

Creación de un campo con autocompletado

Un campo con autocompletado es un campo de texto que en la medida que vamos digitando nos aparece una lista con las opciones disponibles para este campo, filtrados por el texto que hemos ingresado.

En Drupal para que un campo funcione como autocomplete tenemos dos partes: por un lado tenemos un módulo que nos devuelve a través de una función los resultados que deseamos mostrar y por otro el campo de texto en el formulario donde se digitará el texto, y debajo del cual aparecerán las opciones con las cuales se podrán autocompletar.

 

CREACIÓN EN NUESTRO MÓDULO QUE DEVUELVE LOS RESULTADOS

Tenemos un listado de clientes, en una tabla llamada {cliente}. En uno de nuestros formularios necesitamos que conforme el usuario vaya digitando le aparezca la lista de los posibles clientes que estén de acuerdo con el texto que se está escribiendo.

 

A continuación mostramos los pasos para la creación de un campo de autocompletado. Es importante señalar que se puede hacer en cualquier orden; el orden que estamos poniendo es solo referencial.

 

PRIMER PASO: Creación del campo que tendrá el autocompletado

En nuestro fomulario colocamos un campo de tipo textfield, y le agregamos la propiedad autocomplete_path, donde colocaremos la ruta de drupal que responderá con los resultados conforme vayamos escribiendo. Tomemos nota de lo que hemos colocado en esta propiedad, pues será la que tenga que coincidir con la ruta del menú que llame a la función como veremos más abajo.

 

/**
 * Definición del campo de texto que tendrá autocompletado.

 * Se le agrega una propiedad autocomplete_path

 */

  $form['datos']['cliente'] = array(
    '#type' => 'textfield',
    '#title' => check_plain('Cliente'),
    '#required' => TRUE,
    '#default_value' => isset($node->cliente) ? $node->cliente : '',
    '#autocomplete_path' => 'clientes/autocomplete',
    '#size' => 20,
    '#maxlength' => 60,
    '#weight' => 2,
  );

 

SEGUNDO PASO: Creación de la función en el módulo que devolverá los resultados

Escribimos una función que recupere la lista de clientes.

 

function cliente_autocomplete($string = ''){
  $matches = array();
  $result = db_query_range("SELECT DISTINCT nombre
                            FROM {cliente}
                            WHERE LOWER(nombre) LIKE LOWER('%s%%')", $string, 0, 10);
  while ($cliente = db_fetch_object($result)) {
    $matches[$cliente->nombre] = check_plain($cliente->nombre);
  }

  drupal_json($matches);
}

 

TERCER PASO: Hacemos que nuestra función pueda ser accedida por http

Para que la función esté disponible vía http, en el hook_menu del módulo del mismo creamos una nueva entrada.

 

/**
 * Implementacion del hook_menu
 */
function cliente_menu(){
  $items = array();

  //Creamos una entrad en el menú que acceda a nuestra función
  $items['clientes/autocomplete'] = array(
    'title' => 'Lista Clientes Autocomplete',
    'page callback' => 'cliente_autocomplete',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK
  );

  //Acá puede haber otros elementos del array

  return $items; 
}

 

Observemos que en page_callback tiene como valor el nombre de la función que creamos previamente.

 

Es IMPORTANTE notar que la clave del array, tiene la ruta 'clientes/autocomplete' y que es igual a la que colocamos en autocomplete_path al definir el campo en nuestro formulario.

Capa de abstracción de acceso a base de datos

Drupal provee una capa de abstracción para el acceso a base de datos, de tal manera que se pueda cambiar de motor de base de datos (MySQL, Postgres y cualquier otro que sea implementado posteriormente) utilizando el mismo código fuente. Se recomienda el uso de la capa de abstracción de Drupal, pues incluso de usarse solo el motor de MySQL nos previene de posibles incompatibilidades de código generados por el uso de otro tipo de extensión para la conexión. Por ejemplo un servidor puede estar usando PHP's MySQL Extensión o PHP's mysqli Extensión, los cuales tienen APIs distintas. El primero está desarrollado con principios de programación procedural, y el segundo con principios de programación orientado a objetos.

 

Por otra parte el uso de esta capa de abstracción añade algunos controles básicos de seguridad sobre las consultas efectuadas al servidor.

 

Entre las funciones más utilizadas de esta capa de abstracción tenemos dbquery(), db_query_range() y pager_query().

Ejecutar consultas sql en Drupal

db_query($query)

La función db_query() nos permite enviar consultas sql a la base de datos, de manera similar a como lo haríamos con las funciones propias de cada motor de base de datos, por ejemplo mysql_query() para MySQL.

 

Otro aspecto importante de la función db_query() es que permite armar consultas, colocando caracteres que serán reemplazados por parámetros.

Seleccionar un rango de registros

db_query_range($query)

Para seleccionar un rango de registros del total de resultados. Es decir, si ejecutamos una consulta que produce un total de 100 registros y necesitamos los registros del 10 al 20, podemos usar esta función.

Obtener resultados paginados

pager_query($query, $limit = 10, $element = 0, $count_query = NULL)

Si queremos mostrar resultados paginados, es conveniente considerar la utilización de la función pager_query(), que nos permite obtener los resultados que corresponden a una página determinada. En este caso, si ejecutamos una consulta que produce un total de 100 registros y queremos mostrar un máximo de 10 registros por página, la página 1 tendrá los registros del 1 al 10, la página 2 los registros del 11 al 20, la página 3 los registros del 21 al 30 y así sucesivamente.

 

La idea es similar a la función db_query_range(), la cual es usada internamente por pager_query(), con la diferencia que en vez de indicarle el rango de registros, se indica el número de registros que se mostrarán por página, y la página que se quiere mostrar. Según la implementación actual la página que se quiere mostrar no se pasa como parámetro a la función sino que es un parámetro get llamado page. Es decir, en nuestra página existirá algo como http://www.mipagina/articulos?page=5, lo cual significa que estamos mostrando la página 5 de un listado de artículos.

 

Con la función pager_query(), tendremos posibilidad de calcular también el número total de registros y el número total de páginas, aunque como veremos más adelante en muchos casos es necesario enviar una consulta propia para calcular en número total de registros.

Usar javascript

La versión de Drupal viene integrado con jquery. jquery es una biblioteca que facilita la manipulación de la estructura del html, el manejo de eventos, animaciones y peticiones ajax.

 

Drupal behaviors

Una de las formas más comunes de agregar código javascript cuando usamos jquery es ponerlo en la función $(document).ready, lo cual significa que este código se ejecutará o estará disponible cuando el navegador terminado de cargar el árbol DOM.

 

$(document).ready(function(){
  // Acá agregamos nuestro código
});

 

En Drupal 6, la forma recomendada de agregar nuestro código javascript es usar la propiedad behaviors del objeto Drupal. La propiedad behaviors es también un objeto. Para agregar nuestro código extendemos este objeto. Podemos agregar funciones a Drupal.behaviors de la siguiente manera:

 

Drupal.behaviors.miBehavior = function(context){
  // Acá agregamos nuestro código
};

 

Cualquier función definida como propiedad de Drupal.behaviors será llamada cuando el árbol DOM se halla cargado.

Creación de Módulos

En este capítulo explicaremos como crear un módulo para drupal. Trataremos, en la medida de lo posible de seguir las convenciones para la creación de módulos Drupal.

 

Los pasos a seguir para la creación de un módulo drupal son los siguientes:

 

ESCOGER UN NOMBRE CORTO Y ÚNICO PARA NUESTRO MÓDULO

El primer paso para crear un módulo de drupal es escoger un nombre que sea corto y que no se esté usando en otro módulo. Esta tarea que parece sencilla es de vital importancia pues del nombre de nuestro módulo dependerán muchas de nuestras acciones posteriores como por ejemplo el nombre de las funciones que utilicemos.

 

CREACIÓN DE LA CARPETA QUE ALBERGARÁ EL MÓDULO

Esta carpeta tendrá el mismo nombre que el nombre que hemos elegido.

 

 

 

EL SISTEMA DE HOOK

Drupal se comunica con el core a través de un sistema de hooks (ganchos).

Algunos módulos también implementan un sistema de hooks que amplían el sistema existente.

Crear Acciones

Una acción es algo que Drupal puede hacer, y que se puede establecer para que sea lanzado al producirse un evento. Es decir, para que sea llamado por un disparador (Trigger).

Desde el punto de vista de la programación una acción es una función que puede configurarse para que sea llamada cuando se produce un evento. Se parecen a los procedimientos almacenados. Si hay parámetros estos son guardados en la base de datos; los parámetros serán recuperados cuando la acción sea llamada.

 

DESCRIBIR LAS ACCIONES

Nuestro módulo necesita describir las acciones que implementa. Para ello hacemos uso del hook_action_info(). Este hook devuelve un array que tiene la descripción de las acciones de este módulo.

Observemos por ejemplo el siguiente código:

/**
 * Implementación del hook_action_info().
 */ 
function workflow_autostatus_action_info(){
    return array(
    'workflow_autostatus_change_action' => array(
      'description' => t('Change the node status'),
      'type' => 'node',
      'configurable' => FALSE,
      'hooks' => array(
        'nodeapi' => array('presave', 'insert', 'update', 'view'),
        ),
      ),
    );
}

 

Cada elemento del array representa una acción. La llave de este array, además de identificar la acción, tiene el mismo nombre de la función que responderá cuando la acción sea llamada. Para la creación de las llaves que identifican las acciones se sigue unas reglas de nomenclatura: se comienza con el nombre del módulo, luego una descripción de lo que hace la acción y finalmente termina con la palabra _action.

Es decir: nombreMódulo + descripción + _action

En el ejemplo tenemos:

workflow_autostatus + change + _action = workflow_autostatus_chang_action

 

Cada elemento es, a su vez, un array, que tiene la siguiente estructura:

  • description, podemos colocar una descripción de lo que hace la acción.
  • type, indicamos sobre que tipo de objeto actúa la acción. Los posibles valores son node, user, comment y system. O un tipo personalizado propio.
  • configurable, es un valor boolean que indica si la acción es configurable o no. Si es que la acción es configurable, entonces drupal nos mostrará una interfaz para configurarlo antes que podamos usarlo, caso contrario podremos agregar directamente la acción para que sea llamado por un trigger.
  • hooks, es un array que contiene todas las operaciones para las cuales esta acción es apropiada. El módulo action utiliza esta información para filtrar las acciones que se van a mostrar en en la interfaz que permite asignar acciones a eventos.

 

ESCRIBIR UN FUNCIÖN POR CADA ACCIÓN

Por cada acción que hemos definido, necesitamos escribir una función, que es la que se ejecutará cuando la acción sea llamada. El nombre de esta función es la misma que la llave del array que la define.

/**
* Implementación de la acción de Drupal definida en el hook action_info.
* Cambia el status del nodo, de acuerdo a lo especificado
*/
function workflow_autostatus_change_action(&$object, $context = array()){
  global $user;
  if (isset($object->nid)){
    $result = db_query("select sid from workflow_node where nid = %d", $object->nid);
    $statu = db_fetch_object($result);
    db_query("update workflow_node set sid = 2 where nid = %d", $object->nid);
    db_query("insert into workflow_node_history (nid, old_sid, sid, uid, stamp, comment) values(%d,%d,2,%d,%d,'cambio automático por actualización del nodo')", $object->nid, $statu->sid, $user->uid, time());
  }
}

 

Creando Permisos

Con el hook_perm, un módulo puede indicar los nombres de los permisos que este módulo está creando para puedan ser asignados a los diferentes roles. Este hook no nos dice que acceso da cada permiso, sino que solo nos da una lista de los permisos disponibles. Más adelante veremos el hook_access, donde se definen los accesos.

El nombre del permiso no puede ser igual a ningún otro permiso de alguno de los otros módulos instalados, porque sino en la lista de permisos aparecerńa varios iguales (o el sistema podría comportarse de modo inesperado utilizando un permiso que no le corresponde). Para evitar estas colisiones se recomienda poner el nombre el módulo dentro del permiso. Más exactamente se recomienda colocar un verbo que describa la acción a realizar seguido del nombre del módulo.

Ejemplos de nombres de permiso:
  • administrar inventario
  • registrar inventario
  • borrar inventario
  • etc.

Usted podra asignar los permisos a los usuarios en Administer->User management->Permissions

Ejemplos

<?php
/**
* Permisos válidos para este módulo
* @return array Un array con todos los permisos válidos para este módulo
*/
function inventario_perm() {
  return array(
'registrar inventario');
}

?>


Y si tienes varios permisos, solo se agrega más elementos al array:

<?php
function inventario_perm() {
  return array(
'administrar inventario', 'registrar inventario', 'borrar inventario');
}

?>

REVISANDO SI UN USUARIO TIENE UN PERMISO
Si queremos revisar si un usuario tiene un determinado permiso, podemos usar la función

user_access($string, $account = NULL, $reset = FALSE);

En donde:
$string es el nombre del permiso sobre el cual queremos averiguar si el usuario tiene privilegios
$account es la cuenta de usuario para la que queremos saber si se tiene los privilegios. Este parámetro es opcional y si no es proporcionado se toma el usuario actual (actualmente logueado).
$reset, este parámetro indica si se va a resetear o no el cache de los permisos de usuario. De colocar TRUE, se recalcularán los permisos del usuario. Es necesario cuando se necesita los valores modificados cada vez, como cuando se agregan roles de forma dinámica.

Esta función devuelve un boolean, que es TRUE si el usuario tiene el privilegio correspondiente.

Hay que recordar que el usuario con id = 1 (el primer usuario que se creo) es un super-usuario con todos los privilegios, por lo cual usar está función con el mismo siempre nos devolverá TRUE
ESTABLECIENDO EL PERMISO EN EL MENÚ QUE LLAMA LA FUNCIÓN
Al crear un menú, podemos indicar que permiso en necesario para poder acceder a dicha opción del menú. Para ello agregamos en la llave access_arguments, el array con la lista de permisos que pueden acceder al contenido de dicho menú.

Manejo de Fechas

Drupal maneja las fechas en un formato timestamp, al igual que lo hace php. Eso significa que se almacenan las fechas en un número que indica la cantidad de segundos que han transcurrido desde el Unix Epoch, es decir desde el 1 de enero de 1970 a las 00 horas.

De manera que al ver las fechas en la base de datos veremos usualmente un número entero de 10 dígitos.

 

DAR FORMATO A UNA FECHA

Drupal dispone de la función format_date que nos permite darle formato a una fecha, de acuerdo a la configuración que halla hecho el usuario para la instalación de drupal, o incluso darle un formato personalizado. Con el uso de esta función además se mantiene la configuración de idioma establecido para el usuario de drupal.

 

USANDO LOS FORMATOS DE FECHA DEFINIDOS PARA DRUPAL

Para usar el formato definido en la configuración de drupal, para fecha corta, media o larga, podemos usar la función de la siguiente manera:

format_date($timestamp, $type);

Reemplazando $timestamp por la fecha en formato timestamp y $type por el tipo que puede ser 'small', 'medium' o 'large'.

Por ejemplo, consideramos la siguiente configuración:

(TODO: Imagen con configuración)

para poner el formato corto podemos usar algo como:

format_date(1250556783, 'small');

y nos mostrará algo como lo siguiente:

(TODO: Poner imagen)

(TODO: Agregar los otros tipos de formato)

 

USAR UN FORMATO PERSONALIZADO

Para usar un formato personalizado, se muy similar, solo que reemplazamos el parámetro $type, por 'custom', y agregamos un tercer parámetro con el formato. Por ejemplo podemos tener algo como:

format_date(1250556783, 'custom', 'D - m / y');

Y nos devolverá algo como:

(TODO: Imagen)