Entorno para desarrollo Drupal 8 en Mac: Docker con Apache + MariaDB + PHP + …

Vamos a ver cómo crear un entorno de desarrollo para Drupal 8 en OSX utilizando docker-compose. Con unos sencillos pasos conseguiremos tener montado de forma muy rápida un entorno completo que cuente con todo lo necesario para empezar a desarrollar en Drupal 8. Lo que vamos a tener será:
– MariaDB
– PHP-FPM
– Apache
– PhpMyAdmin
– Traefik
– Portainer

INSTALACIÓN DE DOCKER.

La instalación es muy sencilla. En linux ya viene preinstalado. En Mac se instala de forma muy simple, basta con seguir las instrucciones de la documentación oficial: https://docs.docker.com/docker-for-mac/install/#install-and-run-docker-for-mac

INSTALACIÓN DE dockerfordrupal

Lo primero será crear una cuenta en Docker Hub (repositorio de imgs Docker). Después abrimos Terminal y nos loggeamos:

docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: AQUITULOGIN
Password: AQUITUPASSWORD
Login Succeeded

Vamos a aprovechar el trabajo realiazdo por Wodby y clonamos su repo dockerfordrupal.

Para ello:

git clone https://github.com/wodby/docker4drupal
cd dockerfordrupal

En el directorio veremos varios archivos:

docker-compose.override.yml  docker.mk        LICENSE.md  README.md  traefik.yml
docker-compose.yml           docker-sync.yml  Makefile    tests

Vamos a modificar el fichero .env para darle nombre a nuestro proyecto (en mi caso unizar) e indicarle la base url. Para eso editaremos estas líneas.

PROJECT_NAME=unizar
PROJECT_BASE_URL=unizar.docker.localhost

El archivo docker-compose.override.yml de momento no vamos a usarlo. Es útil cuando se tienen varios entornos (desarrollo/producción) con distintos settings. En este caso lo vamos a renombrar porque editaremos directamente docker-compose.yml:

mv docker-compose.override.yml docker-compose.override.yml.back

Lo siguiente será eliminar todo el contenido de docker-compose.yml y reemplazarlo por el siguiente:

version: "3"
  
services:
  mariadb:
    image: wodby/mariadb:$MARIADB_TAG
    container_name: "${PROJECT_NAME}_mariadb"
    stop_grace_period: 30s
    environment:
      MYSQL_ROOT_PASSWORD: $DB_ROOT_PASSWORD
      MYSQL_DATABASE: $DB_NAME
      MYSQL_USER: $DB_USER
      MYSQL_PASSWORD: $DB_PASSWORD

  php:
    image: wodby/drupal:$DRUPAL_TAG
    container_name: "${PROJECT_NAME}_php"
    environment:
      PHP_SENDMAIL_PATH: /usr/sbin/sendmail -t -i -S mailhog:1025
      DB_HOST: $DB_HOST
      DB_USER: $DB_USER
      DB_PASSWORD: $DB_PASSWORD
      DB_NAME: $DB_NAME
      DB_DRIVER: $DB_DRIVER
      COLUMNS: 80 # Set 80 columns for docker exec -it.
## Read instructions at https://wodby.com/stacks/drupal/docs/local/xdebug/
      PHP_XDEBUG: 1
      PHP_XDEBUG_DEFAULT_ENABLE: 1
      PHP_XDEBUG_REMOTE_CONNECT_BACK: 1
      PHP_FPM_CLEAR_ENV: "no"
      #PHP_IDE_CONFIG: serverName=my-ide
      #PHP_XDEBUG_REMOTE_HOST: host.docker.internal # Docker 18.03+ & Linux/Mac/Win
      PHP_XDEBUG_REMOTE_HOST: 172.17.0.1 # Linux, Docker < 18.03
    volumes:
      - ./code:/var/www/html


  apache:
    image: wodby/apache:$APACHE_TAG
    container_name: "${PROJECT_NAME}_apache"
    depends_on:
      - php
    environment:
      APACHE_LOG_LEVEL: debug
      APACHE_BACKEND_HOST: php
      APACHE_VHOST_PRESET: php
      APACHE_DOCUMENT_ROOT: /var/www/html/web
    volumes:
      - ./code/:/var/www/html
# For macOS users (https://wodby.com/stacks/drupal/docs/local/docker-for-mac/)
#      - ./:/var/www/html:cached # User-guided caching
#      - docker-sync:/var/www/html # Docker-sync
    labels:
      - 'traefik.backend=${PROJECT_NAME}_apache'
      - 'traefik.port=80'
      - 'traefik.frontend.rule=Host:${PROJECT_BASE_URL}'


  pma:
    image: phpmyadmin/phpmyadmin
    container_name: "${PROJECT_NAME}_pma"
    environment:
      PMA_HOST: $DB_HOST
      PMA_USER: $DB_USER
      PMA_PASSWORD: $DB_PASSWORD
      PHP_UPLOAD_MAX_FILESIZE: 1G
      PHP_MAX_INPUT_VARS: 1G
    labels:
      - 'traefik.backend=${PROJECT_NAME}_pma'
      - 'traefik.port=80'
      - 'traefik.frontend.rule=Host:pma.${PROJECT_BASE_URL}'

  portainer:
    image: portainer/portainer
    container_name: "${PROJECT_NAME}_portainer"
    command: --no-auth -H unix:///var/run/docker.sock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    labels:
      - 'traefik.backend=${PROJECT_NAME}_portainer'
      - 'traefik.port=9000'
      - 'traefik.frontend.rule=Host:portainer.${PROJECT_BASE_URL}'

  traefik:
    image: traefik
    container_name: "${PROJECT_NAME}_traefik"
    command: -c /dev/null --web --docker --logLevel=INFO
    ports:
      - '8000:80'
      - '8080:8080' # Dashboard
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

Compilamos y construimos:

docker-compose up -d

En este momento ya podemos ver los contenedores, para ello empleamos el comando "docker-compose ps"

docker-compose ps
      Name                    Command               State                      Ports
--------------------------------------------------------------------------------------------------------
unizar_apache      /docker-entrypoint.sh sudo ...   Up      80/tcp
unizar_mariadb     /docker-entrypoint.sh mysqld     Up      3306/tcp
unizar_php         /docker-entrypoint.sh sudo ...   Up      9000/tcp
unizar_pma         /run.sh supervisord -n           Up      80/tcp, 9000/tcp
unizar_portainer   /portainer --no-auth -H un ...   Up      9000/tcp
unizar_traefik     /traefik -c /dev/null --we ...   Up      0.0.0.0:8000->80/tcp, 0.0.0.0:8080->8080/tcp

Si queremos pararlos:

docker-compose stop

Y para arrancarlos:

docker-compose start

Para ver logs:

docker-compose logs

Lo siguiente será editar (como root) el fichero /etc/hosts para indicarle algunos nombres que nos irán bien. Para ello debemos tener presente lo que escribimos anteriormente en el fichero .env. En mi caso sería así:

# /etc/hosts
127.0.0.1    unizar.docker.localhost
127.0.0.1    pma.unizar.docker.localhost
127.0.0.1    portainer.unizar.docker.localhost 

Llegados a este punto ya podremos abrir un navegador y acceder a varias direcciones:
http://unizar.docker.localhost:8000 -> para comenzar la instalación de Drupal, una vez traido el source
http://pma.unizar.docker.localhost:8000 -> para acceder a PhpMyAdmin
http://portainer.unizar.docker.localhost:8000 -> para tener una visión web de los contenedores Dockers.

Si lo que queremos es ENTRAR a la máquina host, podemos hacerlo asi:

docker-compose ps # nos da la lista de dockers que están corriendo

docker exec -it unizar_php bash # ejecutar "bash" dentro del docker llamado "unizar_php" # así mantiene la máquina "viva"

Adding jQuery UI tabs to Drupal 7 [SOLVED]

I recently wanted to add some jQuery UI tabs to nodes. Drupal 7 has jQuery 1.4 included in core, so we’ll take advantage of that.

This is how you do it:

First, edit template.php and create / make some changes to hook _preprocess_html.

function THEMENAME_preprocess_html(&$variables){
   // This function looks for node 1 and only adds the javascript for this.
   // However it can be extended in different ways if required
    drupal_add_library('system', 'ui.tabs');
    drupal_add_js('jQuery(document).ready(function(){
                jQuery("#tabs").tabs({event: "mouseover"});
                jQuery("#tabs").tabs().addClass("ui-tabs-vertical ui-helper-clearfix");
                jQuery("#tabs li").removeClass("ui-corner-top").addClass("ui-corner-left");
            });',
        'inline');
}

*Note that you can remove ui-tabs-vertical if you want horizontal tabs. You can also change the mouseover event to the event you prefer.

This would be the markup:

<!-- TITULOS PESTAÑAS -->
<div id="tabs">
<div id="tab-list">
  <ul>
  <li><a href="#tabs-1">Titulo 1</a></li>
  <li><a href="#tabs-2">Titulo 2</a></li>
  <li><a href="#tabs-3">Titulo 3</a></li>
  <li><a href="#tabs-4">Titulo 4</a></li>
  <li><a href="#tabs-5">Titulo 5</a></li>
  <li><a href="#tabs-6">Titulo 6</a></li>
  <li><a href="#tabs-7">Titulo 7</a></li>
  <li><a href="#tabs-8">Titulo 8</a></li>
  <li><a href="#tabs-9">Titulo 9</a></li>
  <li><a href="#tabs-10">Titulo 10</a></li>
  </ul>
</div>
 
 
<!-- CONTENIDO PESTAÑAS -->
<div id="tab-content">    
  <div id="tabs-1">
    <p>Texto de la TABS-1</p>
  </div>
  <div id="tabs-2">
    <p>Texto de la TABS-2</p>
  </div>
  <div id="tabs-3">
    <p>Texto de la TABS-3</p>
  </div>
  <div id="tabs-4">
    <p>Texto de la TABS-4</p>
  </div>
  <div id="tabs-5">
    <p>Texto de la TABS-5</p>
  </div>
  <div id="tabs-6">
   <p>Texto de la TABS-6</p>
  </div>
  <div id="tabs-7">
    <p>Texto de la TABS-7</p>
  </div>
  <div id="tabs-8">
    <p>Texto de la TABS-8</p>
  </div>
  <div id="tabs-9">
    <p>Texto de la TABS-9</p>
  </div>
  <div id="tabs-10">
    <p>Texto de la TABS-10</p>
  </div>
</div>
</div>

And a sample output:
UI-Tabs-Drupal

Drupal 7: quick-edit link for nodes for authorized users [SOLVED]

Showing a “edit this node” link for users who have authorization to edit that node is a useful feature.

The link will show only to logged users who have the editing privilege.

In Drupal7 this can be done by editing the node template and adding these lines to your node.tpl.php (or node–contenttypename-tpl.php)

  <!-- quick edit link -->
  <?php if(user_access('administer nodes')): 
      echo l(t('Edit this node'), 'node/' . $node->nid . '/edit');
   endif; ?>
  <!-- /quick edit link -->

Or:

<?php
if (node_access('update',$node)){
   print l(t('Edit'),'node/'.$node->nid.'/edit' );
}
?>

Drupal7: list allowed values from a field type list [SOLVED]

In Drupal 7, how do you list all the allowed values in a certain field of some content type?

You can use the list_allowed_values function.

Lets see it with an example.

I will define a new Content Type called “Actividad” (machine name=’actividad‘), which will have a title, a body and a List(text) field type called ‘tipo_actividad‘ (machine name=’field_tipo_actividad‘):
list_allowed_values drupal 7

And then I will define the allowed values for this field_tipo_actividad field:
drupal 7 list_allowed_values for field

Now, how do I programmatically list the allowed values for that field? Using list_allowed_values function:

$field = field_info_field('field_tipo_actividad'); /* field_tipo_actividad=machine name of the field */
$allowed_values = list_allowed_values($field);     /* what are the values allowed for that field? */
return var_dump($allowed_values);

drupal7 list allowed values

If you want a prettier output, you can change the code to:

$field = field_info_field('field_tipo_actividad');
$allowed_values = list_allowed_values($field);
$salida = "";
foreach ($allowed_values as $key=>$value){
    $salida = $salida."Key: ".$key." Value: ".$value."<br />";
}
return $salida;

And you’ll get something like:
drupal list_allowed_values tutorial example

Drupal 7: crear un módulo que consulte base de datos externa [RESUELTO]

Imaginemos que queremos leer información de una base de datos externa en Drupal 7. Para ello vamos a crear un módulo. El tutorial cubrirá el proceso completo desde la instalación de Drupal.

Consideraciones iniciales

Todo el ejemplo se hará en entorno MAMP versión 2.2 sobre OS X.

Apache:
La carpeta de trabajo será el htdocs de Apache en el path: /Applications/MAMP/htdocs
Apache estará corriendo en puerto 80
La versión de PHP utilizada es PHP 5.5.3 (¡con todas las caches -tipo APC- desactivadas!)

Base de datos:
Elegiremos MySQL, que escuchará en el puerto 3306
El usuario con que conectaremos a MySQL será root y su password será root (recuerda no hacer esto NUNCA en un entorno de producción). La base de datos que usaremos para la prueba se llamará drupal y el usuario root tendrá todos los permisos sobre ella.

Variables de entorno
Vamos a añadir al $PATH el path donde tenemos los binarios de MySQL (por defecto en /Applications/MAMP/Library/bin) para poder realizar llamadas a los binarios desde línea de comandos de forma más cómoda. Lo podemos hacer mediante la orden:

miguelm$ export PATH=$PATH:/Applications/MAMP/Library/bin

Drupal:
La realease empleada será Drupal 7.21

Paso 1: Instalación de Drupal

– Descomprimir el zip en /Applications/MAMP/htdocs

– Crear la nueva base de datos para Drupal:

miguelm$ mysqladmin -u root -p create drupal
Enter password:

– Entrar a MySQL:

miguelm$ mysql -u root -p

– Dar permisos al usuario root para usar esa BD:

mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER
    ->   ON drupal.*
    ->   TO 'root'@'localhost' IDENTIFIED BY 'root';
Query OK, 0 rows affected (0,06 sec)

– Configurar Drupal para la instalación.
Primero nos movemos al directorio donde hemos descomprimido el zip de Drupal…

miguelm$ cd /Applications/MAMP/htdocs/

Y editar el fichero settings.php para decirle cómo se llama nuestra base de datos, nuestro usuario y demás…

miguelm$ vim ./sites/default/settings.php

La configuración de las bases de datos tiene que quedar asi:

$databases = array (
  'default' =>
  array (
    'default' =>
    array (
      'database' => 'drupal',
      'username' => 'root',
      'password' => 'root',
      'host' => 'localhost',
      'port' => '3306',
      'driver' => 'mysql',
      'prefix' => '',
    ),
  ),
);

En entornos de desarrollo también es muy interesante incluir estas líneas en el archivo settings.php, para que se muestren en web los errores de PHP:

<?php
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);

El siguiente paso es instalar Drupal. Para ello, abrimos el navegador y (teniendo Apache levantado) procedemos a ejecutar install.php visitando la URL: http://localhost/install.php

instalacion de drupal7

Haremos una instalación estándar en idioma inglés. El instalador debe terminar y veremos esta pantalla.

tutodrup2

Entonces nos loggeamos en nuestro recién creado site visitando la URL: http://localhost/?q=user y rellenando el username y pwd con los valores elegidos (por ejemplo, admin/admin)

Paso 2: Creación de base de datos externa

Ya tenemos Drupal instalado. Vamos ahora a crear una base de datos de prueba con una única tabla muy sencilla, que almacene por ejemplo una relación de títulos de libros.

Creamos la nueva bd, a la que llamaremos externaldatabase.

miguelm$ mysqladmin -u root -p create externaldatabase

A continuación entramos en MySQL para asignar privilegios (lo haremos con el usuario root, aunque recordemos que en entornos de producción elegiríamos otro user para esta bd)

mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER on externaldatabase.* TO 'root'@'localhost' IDENTIFIED BY 'root';
Query OK, 0 rows affected (0,02 sec)

Y creamos nuestra tabla con datos de libros:

mysql> USE externaldatabase;
Database changed
mysql> CREATE TABLE libros (id INT, titulo VARCHAR(200));
Query OK, 0 rows affected (0,04 sec)

Insertamos un par de datos de prueba:

mysql> INSERT into libros (id, titulo) VALUES (1,"El Quijote");
Query OK, 1 row affected (0,03 sec)

Paso 3: Nuevo módulo en Drupal 7:

Los módulos en Drupal están (normalmente) en /sites/all/modules
Vamos a crear una nueva carpeta en ese path que se llamará bdexterna.

miguelm$ pwd
/Applications/MAMP/htdocs
miguelm$ cd sites/all/modules/
miguelm$ mkdir bdexterna

Un módulo se compone de dos archivos: un archivo con extensión .info (definición del módulo) y un archivo con extensión .module (que contiene la lógica -programación PHP-).

Vamos a crear nuestro archivo externaldbtest.info dentro de la carpeta externaldbtest:

miguelm$ cd bdexterna
miguelm$ vim bdexterna.info

El contenido de externaldbtest/externaldbtest.info será:

name = BD Externa
description = "Modulo para consultar BD externa"
package = Aprendiendo Drupal7 en leccionespracticas.com
core = 7.x

También creamos el archivo bdexterna.module que, de momento, dejamos vacío…:

miguelm$ touch bdexterna.module

Esto quiere decir que el módulo se llamará BD Externa, que se ubicará en la categoría de módulos “Aprendiendo Drupal7”, es compatible con Drupal 7.x y también describimos para qué sirve.

Una vez creados estos archivo podemos ir a http://localhost/?q=user/1#overlay=%3Fq%3Dadmin%252Fmodules en nuestro navegador y veremos que se ha creado una nueva categoría llamada “Aprendiendo Drupal7 en leccionespracticas.com” que contiene el módulo “BD Externa” (y se nos permite activarlo):

conexion a base de datos externa drupal con un nuevo módulo

Antes de lanzarnos a hacer queries a nuestra base de datos externa, debemos entender cómo funcionan los módulos de Drupal. Y para ello nada mejor que empezar con un “Hola Mundo!”. Vamos a generar un bdexterna.module de tal forma que al visitar la url http://localhost/?q=libros/listar nos diga “Hola mundo”.

Para programar la respuesta a una determinada URL (/libros/listar) debemos usar la función hook_menu(). Al visitar una URL, Drupal comprueba si alguno de nuestros módulos tiene programado una función nombremodulo_menu() para esta dirección.

Vamos a empezar, por tanto, creando esta función.

saludar/saludar.module

<?php
/**
 * @file
 * Nuestro primer módulo en Drupal (leccionespracticas.com)
 */
 
/**
 * Implementa hook_menu().
 */
function bdexterna_menu() {
  $items['libros/listar'] = array(
    'title' => 'LISTAR LIBROS',
    'page callback' => 'bdexterna_libros_listar',
    'access callback' => TRUE,
  );
  // con lo anterior indicamos que cuando se visite la url libros/listar
  // se muestre una página con título 'LISTAR LIBROS' cuya salida será el
  // resultado de ejecutar la funcion 'bdexterna_libros_listar', que deberemos definir...
 
  return $items;
} 
?>

Y además deberemos crear la función de callback que hemos dicho que debía ejecutarse al visitar esta url, que será muy simple porque solo queremos mostrar un “Hola Mundo!”. Recordamos que el nombre que debemos darle a esta función es el que indicamos en el parámetro page_callback, osea bdexterna_libros_listar.

/**
 * Callback para libros/listar.
 */
function bdexterna_libros_listar() {
  return "Hola Mundo";
}

La primera versión completa de nuestro archivo bdexterna.module será, por tanto, asi:

<?php
/**
 * @file
 * Nuestro primer módulo en Drupal (leccionespracticas.com)
 */
 
/**
 * Implementa hook_menu().
 */
function bdexterna_menu() {
  $items['libros/listar'] = array(
    'title' => 'LISTAR LIBROS',
    'page callback' => 'bdexterna_libros_listar',
    'access callback' => TRUE,
  );
  // con lo anterior indicamos que cuando se visite la url libros/listar
  // se muestre una página con título 'LISTAR LIBROS' cuya salida será el
  // resultado de ejecutar la funcion 'bdexterna_listar_libros', que 
  // deberemos definir
  // el parámetro access callback indica quién puede acceder al callback:
  // en este caso, al tener el valor TRUE, todos pueden acceder.
 
  return $items;
} 
 
/**
 * Callback para URL libros/listar
 */
function bdexterna_libros_listar() {
  return "Hola Mundo";
}

Solo nos queda activar el módulo, vaciar las caches de Drupal (URL http://localhost/?q=admin/config/development/performance) y visitar la URL elegida (http://localhost/?q=libros/listar). Comprobamos que se ve el “Hola Mundo”.

Drupal hola mundo menu_hook

Función (callback) para consulta a base de datos

Ahora que hemos entendido cómo funcionan los hooks y hemos construído un módulo básico, podemos lanzarnos a conseguir nuestro objetivo: listar los libros.

Para ello modificaremos la función de callback que habíamos definido en el archivo bdexterna.module (llamada bdexterna_libros_listar) y añadiremos el código necesario para realizar la consulta a la BD externa de libros.

Lo primero que debemos hacer es definir la nueva base de datos externa en Drupal. Como vimos en el post anterior, podemos hacerlo de dos formas. En este caso y puesto que solo usaré esta base de datos desde este módulo, optaré por definir la base de datos directamente en el propio módulo.

El código es bastante sencillo y self-explaining:

/**
 * Callback para libros/listar.
 */
function bdexterna_libros_listar() {
  // Definimos la base de datos externa...
 
  $bd_libros = array(
    'database' => 'externaldatabase', // nombre de la BD externa
    'username' => 'root',             // usuario para acceder a la BD externa
    'password' => 'root',             // password del usuario
    'host' => 'localhost',            // host donde se encuentra la BD
    'driver' => 'mysql',              // tipo de BD
  );

A continuación introducimos el código para limpiar las caches. Este paso es vital. De no hacerlo, podríamos encontrarnos con que Drupal intenta acceder a tablas de la nueva base de datos que no existen (semaphore, session, etc etc) y nos encontraríamos con errores del tipo:

Uncaught exception thrown in shutdown function.
 
PDOException: SQLSTATE[42S02]: Base table or view not found: 1146 Table &#039;externaldatabase.semaphore&#039; doesn&#039;t exist: ...

Y si no me creéis, mirad:
drupal external database semaphore error

El código que limpia las caches es el siguiente (lo he cogido prestado de aqui).

  $core = array('cache', 'cache_path', 'cache_filter', 'cache_bootstrap', 'cache_page');
  $cache_tables = array_merge(module_invoke_all('flush_caches'), $core);
  foreach ($cache_tables as $table) {
   cache_clear_all('*', $table, TRUE);
  }

A continuación añadimos la BD definida anteriormente y la marcamos como BD activa:

Database::addConnectionInfo('externaldatabase', 'default', $bd_libros);
  // 'MisLibros' es una clave única de la BD en este módulo, default es el target y $other_database el vector que contiene la información de conexión
 
  db_set_active('externaldatabase');
  // marcamos la bd activa como la externa...

Y podemos empezar a construir queries y mostrar resultados…

// y empezamos a construir la query..
  $sql = "SELECT * from libros";
 
  //print "Vamos a ejecutar la consulta '".$sql."'...<br />";
 
  // lanzamos la ejecución de la query y guardamos en $result todos los resultados...
  $results = db_query($sql);
 
  // mostramos los resultados...
  foreach($results as $res){
    print_r($res);
  }

Y lo único que restaría sería restaurar la BD por defecto:

// restaurar la BD activa a la Bd de Drupal...
  db_set_active();

El fichero bdexterna.module completo quedaría asi:

bdexterna.module

<?php
 
/**
 * @file
 * Nuestro primer módulo en Drupal (leccionespracticas.com)
 */
 
/**
 * Implementa hook_menu().
 */
function bdexterna_menu() {
  $items['libros/listar'] = array(
    'title' => 'LISTAR LIBROS',
    'page callback' => 'bdexterna_libros_listar',
    'access callback' => TRUE,
  );
  // con lo anterior indicamos que cuando se visite la url libros/listar
  // se muestre una página con título 'LISTAR LIBROS' cuya salida será el
  // resultado de ejecutar la funcion 'bdexterna_listar_libros', que deberemos definir
 
  return $items;
}
 
 
function bdexterna_clear_cache() {
  $core = array('cache', 'cache_path', 'cache_filter', 'cache_bootstrap', 'cache_page');
  $cache_tables = array_merge(module_invoke_all('flush_caches'), $core);
  foreach ($cache_tables as $table) {
   cache_clear_all('*', $table, TRUE);
  }
}
 
/**
 * Callback para libros/listar.
 */
function bdexterna_libros_listar() {
 
  bdexterna_clear_cache();
  db_set_active();
 
  // Definimos la base de datos externa...
  $bd_libros = array(
    'database' => 'externaldatabase', // nombre de la BD externa
    'username' => 'root',             // usuario para acceder a la BD externa
    'password' => 'root',             // password del usuario
    'host' => 'localhost',            // host donde se encuentra la BD
    'driver' => 'mysql',              // tipo de BD
  );
 
  Database::addConnectionInfo('externaldatabase', 'default', $bd_libros);
  // 'MisLibros' es una clave única de la BD en este módulo, default es el target y $other_database el vector que contiene la información de conexión
 
  db_set_active('externaldatabase');
  // marcamos la bd activa como la externa...
 
  // y empezamos a construir la query..
  $sql = "SELECT * from libros";
 
  // lanzamos la ejecución de la query y guardamos en $result todos los resultados...
  $results = db_query($sql);
 
  // mostramos los resultados...
  foreach($results as $res){
    print_r($res);
  }  
 
  // restaurar la BD activa a la Bd de Drupal...
  db_set_active();
 
  //informar del final del proceso
  drupal_set_message(t('The queries have been made.'));
}
 
?>

Y el resultado:
tutodrup10

Nótese que el resultado sale “feo” porque hacemos print’s desde el módulo. Lo que deberíamos hacer es RETURN’s de HTML (ver el siguiente punto).

El código final del módulo

He comprobado que algunas cosas de las enunciadas en apartados anteriores no son del todo ciertas. Por ejemplo, no es necesario el tema de vaciar las caches. También es interesante capturar en bloques try/except algunas cosas. Y por supuesto, no realizar presentación desde la capa de lógica (es decir, devolver cadenas HTML, no hacer prints desde el módulo…)

Esta versión del código contempla estas modificaciones.

bdexterna.info:

name = BD Externa
description = "Modulo para consultar BD externa"
package = Aprendiendo Drupal7 en leccionespracticas.com
core = 7.x

bdexterna.module:

<?php
 
/**
 * @file
 * Nuestro primer módulo en Drupal (leccionespracticas.com)
 */
 
/**
 * Implementa hook_menu().
 */
function bdexterna_menu() {
  $items['libros/listar'] = array(
    'title' => 'LISTAR LIBROS',
    'page callback' => 'bdexterna_libros_listar',
    'access callback' => TRUE,
  );
  // con lo anterior indicamos que cuando se visite la url libros/listar
  // se muestre una página con título 'LISTAR LIBROS' cuya salida será el
  // resultado de ejecutar la funcion 'bdexterna_listar_libros', que deberemos definir
 
  return $items;
}
 
/**
 * Callback para libros/listar.
 */
function bdexterna_libros_listar() {
 
  $bd_libros = array(
    'database' => 'externaldatabase', // nombre de la BD externa
    'username' => 'root',             // usuario para acceder a la BD externa
    'password' => 'root',             // password del usuario
    'host' => 'localhost',            // host donde se encuentra la BD
    'driver' => 'mysql',              // tipo de BD
  );
 
  try{
  	  Database::addConnectionInfo('externaldatabase', 'default', $bd_libros);
     db_set_active('externaldatabase');
  } 
  catch (Exception $e){
  	  db_set_active();
     return "se produjo un error al marcar la BD activa: ".$e;  
  }
  $sql = "SELECT * from libros";
  $results = db_query($sql);
  $salida = "";  
  foreach($results as $res){
    $salida = $salida."[ID] =".$res->id."  [TITULO] = ".$res->titulo;
  }  
  db_set_active();
  return $salida;
}
?>

Y la salida que conseguiremos:
drupal consulta base de datos externa resuelto

Drupal 7: acceso a bases de datos externas

En Drupal 7 hay varias formas de acceder a bases de datos externas.

Método 1: Añadir bases de datos adicionales en la configuración (settings.php)

<?php
$databases = array();
$databases['default']['default'] = array(
  // Drupal's default credentials here.
  // This is where the Drupal core will store it's data.
);
$databases['my_other_db']['default'] = array(
  // Your secondary database's credentials here.
  // You will be able to explicitly connect to this database from your modules.
);
?>

Y asi es como realizaríamos el cambio de base de datos activa en el módulo…

<?php
// Use the database we set up earlier
db_set_active('my_other_db');
 
// Run some queries, process some data
// ...
 
// Go back to the default database,
// otherwise Drupal will not be able to access it's own data later on.
db_set_active();
?>

Método 2: definir las bases de datos adicionales “al vuelo” en el propio módulo

Esta forma es la más adecuada si las queries a la base de datos secundaria provienen únicamente de un módulo.

Puedes leer la información de la capa de abstracción para bases de datos de drupal aqui: http://api.drupal.org/api/drupal/includes%21database%21database.inc/group/database/7

<?php
  $other_database = array(
      'database' => 'databasename',
      'username' => 'username', // assuming this is necessary
      'password' => 'password', // assuming this is necessary
      'host' => 'localhost', // assumes localhost
      'driver' => 'mysql', // replace with your database driver
  );
  // replace 'YourDatabaseKey' with something that's unique to your module
  Database::addConnectionInfo('YourDatabaseKey', 'default', $other_database);
  db_set_active('YourDatabaseKey');
 
  // execute queries here
 
  db_set_active(); // without the paramater means set back to the default for the site
  drupal_set_message(t('The queries have been made.'));
?>

Drupal 7: install module translation [SOLVED]

To install a Drupal 7 module translation, this is, a .po file (for instance, the fullcalendar spanish translation) go to Administration » Configuration » Regional and language (/admin/config/regional/translate).

Click the ‘Import’ tab and use the ‘Import translation’ form.

drupl

What if there are still some strings translations missing? No worries, just go to /admin/config/regional/translate/translate and add them (by hand).

Tip: use Localization update module 😉

Drupal 7: display language switcher in template [SOLVED]

Some days ago we learnt to display a search box in Drupal template files, and according to that post and the Drupal handbook rendering a language switcher block in your theme templates should be easy.

This should work:

<?php
$block = module_invoke('locale', 'block_view', 'language');
print render($block);
?>

Unfortunately, in my D7 it does not. What does work is this:

Add to template.php:

<?php
function block_render($module, $block_id) {
  $block = block_load($module, $block_id);
  $block_content = _block_render_blocks(array($block));
  $build = _block_get_renderable_array($block_content);
  $block_rendered = drupal_render($build);
  return $block_rendered;
}
?>

Add to page.tpl.php

<?php
  print block_render('locale', 'language');
?>

Now that the language switcher is rendering, you might want to customize its appeareance 😉

Drupal 7: Styling language switcher

Language switcher is a Drupal module which allows to display a block that allows visitors to choose between the languages in which your site is available (check /admin/config/regional/language to list the available languages in your Drupal site).

By default, it produces an unordered list with each item in a new line and a bullet before each item. Not a big fan of this look.

With the following CSS code you’ll have an inline language switcher, with li items separed by “/”.

/* language switcher */
ul.language-switcher-locale-url{
list-style: none;
display: inline;
}
 
ul.language-switcher-locale-url li{
display: inline;
}
 
ul.language-switcher-locale-url li:after{
   content: "/";
}
 
ul.language-switcher-locale-url li:last-child:after{
   content: "";
}

For more advanced styling options, check this link.

Drupal 7: Path breadcrumbs in Views page [SOLVED]

Path breadcrumbs module allows you to easily add breadcrumbs to your Drupal site. The module lacks of documentation, but they offer this image as an example:

path_breadcrumbs

And there is a videotutorial as well.

The above show how to configure breadcrumbs in nodes, but… how to display breadcrumbs in Views pages?

This struggled me for a while, because I was taking the (limited) instructions too literally.

To create a breadcrumb to a views page display that has a path like “/path/to/my-view” just use that as the path (as it isn’t an alias). To know the path, go to your view and edit it (admin/structure/views/view/VIEWNAME/edit) and refer to the Page Settings – Path value.

Also, leave the “Arguments” and “Selection rules” empty, then setup your breadcrumbs as you did with nodes.

Let’s see an example. First, the path to the view:

Path breadcrumbs in views

Path breadcrumbs in views

And the path breadcrumbs configuration:

drupal 7 Path breadcrumbs in views

drupal 7 Path breadcrumbs in views

And here is the resulting breadcrumb. Remember that your theme’s page.tpl.php must print $breadcrumb or you will not see breadcrumbs at all…

path_breadcrumbs_in_views_page_2