Aliados de Views en Drupal

Posteado el por: moncada.nicolas
FacebookTwitter
Ejemplo de configuración de views

En post anteriores he mencionado muchas veces el módulo Views, la razón es que es uno de esos modulos impresindible en cualquier proyecto (tanto así que fue incluido en el core de Drupal 8). Sin embargo, en muchos proyectos existirán requerimientos que lamentablemente Views no podrá suplir, salvo que como desarrolladores hagamos algo al respecto. Algunos optan por instalar Views PHP y agregar código en las mismas vistas, siendo esta una de las peores prácticas. Otros optan por hacer sus vistas manualmente (No usando Views). Pero existen otras opciones usando el método "Drupal Way", una de ellas es simplemente extendiendo la funcionalidad de Views para resolver nuestros requerimientos. De esto último tratará este post, en donde resolveremos un problema con este método.

Antes de empezar ¿Que hace Views?

Nos permite, en la interfaz de administración, hacer vistas con la información que existe en nuestra base de datos (u otra fuente), definiendo que datos mostrar, como filtrar, como ordenar, etc. Esto se logra gracias a varios factores, por un lado Views se encarga de "mapear" lo que viene ya por defecto en Drupal: nodos, users, comentarios, taxonomias y otros. Y el módulo Entity API se encarga de "mapear" todas las entidades restantes creadas por otros módulos o por uno mismo. Este "mapeo" se logra por medio del hook_views_data en donde se informa de la existencia de las tablas y el como interpretarlo. También existe el hook_views_data_alter, que nos permitirá alterar la información. Ya sabiendo esto procederemos a analizar el problema.

El Problema a resolver

Supongamos que se debe desarrollar un proyecto en el que interactuan muchos usuarios. En éste se solicita que cada usuario pueda, opcionalmente, agregar su nombre completo en el perfil de usuario. Por otra parte se solicitan vistas de responsabilidades y que en esta se imprima el Nombre Completo o el username en caso de no tenerlo. En estas mismas se debe agregar un único filtro que busque por el Nombre Completo o por username.

Para resolver esto vamos a ir paso a paso:

Paso 1: ¿Donde registramos el Nombre Completo? Usemos fields

A las entidad usuario, en Drupal 7, podemos extender su información usando Fields. Entonces vamos a crear un campo de texto simple para registrar el nombre completo, cuyo nombre de máquina será "field_user_nombre_completo" (Pueden usar cualquiera, pero deben tenerlo en cuenta ya que lo usaremos después).

Paso 2: Creamos nuestro módulo

Necesitamos tener un módulo custom para hacer el pedido (Personalmente esto lo adjunto al módulo base del proyecto). Como ejemplo nuestro módulo se llamará "mimodulo" y creamos los siguientes archivos:

  • mimodulo.info
  • mimodulo.module

En mimodulo.info definen el nombre del módulo y la descripción. También agregan los archivos que vamos a crear en el futuro.

  • name = MIModule
  • description = Modulo base para nuestro ejemplo.
  • core = 7.x
  • package = MIModule
  • files[] = views/handlers/mimodulo_nombre_completo_views_handler_field.inc
  • files[] = views/handlers/mimodulo_nombre_completo_views_handler_filter.inc

En mimodulo.module implementan el hook_views_api().

<?php
/**
 * Implements hook_views_api().
 */
function mimodulo_views_api() {
  return array(
   
'api' => 3,
   
'path' => drupal_get_path('module', 'mimodulo') . '/views',
  );

?>

Paso 3: Extendiendo la información de usuario en Views.

Creamos el archivo mimodulo.views.inc en la carpeta views del módulo en donde vamos a definir nuestro nuevo campo y filtro con nombre completo.

  • mimodulo.info
  • mimodulo.module
  • views/mimodulo.views.inc

En dicho archivo adjuntamos el siguiente código

<?php
/**
 * Implements hook_views_data_alter().
 */
function mimodulo_views_data_alter(&$data) {
 
$field_name = 'field_user_nombre_completo';
 
$data['users']['mimodulo_nombre'] = array(
   
'title' => 'Nombre Usuario (MiModulo)',
   
'help' => 'Nombre del usuario, si tiene nombre completo lo usa.',
   
'field' => array(
     
'title' => 'Nombre Usuario (MiModulo)',
     
'field' => 'name',
     
'handler' => 'mimodulo_nombre_usuario_handler_field',
     
'additional fields' => array(
       
'nombre_completo' => array(
         
'table' => 'field_data_' . $field_name,
         
'field' => $field_name . '_value',
        ),
      ),
    ),
   
'filter' => array(
     
'field' => 'name',
     
'table' => 'users',
     
'handler' => 'mimodulo_nombre_usuario_handler_filter',
     
'allow empty' => 1,
     
'additional fields' => array(
       
'nombre_completo' => array(
         
'table' => 'field_data_' . $field_name,
         
'field' => $field_name . '_value',
        ),
      ),
    ),
  );
}
?>

Lo que hacemos es agregar otro campo a la tabla users que claramente no existe, es por eso que especificamos que es en base del campo name. También agregamos el campo adicional que corresponde al field_user_nombre_completo (Esto realizará el respectivo join).

Paso 4: Definiendo el Handler Field

Como campo hemos definido al handler mimodulo_nombre_usuario_handler_field. Vamos a crear el archivo con dicho nombre en la ruta views/handlers.

  • mimodulo.info
  • mimodulo.module
  • views/mimodulo.views.inc
  • views/handlers/mimodulo_nombre_usuario_handler_field.inc

Dicho archivo tendrá lo siguiente:

<?php
 
class mimodulo_nombre_usuario_handler_field extends views_handler_field_user_name {
  function
set_value($values, $field, $value) {
   
$alias = isset($field) ? $this->aliases[$field] : $this->field_alias;
    if (isset(
$values->{$alias})) {
     
$values->{$alias} = $value;
    }
  }
  function
render_link($data, $values) {
   
$nombre_completo = $this->get_value($values, 'nombre_completo');
    if (
$nombre_completo) {
     
$this->set_value($values, NULL, $nombre_completo);
    }
    return
parent::render_link($data, $values);
  }
}
?>

Lo que hacemos es crear la clase extendiendo la que usa el campo username. Así reutilizamos la configuraciones que suele darnos el campo, por ejemplo imprimir el campo como link al perfil del usuario.

Paso 5: Definiendo el Handler Filter

Este es el handler más complejo, necesitamos que no solo filtre por el nombre completo sino que también por el username. ¿Por qué? Porque pueden haber usuarios que no hayan definido su nombre completo (También fue pedido). Al igual que el handler field, creamos el archivo para el filter.

  • mimodulo.info
  • mimodulo.module
  • views/mimodulo.views.inc
  • views/handlers/mimodulo_nombre_usuario_handler_field.inc
  • views/handlers/mimodulo_nombre_usuario_handler_filter.inc

En este archivo adjuntamos el siguiente código

<?php
class mimodulo_nombre_usuario_handler_filter extends views_handler_filter_string {
  var
$additional_fields = array();
  var
$fields = array();
  var
$aliases = array();
  function
construct() {
   
parent::construct();
   
$this->additional_fields = array();
    if (!empty(
$this->definition['additional fields'])) {
     
$this->additional_fields = $this->definition['additional fields'];
    }
  }
 
// Solo puede haber dos tipos de operaciones. El que contiene cualquiera o todas las palabras.
 
function operators() {
   
$operators = array(
     
'word' => array(
       
'title' => t('Contains any word'),
       
'short' => t('has word'),
       
'method' => 'op_word',
       
'values' => 1,
      ),
     
'allwords' => array(
       
'title' => t('Contains all words'),
       
'short' => t('has all'),
       
'method' => 'op_word',
       
'values' => 1,
      ),
    );
    return
$operators;
  }
  function
query() {
   
$this->ensure_my_table();
   
$this->add_additional_fields();
   
$field_name = "$this->table_alias.$this->real_field";
   
$this->fields[] = $field_name;
   
$info = $this->operators();
    if (!empty(
$info[$this->operator]['method'])) {
     
$this->{$info[$this->operator]['method']}($this->fields);
    }
  }
 
// Copiamos el método op_word y lo modificamos para soportar multiple campos.
 
function op_word($fields) {
   
$or = db_or();
   
// Don't filter on empty strings.
   
if (empty($this->value)) {
      return;
    }
   
preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' ' . $this->value, $matches, PREG_SET_ORDER);
    foreach (
$fields as $field) {
     
$where = $this->operator == 'word' ? db_or() : db_and();
      foreach (
$matches as $match) {
       
$phrase = false;
       
// Strip off phrase quotes
       
if ($match[2]{0} == '"') {
         
$match[2] = substr($match[2], 1, -1);
         
$phrase = true;
        }
       
$words = trim($match[2], ',?!();:-');
       
$words = $phrase ? array($words) : preg_split('/ /', $words, -1, PREG_SPLIT_NO_EMPTY);
        foreach (
$words as $word) {
         
$placeholder = $this->placeholder();
         
$where->condition($field, '%' . db_like(trim($word, " ,!?")) . '%', 'LIKE');
        }
      }
     
$or->condition($where);
    }
    if (!
$or) {
      return;
    }
   
// previously this was a call_user_func_array but that's unnecessary
    // as views will unpack an array that is a single arg.
   
$this->query->add_where($this->options['group'], $or);
  }
 
// Se copia el metodo add_additional_fields de views_handler_field y lo modificamos.
 
function add_additional_fields($fields = NULL) {
    if (!isset(
$fields)) {
     
// notice check
     
if (empty($this->additional_fields)) {
        return;
      }
     
$fields = $this->additional_fields;
    }
   
$group_params = array();
    if (
$this->options['group_type'] != 'group') {
     
$group_params = array(
       
'function' => $this->options['group_type'],
      );
    }
    if (!empty(
$fields) && is_array($fields)) {
      foreach (
$fields as $identifier => $info) {
        if (
is_array($info)) {
          if (isset(
$info['table'])) {
           
$table_alias = $this->query->ensure_table($info['table'], $this->relationship);
          }
          else {
           
$table_alias = $this->table_alias;
          }
          if (empty(
$table_alias)) {
           
debug(t('Handler @handler tried to add additional_field @identifier but @table could not be added!', array('@handler' => $this->definition['handler'], '@identifier' => $identifier, '@table' => $info['table'])));
           
$this->aliases[$identifier] = 'broken';
            continue;
          }
         
$params = array();
          if (!empty(
$info['params'])) {
           
$params = $info['params'];
          }
         
$params += $group_params;
         
$this->fields[] = $table_alias . '.' . $info['field'];
         
$this->aliases[$identifier] = $this->query->add_field($table_alias, $info['field'], NULL, $params);
        }
        else {
         
$this->aliases[$info] = $this->query->add_field($this->table_alias, $info, NULL, $group_params);
        }
      }
    }
  }
}
?>

Esta clase extiende de views_handler_filter_string para reutilizar algunos métodos, sin embargo tenemos que agregar varias modificaciones para hacer compatible el campo adicional y que busque en más de una tabla. Ya con esto podremos usar el filtro que busca tanto en el Campo field_user_nombre_completo como en el username.

Resultado

Crearemos una vista de usuarios, imprimiendo el nombre completo y username. También agregaremos el filtro. Al agregar un campo en administración, nos aparecerá lo siguiente:

Vemos el nuevo campo que hemos creado en el grupo User. Lo seleccionamos y debería mostrar algo así:

En esta lista se visualiza que el usuario abogado1 no tiene asignado un nombre completo. Si es el caso de los otros usuarios. Ahora agregaremos el filtro y filtraremos por la palabra "Usuario".

El sistema lo encuentra en el Campo field_user_nombre_completo. Y si en cambio buscamos por "administrador":

El sistema lo encuentra en el campo username.

Así podrán asegurarse de que el filtro siempre encontrará a los usuarios en el sistema, sea usando el username o el Nombre Completo.

En otra oportunidad veremos otros ejemplos más complejos en donde haremos que views sea un mejor aliado para nuestros proyectos. Espero que esto les sea de utilidad.

moncada.nicolas

Últimos Comentarios

Blog

En esta sección compartimos algunas experiencias concretas para la comunidad de desarrolladores de código abierto

Hace un tiempo atrás, Transbank (la empresa detrás de Webpay) había habilitado una nueva modalidad para integrar su sistema de pago con nuestros sitios. Se trata de un servicio web que utiliza el protocolo SOAP, haciendonos más fácil la integración con respecto a su antecesor. Y para soportar esto en Drupal, se ha publicado una nueva versión del módulo Webpay y aquí veremos como funciona.

Posteado el por: moncada.nicolas

Para la junta de Drupal (realizado el 20 de Diciembre del 2016) he presentado el desarrollo de un módulo pensado para la comunidad de Drupal Chile, llamado Badge. El objetivo del módulo es crear logros o insignias y asignarlo a usuarios u otras entidades de nuestro sitio.