Translate to

Buscar

5 de octubre de 2011

HTML + Javascript para usar cualquier framework

Lea el artículo original en Puroguramu.

13 de septiembre de 2011

Por qué la Programación Orientada a Objetos es mala para Drupal


artículo de Matt Buttcher (2010/10/13), traducción libre de Antonio Kobashikawa
Nota de traductor: Aunque Drupal se ha convertido en un movimiento poderoso, parece que, en el interés por capitalizar algunos negocios asociados, se han tomado decisiones que están distanciando al producto de sus objetivos primarios para la comunidad. Algunas personas han estado notando eso desde hace algún tiempo y me parece interesante el tema.
He conducido o contribuido a docenas de proyectos Open Source. Y todo mi código ha sido siempre Orientado a Objetos, con una excepción. Esa excepción es Drupal. Java, Python, PHP, y aún OO Perl... soy un desarrollador que teje con la lana del OO. Así que esto puede sonar chocante para cualquiera que me conoce, pero mi argumento es que la OO es mala para Drupal.

Créame que decirlo no es algo notable en mi vida de desarrollador. Me siento como si le dijera a mi niño que debe faltar al colegio, sólo porque tal nivel de sofisticación no es necesaria en su vida. Y supongo que ese es, precisamente, el sentido de lo que estoy diciendo.

1. La compejidad es la antitesis de la manera Drupal

¿Por qué tiene Drupal miles de modulos mientras otros CMS -muchos más antiguos que Drupal-, tienen mucho menos? La razón es tan clara como el día. Los módulos de Drupal son simples de desarrollar. Y esto es no sólo porque los hooks son fáciles de usar, sino porque el API está bastante enfocado en hacer fáciles las cosas difíciles.

Pero una parte del camino está al revés: las cosas faciles se han vuelto más y más difíciles.

Me ocurrió durante una conversación con un amigo mío. Él es un excelente desarrollador PHP que había trabajado con Drupal más tiempo que yo. Se lamentaba que Drupal 4.7 había sido mucho mejor que Drupal 6. Cuando le pregunté por qué. empezó a enumerar las formas que que Drupal se ha vuelto más complejo con los años. Él no es un egresado deseando que la vida fuera de la universidad sea tan buena como cuando vivía en su dormitorio. Se trata de un desarrollador experimentado, y sus ideas eran profundas.

Alguna vez, fue posible para un desarrollador promedio conocer Drupal. Ahora, poca gente (quizás 5-10) entienden todos los sistemas del core de Drupal. Ese punto realmente me atrapó. No es sólo que el código base sea complejo. Es la plétora de subsistemas, de formas de pasarse los datos, de convenciones especializadas, y aún de la terminología. Drupal es un hipopótamo conceptual.

Introducir OO en el cuadro no es una simplificación. De hecho, es una inyección masiva de complejidad. Muchos desarrolladores Drupal están ahora mismo tratando de aprender un nuevo conjunto de prácticas. La profundidad conceptual de Drupal 7 es abismal. Nodos, campos, entidades, usuarios, comentarios, menús, los otros menús, vistas, las otras vistas, queries, filtros, acciones, hooks, funciones de tema, preprocesadores, templates, módulos... y para Drupal 8, ¡agregaremos más! contextos, plugins... Estos nuevos subsistemas OO tendrán (o ya tienen) nuevas convenciones y nueva terminología y nuevas prácticas de programacion. ¿Cómo puede un recién llegado entender esto?

¿Acaso es "mantenganlo en perspectiva" la respuesta? ¿Qué vamos a sacar de esta nueva tendencia? ¿Un mejor sistema, a qué costo? Contribuyentes y usuarios. Renunciarán. Drupal se está volviendo el sueño del consultor ocioso: toneladas de jerga, montones de complejidad, y una pila de llamadas que requiere una pizarra del tamaño de una pared para diagramarla.

2. Privado, protegido, y filosóficamente discordante

El debate sobre si deben o no usarse las palabras clave private y protected en Drupal ha sido revelador para mí. No porque porque piense que la gente este siendo banal. De hecho, es lo opuesto. Pienso que esta siendo profunda.

Nosotros, la gente con mentalidad OO, no podemos entender que alguien pueda poner en tela de juicio la utilidad de las variables protegidas. ¡Herejía! Y tenemos nuestro pequeño arsenal de argumentos en favor de propiedades privadas incluso para proyectos FOSS (Free and Open Source Software). Pero cuando reflexionamos sobre ello, el propósito de los miembros privados y protegidos de un objeto es mantener otras cosas (léase: programadores cargosos con "grandes ideas") de modificar su preciosa data.

Drupal, simplemente, no va por ahí.

De hecho, la sentencia de muerte (bienvenida) del anti-pattern static vino en la forma de drupal_static(), el cual ahora nos provee de acceso no controlado al único mecanismo que teníamos para controlar datos en el ámbito global. ¡Las funciones estáticas son ahora tan públicas como las variables globales!

¿Qué es lo que ilustra esto? Durante mucho tiempo, Drupal ha permitido que los desarrolladores puedan dispararse a sí mismos (o sus websites) en el pie, haciendo cosas estúpidas. Drupal nunca ha tratado de proteger a un desarrollador de sí mismo, o de proteger mi API de las intenciones insidiosas de otros desarrolladores. Lo que tenemos aquí es una diferencia filosófica profunda entre las arquitecturas Orientadas a Objeto y la disposición de Drupal al laissez-faire (dejar hacer, dejar pasar). ¿Por qué tratar ahora de mezclar los contrarios?

3. Un cúmulo mal hecho de técnicas sin forma

¿Por qué Drupal hace una distinción entre themers y desarrolladores? Una gran razón es porque las dos capas requieren distintas arquitecturas y convenciones. Los templates PHP fueron escritos alrededor de 1997 (¡Cómo lo deseaba Rasmus!). Esto if; esto else; esto print. EOF. Los módulos, por otro lado, son tan manejados por datos que, si un bit de HTML se cuela en su módulo (y no es una función de tema), podrá esperar repercusiones en su cola de issues.

Así que hay dos técnicas diferentes que el Rock Star (este... ¿dije eso?) Drupal developer tiene que dominar. ¿Es así? Bueno... en realidad, hay más.

Hay hooks, una casi-novedad en la esfera de la programación PHP. Y arrays anidados -la Única Estructura de Datos para Gobernarlos a Todos, según la forma de ver de algunas personas. Lo más cercano que haya visto a los arrays FAPI, fuera de Drupal, es el LDAP API... y eso es como comparar a los Yankees con un equipo de las ligas menores. Hay archivos .info, que son casi como archivos PHP .ini. Hay convenciones de nombres importantes para programar en funciones, extensiones de archivos, directorios, tablas de base de datos, sentencias SQL, arrays de renderizado, y cadenas. Todos son diferentes, por supuesto. Donde uno puede esperar cinco funciones, hay solamente una... con un argumento $op. Estas no son prácticas estándar de programacion fuera de Drupal. Ya estábamos esperando mucho de nuestros desarrolladores.

Drupal ya tiene una matizada mezcla procedural/funcional de estilos (con alguna teoría Orientada a Aspectos que la sazona). Y tiene una serie de convenciones profundamente arraigadas, aunque incipientes, construidas en torno a esas técnicas. ¿Y ahora estamos hablando de agregar otra importante nueva técnica de codificación? ¿Cuántos nuevos libros ha visto sobre teoría y práctica OO? ¿Y como es que se mezclará eso con nuestra arquitectura existente?

Pero, en serio... ¿cuál es el problema? Hay dos razones:
Consistencia: ¿Cómo puede uno construir algo que se aproxime a una arquitectura consistente cuando siempre hay al menos tres teorías que compiten sobre cómo se debería hacer?
Reutilización: No estoy hablando de código. Estoy hablando de wetware (la mente humana). A medida que nuestro estilo de codificación se aparte de la corriente principal, se vuelve más difícil para los neofitos imaginar cómo hacer las cosas. La OOP Drupalesca está introduciendo otra anomalía que el nuevo usuario debe entender.

4. Refactorizando: El paso en falso, sobre aguas de tiburones, de los proyectos FOSS

¿Cómo matar un proyecto Open Source? Reescriba toda la cosa. Y el gran ejemplo FOSS por excelencia para esto es... ¡PERL!

Ahora que hemos iniciado el sendero del OO, todo parece ser candidato a refactorizar. ¿Qué tan fuerte reclama la gente la fecha límite para Drupal 7? ¿Y qué tanto y tan fuerte reclamará la gente antes de que se distraigan con algo brillante... y más simple... y que se perciba como "más activo"? ¿Cuánta gente está dispuesta a tolerar un gran cambio en el API cuando podría ser más facil simplemente aprender algo nuevo?

Esta es una razón significativa, pienso, para resistirse activamente a saltar sobre la catarata de la reescritura API a la OO.

5. La controversia consume tiempo

Por largo tiempo, nunca entendí porque la gente lee las revistas que se venden junto a las cajas registradoras de los supermercados. ¿A quién le importa si Britney deja a su niño en el asiento de atrás mientras se va a comprar alimento para mascotas? ¿Por qué es tan importante que dos ricas celebridades de Hollywood se estén separando luego de tres meses de matrimonio?

Pero la respuesta me golpeó la duodécima vez que refrescaba Twitter durante el DrupalCon de Copenhage: los humanos (incluido yo) aman la controversia. Tanto así que nos distraemos fácilmente con ella -lejos de las cosas por las que, aparentemente, nos preocumamos más.

Una vez que empezamos a mezclar los principales estilos de programación, como estamos empezando a hacer, abrimos una gran lata de gusanos. Ahora sera The Gang of Four (la Banda de los Cuatro, los chicos Orientados a Objetos, que son de lejos menos malvados que el grupo de Mao) vs Kernigan y Ritchie (los creadores de C). Y de tiempo en tiempo veremos otras vez choques que generarán controversia fresca. La potencial pérdia de tiempo ya se está manifestando (¿en este artículo?).

Debemos boicotear OOP?

¿Cuál es el balance final? ¿Debemos prohibir por decreto toda OO? No lo creo. Para algunos casos, creo que es probablemente la elección adecuada. Por ejemplo, pienso que el caracteristicas encadenadas del nuevo API de base de datos es una gran manera de cumplir efectivamente con los objetivos de Drupal. Aún así, me encuentro reticente a recomendar algún otro lugar donde deba ponerse OO como lo principal.

Seguro que vemos muchos ejemplos de módulos contribuídos que usan OO, y debemos tratar a los módulos contribuídos como siempre hemos hecho: como los soberanos de las contribuciones.

Pero, para el core, es la hora de contener los esfuerzos OO, y dar un buen vistazo, aunque difícil, a como queremos mantener el API de Drupal... bueno... drupalizado.

15 de julio de 2011

Acceder a los valores de un nodo con javascript


Una forma simple de hacer esto, es a través de Drupal.settings, que es un objeto siempre disponible.

Para agregar el nodo a Drupal.settings, lo hago en alguna función adecuada, por ejemplo en la implementación del hook_form_alter(), ya que allí se hacen las alteraciones de los formularios de edición de nodos.

Por ejemplo:

function mymodule_form_alter(&$form, &$form_state, $form_id) {
  switch ($form_id) {
    case 'my_node_form':
      ...
      $node = node_load($form['nid']['#value']);

      // port to javascript
      if ($node) {
        drupal_add_js(array('node' => $node), 'setting');
      }

      ...
      break;
    ...
  }
}

Luego, en algún javascript que se invoque para el tipo my_node, se puede usar algo como:

  var node = Drupal.settings.node;

  var nid = Drupal.settings.node.nid;
  var node_my_field = node.field_my_field[0].value;


Si usa Firebug, podrá encontrar el objeto Drupal en la pestaña DOM y navegar por los otros elementos disponibles en node.

13 de julio de 2011

Resolviendo el cambio de alias con views_customfield

Views Custom Field es un módulo que permite colocar PHP como contenido de un campo de una vista.

Normalmente, uno usa la variable $data para acceder a los valores de los campos que se hayan definido.

Por ejemplo, $data->nid permite obtener el valor de nodo y haciendo print_r($data) uno puede explorar otros valores que usar.

Pero hay un problema con los valores de los campos CCK. El nombre de los campos que contienen sus valores en $data corresponden a aliases que no son constantes. Por ejemplo, cuando usa filtros expuestos, podrá encontrar que ese nombre de campo en $data cambia según qué filtro esté aplicando.

Una forma de solucionar esto es usar $this->view, también disponible, para obtener el alias adecuado en cada caso. Es decir, en lugar de usar $data->some_field_alias usar $data->{$this->view->field['field_id']->field_alias}

Este es un ejemplo real:

<?php
$cliente_nid = $data->{$this->view->field['field_client_nid']->field_alias};
$pieza = $data->{$this->view->field['field_job_pieza_value']->field_alias};
$descuento = $data->{$this->view->field['field_job_descuento_value']->field_alias};
$factor = $data->{$this->view->field['field_job_factor_value']->field_alias};
$cantidad = $data->{$this->view->field['field_job_cantidad_value']->field_alias};
return mymodule_get_tarifa_final($cliente_nid, $pieza, $descuento, $factor, $cantidad);
?>

Referencias

12 de julio de 2011

Aplicando un patch en Windows

El comando patch, disponible en Linux, usualmente no está disponible en Windows. Cuando uno quiere aplicar algún patch, suele encontrar una traba de solución no tan fácil.

En lugar de las recomendaciones de instalar Cygwin, o algo parecido, alguna vez use Aptana (un derivado de Eclipse) para aplicar un parche.

Hoy estoy instalando Simpletest y la documentación indica aplicar patch -p0 < path/to/simpletest/D6-core-simpletest.patch y ya no recuerdo cómo lo hice con Aptana :-S Tampoco quisiera instalar Cygwin sólo para eso.

Esta vez, noto que tengo instalado Git para Windows (https://git-scm.com/download/win), hago click derecho sobre el directorio drupal y elijo Git Bash Here. Hago patch --help para comprobar que cuento con el comando y luego:

$ patch -p0 < sites/all/modules/simpletest/D6-core-simpletest.patch
patching file `install.php'
Hunk #1 succeeded at 19 (offset -1 lines).
patching file `includes/bootstrap.inc'
Hunk #1 succeeded at 1111 (offset -1 lines).
Hunk #3 succeeded at 1343 (offset -1 lines).
patching file `includes/common.inc'
Hunk #1 succeeded at 530 (offset -1 lines).
Hunk #2 succeeded at 2663 (offset 2 lines).
Hunk #3 succeeded at 3805 (offset 6 lines).

Ojalá le sirva de ayuda.

8 de julio de 2011

Generación modular de HTML

Actualmente, cuando desarrolla un sitio usando cierto framework, no lo puede modificar con otro. Si está metido en esto, puede parecer obvio y hasta natural. Pero, si lo vemos en perspectiva, es algo así como una limitación por diseño. Algo que se podría mejorar.
Lea el artículo original en Puroguramu: Generación modular de HTML

30 de junio de 2011

Redirigir, excepto si se está recuperando contraseña

El requerimiento es que los usuarios logueados sean redirigidos a la página proyectos-nuevos.

Eso se puede implementar usando actions/triggers.

Es decir, el action redirigir a proyectos-nuevos cuando ocurre el trigger login del usuario.

Cuando un usuario solicita recuperar su contraseña, se le facilita un enlace que conduce a una página de acceso provisional y luego, normalmente ocurre una redirección a la edición de su cuenta.

Sin embargo, la redireccion con actions/triggers interfiere con esto y conduce también a este caso hacia proyectos-nuevos.

Para solucionarlo, es mejor usar el módulo rules.

Instalado rules, en admin/rules/trigger se puede agregar una nueva regla que conduzca a proyectos-nuevos cuando ocurra el login.

Produce el mismo efecto que al usar actions/triggers, incluso el de interferir con la redirección de recuperación de contraseña.

Para prevenir la redirección en ese caso, se puede agregar una condición PHP a la regla:

if (arg(0) == 'user' && arg(1) == 'reset') {
  return FALSE;
} else {
  return TRUE;
}

Referencias

Nombre real en el email de registro

Normalmente, en el proceso de registro con Drupal, el usuario recibe un email que se refiere a él usando su nombre de usuario.

Si entra a admin/user/settings puede ver que existen ciertos tokens propios, entre los que esta !username.

¿Y si se desea mostrar el nombre real? Por ejemplo, Antonio Kobashikawa, en lugar de akobashikawa.

Una forma es usando hook_mail_alter(). Por ejemplo:

// Custom email registration message
function misc_mail_alter(&$message) {
  // http://drupal.org/files/issues/realname_token_system_mail_notifications.patch
  $midparts = explode('_', $message['id'], 2);
  if ($midparts[0] == 'user' || $midparts[0] == 'logintoboggan') {
    switch ($midparts[1]) {
      case 'register_no_approval_required':
        $realname = trim($_POST['field_profile_nombres'][0]['value']) . " " . trim($_POST['field_profile_apellidos'][0]['value']);
        $message['subject'] = str_replace('!realname', $realname, $message['subject']);
        $message['body'][0] = str_replace('!realname', $realname, $message['body'][0]);
        break;
      case 'register_admin_created':
      case 'register_pending_approval':
      case 'password_reset':
      case 'status_blocked':
      case 'status_activated':
      case 'status_deleted':
      case 'logintoboggan_resend_validation':
        $account = user_load(array('mail' => $message['to']));
        if ($account) {
          $realname = isset($account->realname) ? $account->realname : realname_make_name($account);
        } else {
          $realname = $message['params']['account']->name;
        }
        $message['subject'] = str_replace('!realname', $realname, $message['subject']);
        $message['body'][0] = str_replace('!realname', $realname, $message['body'][0]);
        break;
    }// switch
  }// if
}

De ese modo, estará disponible también el token !realname.

Los ids se pueden encontrar en _user_mail_text() de modules/user/user.module.

field_profile_nombres y field_profile_apellidos son los nombres de los campos que definí en el tipo de contenido profile, que asocio al registro usando el módulo content_profile.

realname_make_name() es una función de realname, que es un módulo que ayuda a que el nombre real sea mostrado al ver la cuenta del usuario.

Referencias

Entorno Drupal en Linux Mint

Luego de usar Ubuntu Natty Narwhal por algunas semanas, estoy probando Linux Mint 11. Felizmente es un derivado de Ubuntu, así que puedo usar lo que aprendí allí. Los pasos que describo a continuación servirían también en Ubuntu Natty Narwhal.
Una de las razones por la que decidí probar Linux Mint fue la incomodidad de usar el escritorio Unity.  Ahora, en Linux Mint, siento que las cosas son más simples y claras. 

Para instalar un entorno para trabajar con Drupal:

Usé Synaptic Package Manager marcando los siguientes paquetes (y respondiendo afirmativamente a sus requerimientos de dependencias):

apache2
php5
mysql-server
mysql-client
php5-mysql

Y otras que me parecieron adecuadas (aunque no sabría decir cuáles son estríctamente necesarias):

php5-gd
php5-curl
php5-xmlrpc
php5-sqlite
php5-mcrypt
php5-xdebug
php5-adodb
php5-geoip
php-pear

En el caso de mysql-server, en algún punto solicitará la contraseña del root para proseguir.

Como instalé primero apache2, luego de instalar lo demás, reinicié el servicio:

sudo service apache2 restart

Apareció un mensaje indicando faltaba especificar 'fully qualified domain name'.

Edité /etc/apache2/apache2.conf:

sudo gedit /etc/apache2/apache2.conf

y agregué la línea:

ServerName localhost

Reinicié el servicio y probé crear /var/www/phpinfo.php:

<?php phpinfo();

Y comprobé que funcionara en http://localhost/phpinfo.php

PhpMyadmin
Para instalar phpmyadmin, fui otra vez a Synaptic e indiqué instalar:

libapache2-mod-auth-mysql
phpmyadmin

Módulo Rewrite
Para comprobar si está habilitado el módulo rewrite:

apachectl -M

Para habilitar el módulo rewrite:

sudo a2enmod rewrite

Y reiniciar apache2

Dropbox
Encuentro práctico tener un directorio web en mi dropbox: /home/rulo/Dropbox/htdocs/drupal

Para que funcionaran bien, seguí algunos pasos extra:

En /etc/apache2/apache2.conf:

Alias /dropbox/ "/home/rulo/Dropbox/htdocs/"

  Options Indexes FollowSymLinks Includes ExecCGI
  AllowOverride All
  Order allow,deny
  Allow from all

Y, quizás porque está dentro de un directorio con alias, en el .htaccess de cada site drupal, tengo una línea similar a:

RewriteBase /dropbox/drupal/mysite

Referencias

29 de junio de 2011

Geany con Zen Coding

Geany es un editor de texto disponible para Linux y también para Windows.

Zen Coding es una utilidad que permite generar HTML a partir de una expresión abreviada. Por ejemplo, resulta práctico que algo como 'html:xs' pueda expandirse en el esqueleto completo de un documento XHTML.

Hay plugins que permiten tener la funcionalidad de Zen Coding en editores como Notepad++ o gEdit. Para Geany, está tardando un poco. Pero hay una manera, sugerida en el artículo http://damour-ua.com/?p=65 que puede ayudar mientras tanto.
  1. Descargar el archivo http://damour-ua.com/files/zen.zip y descomprimirlo en algún lugar. Por ejemplo, yo lo coloqué como /home/rulo/Bin/zen
  2. En Geany, entrar a Edit, Format, Send Selection To, Set Custom Commands y colocar allí una línea como:
    python /home/rulo/Bin/zen/1.py
  3. Para probar la funcionalidad, en un documento de Geany, ingrese una expresión zen coding, como html:xs, selecciónela y pulse CTRL+1.
Referencias
Hay un plugin independiente que se puede instalar (instrucciones en https://github.com/codebrainz/geany-zencoding/blob/master/README.markdown).

Para hacerlo en Linux Mint 11 (2.6.38-8-generic #42-Ubuntu) requerí instalar autoconf, libtool, libgtk2.0-dev, y python-dev.


16 de junio de 2011

Drupal 6 con Admin Menu, Admin Theme, Seven, Bartik Mod

Hacen una bonita combinación.

Admin Menu


Coloca un menú dinámico en el top de la página. Se puede indicar que mantenga la posición aunque haya scroll hacia abajo.
Una vez habilitado, puede entrar a la configuración de su tema de administración y quitar el bloque Navigation (Navegación), ya que contiene básicamente los mismos items.

Admin Theme


Agrega funcionalidades al Administration Theme (admin/settings/admin). Entre ellas, poder indicar en qué otras páginas usar también el tema de administración, o en cuáles no.

Seven


Un tema simple y agradable destinado a administración. Hace juego con Bartik.

Bartik Mod


Bartik es el tema oficial de Drupal 7. Aunque no hay una versión oficial para Drupal 6, están estos ports disponibles.

6 de junio de 2011

Mostrando nodos con bloques asociados

Lo que se requiere es mostrar páginas, cada una de las cuales tiene un banner superior y un banner lateral.

Además, que ambos banners sean fácilmente editables.

Quizás se pueda resolver con relativa facilidad usando Panels, pero tengo problemas usando ese módulo en un servicio de hosting que limita la memoria para PHP a 32 MB.

Idea
Que los banners sean nodos asociados a un tipo principal (usando CCK, node reference). La presentación sería usando bloques. Los bloques que contienen a los nodos se definirían usando Views.

Implementación
  1. Las páginas son nodos de tipo page.
  2. Creo el tipo de contenido banner_superior.
    En mi caso, contiene un CCK imagefield.
  3. Creo una vista que provee una lista de nodos de tipo banner_superior.
    No es necesario definir un display, como se verá en el paso 4.
  4. En el tipo de contenido page, agrego el campo field_page_banner_superior, de tipo node reference, con widget tipo select.
    En la configuración de este campo, indico que el select use los valores que devuelve la vista definida en el paso 3.
  5. Creo una vista para mostrar el banner superior en un display de tipo block.
    Tomo como base para la vista el nodo de tipo page.
    En Relationships, defino una relación de page con banner_superior a través del campo field_page_banner_superior.
    Esto permite que en Fields se puedan agregar los campos de banner_superior (indicando, al seleccionar el campo que se use la relación).
    Si han definido algunos nodos de prueba de tipo banner_superior, el preview listará todos ellos. En Arguments indico que se tome como argumento por default el url del nodo que se esté mostrando.
    Aunque no se muestre ningún campo del nodo page, el argumento permite que se muestre el node reference correspondiente a un nodo page determinado.
  6. En la configuración de bloques, coloco el banner superior en la región content-top.
  7. Creo el tipo de contenido banner_lateral y procedo del modo similar a banner_superior, colocando al final el banner en la región lateral derecha.

20 de mayo de 2011

18 de mayo de 2011

Un entorno de desarrollo Drupal portable con XAMPP y FreeCommander

Uso habitualmente Windows 7 y XAMPP 1.7.1 (por la compatibilidad con Drupal 6.x). Hay proyectos que avanzo en casa y hay otros que avanzo en la oficina. En muchas ocasiones requiero consultar cómo hice algo en tal o cual site y me gustaría poder tenerlos al mismo tiempo.

Portatil
Ir cargando una laptop podría ser ser, pero creo que preferiría otra opción.

USB
Podría tener mi entorno de desarrollo en el USB y trabajar directamente allí. XAMPP tiene versiones portables que se pueden instalar en un USB. XAMPP Lite es incluso más ligera.

Sin embargo, mi experiencia es que es realmente muy lento.

Hosting
Un tiempo probé hacer el desarrollo directamente en un hosting. La ventaja es que el desarrollo se hace en un solo lugar. La desventaja es que la edición via FTP es mucho más lenta.

Además, es una opción vulnerable porque el hosting puede quedar fuera de servicio o haber problemas de conectividad, lo que me ocurrió algunas veces (es para ponerse a pensar, si uno ha puesto bastante de su vida en la red).

Mejor parece ser tener una copia local y sincronizar con una copia en la red.

rsync
rsync es un comando que puede ayudarme a sincronizar un site que estoy desarrollando localmente con otro en un hosting.

Para subir un directorio de Windows a un host Linux puedo usar comandos similares a:

cd drupal-dev
rsync --progress -vKrtDzp --chmod=u+rwx,g+rx,o+rx --exclude='.git/*' --exclude='settings.php' . myuser@myhost:public_html/drupal

Y para bajar el directorio del host Linux al directorio Windows puedo usar algo como:

cd drupal-dev
rsync --progress -vrtzL myuser@myhost:public_html/drupal/. .

Así que podría usar cualquier hosting como un punto central de sincronización de archivos.

Opcionalmente, se podría publicar el site si se toma el trabajo extra de actualizar una base de datos en el hosting.

Dropbox
Dropbox es un servicio que permite sincronizar automáticamente un directorio del disco con otro en la red. Entonces, sincronizo el directorio mydropbox de la casa y luego, en la oficina, espero a que la sincronización automática actualice el directorio mydropbox.

Esto funciona bastante bien para compartir información como documentos, manuales e imágenes entre varias computadoras.

Sin embargo, en el caso de Drupal, los cambios que ocurren en los archivos temporales de la base de datos MySQL sobrecargan mucho el tráfico de la red.

Mejor parece ser deterner la sincronización automática mientras se trabaja y luego reanudarla al final.

FreeCommander
FreeComander, es un navegador de archivos alternativo al Explorador de Archivos de Windows. Es libre y se puede descargar de http://www.freecommander.com/. Hay también versiones portables como la de http://portableapps.com/apps/utilities/freecommander_portable.

FreeCommander tiene una opción para sincronizar directorios:


Esto puede ser más fácil de usar que rsync para el caso de sincronizar con un directorio en un USB. Además, usando las versiones portables, podría tener tanto FreeCommander, como XAMPP en el USB.

Algunas soluciones
  1. Usar un hosting para contener una copia intermedia de todo el árbol de xampplite, y usar rsync para las sincronizaciones.
  2. Usar Dropbox para contener una copia intermedia de todo el árbol de xampplite, cuidando de apagar el programa de sincronización automática al empezar y encenderlo al terminar.
  3. Usar un USB para contener una copia intermedia de todo el árbol de xampplite, y usar FreeCommander para las sincronizaciones.
Cuestión de probar, y ver que otras opciones pueda haber :)

Una idea
Me gustaría que se pudiera poner en el USB un Linux portable, que se pudiera bootear rápidamente y permitiera persistencia, para instalar nuevas cosas (como xampplite) y avanzar proyectos.

Que tuviera un LAMP ligero que corriera en un RAM disk en lugar de sobre el USB, para agilizar las operaciones de lectura/escritura. La escritura en el USB sería al final o cuando fuera conveniente.

Me suena a Druppix :) Cuestión de probarlo también.

11 de mayo de 2011

uphp para Drupal

En días recientes, navegando por Internet, me topaba con las opiniones de algunos desarrolladores sobre Drupal.

Comparado con otros frameworks, como los tipo MVC, CakePHP o similares, Drupal es raro. Brillante, pero raro. No es MVC y se apoya, más bien, en una arquitectura que usa hooks para permitir la colaboración de los módulos que se instalen.

Drupal tiene su modo particular de hacer las cosas y cuesta un poco acostumbrarse. A su modo de tratar los formularios, de hacer los temas, de intervenir en el flujo.

Sin embargo, el hecho es que Drupal está floreciendo más rápido que otros frameworks.

Muchos puristas de OOP critican que no use objetos, aunque Drupal dice que implementa los conceptos de OOP de modo diferente.

Quizás sea eso. Que Drupal ofrece una forma diferente de hacer las cosas que, una vez que se aprenden, ayuda a ser más colaborativo y productivo.

Pero, si uno tiene un sistema modelado en OOP tradicional, puede encontrar algunos inconvenientes para pasarlo a Drupal.

Aunque me ha ayudado mucho, Drupal tampoco tiene una forma que me haga sentir completamente cómodo.

Drupal tiene un sistema de tablas que usa para funcionar. Una de las tablas vitales es node. O, al menos, era lo que yo pensaba.

Resulta que node era una tabla opcional que se volvió obligatoria en algún momento del tránsito de la versión 4 a la 5.

¿Sería posible un Drupal sin node? ¿Sería posible un Drupal sin base de datos?

Parece que hay iniciativas al respecto. Aunque node es la base para luego poner CCK y Views, dos de sus módulos más destacados, hay quienes piensan que fue un error hacer que node se volviera obligatorio y que eso se podría corregir (Make Node module optional).

Inspirado por esa posibilidad, traté de ver si podría lograr que Drupal procesara archivos HTML sin necesitar usar tanto la base de datos para ello. Creé un módulo llamado html con la idea de que procesara los archivos que colocara en sites/default/html. Fui incorporando ideas de uphp y, poco a poco, me di cuenta que, en realidad, estaba portando uphp hacia Drupal.

uphp tiene la idea de empezar el desarrollo web con páginas estáticas e ir incorporándole comportamiento dinámico desde afuera, a la manera de unobstrusive javascript. En javascript, jQuery ayuda en eso. En PHP, es QueryPath.

La forma Drupal de hacer las cosas ha sido una inspiración al desarrollar uphp. No me considero un experto en Drupal, sino más bien un principiante, así que me sorprendió que uphp pudiera incorporarse de modo tan natural a Drupal. Sin estudiar mucho las cuestiones del core de Drupal, parece que lo que había imaginado resultó bastante similar.

Así que el módulo pasó a llamarse uphp. Los archivos estáticos se colocan en sites/default/files/html.

Me pareció que podría ser de utilidad a la comunidad y he aplicado para ver si es aceptado como proyecto en drupal.org.

Mientras tanto, he subido el archivo al repositorio de uphp en SourceForge.

4 de mayo de 2011

Resolviendo node.save en Services

El módulo services viene con algunos servicios por default. Node Service pone a nuestra disposición varios servicios relativos a nodos. Uno de ellos es node.save.

node.save permite publicar un nodo desde otro site. En particular, otro site Drupal.

Sin Keys
Servidor
Suponiendo que el site donde queremos publicar es http://myserver.example.com, allí se instala services. Hay que activar los módulos:
  • Services
  • XMLRPC Server
  • Node Service
En la interfaz de administración (admin/build/services), en la pestaña Browse, puede navegar por los servicios disponibles.

Como está activado Node Service, estarán disponibles:
  • node.view
  • node.get
  • node.save
  • node.delete
Cuando elige un servicio, se le mostrará su sintaxis y un formulario de prueba. En el caso de node.save, se requiere como parámetro un array node.

Como la publicación se hará como usuario anónimo, hay que darle los permisos necesarios para crear el tipo de nodo que deseamos. Por ejemplo, si queremos crear de este modo un nodo de tipo 'job', entonces habrá que darle a los usuarios anónimos permiso para eso. Además del permiso load node data del módulo node_service.

Cliente
En algún módulo del cliente, podrá usar un código similar a:

function mymodule_job_plandevuelo_publicar($nid) {
  $plandevuelo_url = 'http://myserver.example.com/';
  $node = node_load($nid);
  $node->type = 'job';
  unset($node->nid);
  $xmlrpc_result = xmlrpc($plandevuelo_url.'xmlrpc.php', 'node.save', $node);
  return $xmlrpc_result;
}

En este ejemplo, el cliente obtiene, de sus propias tablas, un nodo con nid especificado. Luego, usa la función xmlrpc para llamar al servicio node.save en el servidor.

Notar que el url es el del archivo xmlrpc.php del servidor. También que el nombre del service es el segundo parámetro de xmlrpc(). A partir de allí, los parámetros restantes son pasados en el mismo orden a la función que atiende el servicio, en el servidor. Así que $node será el primer parámetro para la función que atiende node.save.

Aunque es importante seguir las reglas del service, a veces puede ser frustrante. Por ejemplo, para crear un nuevo nodo, el service requiere que el nodo no tenga nid. Si lo tiene, se obtiene un mensaje de Acceso Denegado. También requiere que se indique el tipo del nodo ('job', en este ejemplo).

Con Keys
Servidor
Se configura el servidor como en el caso Sin Keys.

Se requiere activar, además, los módulos:
  • Key Authentication
  • System Service
También, en la interfaz de administración (admin/build/services), en la pestaña Opciones, elegir en el combo que el módulo de autentificación es Key authentication.

Si marca la casilla Use keys, cuando guarde las opciones, aparecerá una pestaña Keys donde puede definir llaves para limitar el acceso a services por dominio.

Por ejemplo, podría definir una llave My Client, para el dominio myclient.example.com, marcando alli node.save y otros servicios que se deseen.

Deje marcada la casilla Use sessid para usar incluir un session id en la identificación. El session id se obtiene con ayuda del servicio system.connect.

Entrando a Browse y eligiendo node.save, podrá ver que la sintaxis ha cambiado y requiere más parámetros antes del node.

a) Si dejó desmarcada la opción Use keys, node.save requerirá dos parámetros:
  1. sessid
  2. node
b) Si dejó marcada la opción Use keys, node.save requerirá seis parámetros:
  1. hash
  2. domain_name
  3. domain_time_stamp
  4. nonce
  5. sessid
  6. node
Cliente

a) Sin Use keys
Si se dejó desmarcada la opción Use keys en el servidor, en el cliente se podrá usar un código similar a:

function mymodule_job_plandevuelo_publicar($nid) {
  $plandevuelo_url = 'http://myserver.example.com/';
  $node = node_load($nid);
  $node->type = 'job';
  unset($node->nid);

  $domain_name = $_SERVER['HTTP_HOST'];
  $domain_time_stamp = (string) time();

  // connect
  $method = 'system.connect';
  $xmlrpc_result = xmlrpc($plandevuelo_url.'xmlrpc.php', $method);
  $sessid  = $xmlrpc_result['sessid'];

  // save
  $method = 'node.save';
  
  $xmlrpc_result = xmlrpc($plandevuelo_url.'xmlrpc.php', $method,
    $sessid,
    $node
  );
  
  return $xmlrpc_result;
}

Notar que ahora se requiere invocar a system.connect para obtener sessid.

b) Con Use keys
Si se dejó marcada la opción Use keys en el servidor, podrá usar un código similar a:

function mymodule_job_plandevuelo_publicar($nid) {
  $plandevuelo_url = 'http://myserver.example.com/';
  $node = node_load($nid);
  $node->type = 'job';
  unset($node->nid);

  $domain_name = $_SERVER['HTTP_HOST'];
  $domain_time_stamp = (string) time();

  // connect
  $method = 'system.connect';
  $xmlrpc_result = xmlrpc($plandevuelo_url.'xmlrpc.php', $method);
  $sessid  = $xmlrpc_result['sessid'];

  // save
  $method = 'node.save';
  $nonce = user_password();
  $domain_time_stamp = (string) time();
  $hash = hash_hmac('sha256', $domain_time_stamp .';'.$domain_name .';'. $nonce .';'.$method, '90a84268ba54e7ea02571524586997db');
  
  $xmlrpc_result = xmlrpc($plandevuelo_url.'xmlrpc.php', $method,
    $hash,
    $domain_name,
    $domain_time_stamp,
    $nonce,
    $sessid,
    $node
  );
  
  return $xmlrpc_result;
}

Una forma de hacer update

node.save requiere el $node->nid y además un $node->changed actualizado, por ejemplo:

...
  /* // CREATE
  $node->type = 'job';
  unset($node->nid);
  */
  // UPDATE
  $node->nid = 16;
  $node->changed = time();
...

Algunas precauciones
Aunque en este ejemplo he pasado a node.save casi directamente el $node que obtenía con node_load(), he notado que puede ser más conveniente formar un array con sólamente los valores requeridos y pasar recién ese array a node.save.

De otro modo, puede tener resultados inesperados. Por ejemplo, si el nodo en el local no tiene habilitado la opción de comentarios, mientras que en el destino se desea que sí lo tenga, si uno pasa el node completo se pasan también esas preferencias. Crearía un nodo sin la opción de comentarios o, al actualizar, eliminaría los comentarios que se le hubieran asociado.

Referencias:

3 de mayo de 2011

Usando XMLRPC con Services

Services es un módulo que permite definir web services que responden a solicitudes usando diversos protocolos. Por default, viene con soporte para XMLRPC.

En este ejemplo, hay un proveedor de web services y un cliente de web services.

Proveedor
Supongamos que el proveedor del web service tenga el url http://webservice-provider.com

Luego de instalar el módulo services (y activar Services y XMLRPC Server) puede entrar al administrador, admin/build/services, para navegar por los services definidos por default.

Uno puede agregar más services, colocando un módulo adecuado en modules/services/services/.

Por ejemplo, si tengo un tipo de contenido llamado job:

modules/services/services/job_service/job_service.info
name = Job Service
description = Provides a job service
package = Services - services
dependencies[] = services
core = 6.x


modules/services/services/job_service/job_service.module
/**
 * Implements hook_service
 */
function job_service_service() {
  return array(
    array(
      '#method' => 'job.all',
      '#callback' => 'job_service_all',
      '#return' => 'array',
      '#args' => array(
        array(
          '#name' => 'fields',
          '#type' => 'array',
          '#optional' => TRUE,
          '#description' => t('List of fields')
        ),
        array(
          '#name' => 'limit',
          '#type' => 'int',
          '#optional' => TRUE,
          '#description' => t('To limit result')
        )
      ),
      '#help' => t('Returns a list of the trabajo content type nodes.'),
      '#access callback' => 'job_service_all_access'
    )
  );
}
/**
 * Callback
 */
function job_service_all($fields=array(), $limit=0) {
  $sql = "SELECT * FROM {node} WHERE type='job'";
  if ($limit>0) {
    $sql .= " LIMIT $limit";
  }
  $result = db_query($sql);
  $nodes = array();
  while ($node = db_fetch_object($result)) {
    $nodes[] = services_node_load(node_load($node->nid), $fields);
  }
  return $nodes;
}
/**
 * Access Callback
 */
function job_service_all_access() {
  return TRUE;
}

Una vez definido, se puede probar en la lista de services que aparece en admin/build/services.

Cliente
En el cliente del web service, se puede probar el acceso al web service del proveedor con algo como:

/**
 * Implemens hook_menu
 */
function misc_menu() {
  $items = array();
  $items['misc/test'] = array(
    'page callback' => 'misc_test',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

function misc_test() {
  $items = xmlrpc('http://webservice-provider.com/xmlrpc.php', 'job.all');
  pr($items);
  pr($openid_provider['url'].'xmlrpc.php');
  return 'OK';
}

Como se observa, xmlrpc() permite invocar al service job.all. El url es el del archivo xmlrpc.php localizado en el proveedor.

Si hubiera más argumentos, serán pasados en el mismo orden al método que atiende el service. En este caso, job_service_all().

27 de abril de 2011

Haciendo backtrace rápido y sucio

En el artículo Quick-and-Dirty Debugging, de Angela Byron, se muestra una manera rápida de ver el backtrace (la secuencia de llamadas de funciones), de un error.

Consiste en modificar drupal_set_message(), localizado en includes/bootstrap.inc:

function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) {
  if ($type == 'error') {
    $message .= '<xmp>'. print_r(debug_backtrace(), 1) .'</xmp>';
    drupal_add_js('console.log('.json_encode(array('Drupal Backtrace'=>debug_backtrace())).');','inline');
  }
  
  if ($message) {
  ...
}

La línea en amarillo está basada en la propuesta de Byron y la verde en la que David Konsumer puso en los comentarios al artículo.

Puedes comentar la linea que desees o el condicional if para hacer el backtrace en diferentes circunstancias.

Listando los hooks invocados


Al programar con Drupal, algo que me gustaría conocer es cuáles son los hooks que podría usar en un momento determinado.

Y, si se pudiera, en que orden son ejecutados.

Cuando he preguntado a otros, nadie parece tener la respuesta.

Bueno, aún no la he hallado, pero he encontrado en el artículo Drupal: Exposed de Angie Byron, una forma de listar los hooks usados en un momento determinado.

Consiste en localizar la funcion module_implements, que está en includes/module.inc, y hacer algo algo como:

function module_implements($hook, $sort = FALSE, $reset = FALSE) {
  ...
  drupal_set_message("hook_$hook");
  ...
}

Dice el articulo que como pueden haber hooks que se invocan después que la página es mostrada (no sabía que eso podía suceder), esta lista de hooks los mostraría en la siguiente carga de la página.

CSS3 para mejorar el breadcrumb de un tema Zen

CSS3 está permitiendo hacer muchas cosas interesantes que antes requerían además javascript o imágenes de apoyo.

Por ejemplo, usando sólo CSS se puede hacer que la lista de breadcrumbs se vea como:


La idea es independiente del tema. En este caso, la ilustraré con un subtema Zen.

Configuración del tema
Normalmente hay un caracter de separación para los términos del breadcrumb. Ingresar a la configuración del tema e indicar que no se use ninguno. También indicar que no se agregue dicho caracter al final de la lista.

HTML
De ese modo, el HTML del breadcrumb será similar a:

<div class="breadcrumb">
  <a href="/drupal/inmobiliaria/">Inicio</a>
  <a href="/drupal/inmobiliaria/admin">Administrar</a>
  <a title="Controla la apariencia del sitio."
    href="/drupal/inmobiliaria/admin/build">Construcci&#195;&#179;n
    del sitio</a>
  <a title="Cambia el tema que usa el sitio o permite que los usuarios lo elijan."
    href="/drupal/inmobiliaria/admin/build/themes">Temas</a>
</div>

Es decir, una lista simple de enlaces bajo la clase breadcrumb.

CSS
En el archivo css/pages.css del tema:

...
.breadcrumb /* The path to the current page in the form of a list of links */ {
  padding-bottom: 0; /* Undo system.css */
}
.breadcrumb a {
  text-decoration: none;
  background-color: #fa4;
  display: inline-block;
  line-height: 20px;
  padding-left: 10px;
  border-left: 3px solid #fff;
}
.breadcrumb a:first-child {
  border-left: none;
}
.breadcrumb a:hover {
  background-color: #fea;
}
.breadcrumb a:hover:after {
  border-left-color: #fea;
}
.breadcrumb a:after {
  content: " ";
  width: 0;
  height: 0;
  position: absolute;
  border-top: 10px solid transparent;
  border-left: 8px solid #fa4;
  border-bottom: 10px solid transparent;
  margin: 0;
  z-index: 2;
}
.breadcrumb a:before {
  content: " ";
  width: 0;
  height: 0;
  position: absolute;
  border-top: 10px solid transparent;
  border-left: 8px solid #fff;
  border-bottom: 10px solid transparent;
  margin-left: -10px;
}
.breadcrumb a:first-child:before {
  content: " ";
  width: 0;
  height: 0;
  position: relative;
  border: none;
  margin-left: -5px;
}
...

  • La idea es formar los triángulos en los extremos de cada enlace, darles los colores adecuados y luego superponerlos para que se concatenen los items.
  • Los enlaces se muestran con inline-block para establecer sus dimensiones (con line-height).
  • Se usa el pseudo selector :after para agregar después de cada enlace un triángulo del mismo color de fondo.
  • Para lograr el triángulo se usa un truco. El browser dibuja una diagonal en el cruce de un borde horizontal y vertical, si son de diferente color. Esto se aprecia en los bordes gruesos.
    Entonces, si se hace una caja de 0px por 0px con bordes de 5px, del mismo color de fondo para el izquierdo y transparente para el superior e inferior, se tendrá un triángulo apuntando a la derecha.
  • Para darle un borde al triángulo, se usa el pseudo selector :before para agregar antes de cada enlace un triángulo de color blanco que calce debajo del triángulo del bloque anterior y ligeramente desfasado.
  • Para diferenciar el primer enlace de los demás, se usa el pseudo selector :first-child
  • Además, se usa el pseudo selector :hover para proveer un cambio de color al pasar el mouse.
  • El borde dibujado con los triángulos solapados tiene un espacio en los vértices de su base. Para mejorar eso, se dibuja una línea del mismo color en el lado izquierdo de cada enlace, excepto el primero.
  • Se usan márgenes y paddings para posicionar los elementos.
Ojalá esta técnica te sirva de ayuda.

26 de abril de 2011

Hacking Views

Vengo usando Views prácticamente desde que empecé con Drupal. Aprender Views me requirió paciencia y, principalmente, práctica, pero creo que ha valido el esfuerzo. La interfaz puede parece un poco intimidante al comienzo, pero luego va adquiriendo sentido.

Hay varias cosas de Views que me gustaría saber, pero aún no he resuelto. Estas son algunas, sin ningún orden en particular:

  • ¿Se puede usar Views para mostrar un array arbitrario?
    Eso me ayudaría a usar su mecanismo de paginación, al menos, para presentar otro tipo de resultados que no tengan que ver necesariamente con base de datos.
  • ¿Puede usar Views un SQL arbitrario?
  • ¿Cuál es la forma de modificar, programaticamente, la consulta, el proceso o el resultado?
  • ¿Cómo lograr una caja de búsqueda que ubique el termino en cualquiera de un conjunto de campos especificados?
Hoy encontré este artículo de Jeff EatonHacking Views, Part 1: Basic Concepts

Me está ayudando a entender mejor algunas cosas. Por ejemplo, yo suelo definir en una sola vista todas las vistas que requiero para cierto tipo de dato, colocando cada una en un display. Ahora me parece que una mejor forma de trabajar sería dar a cada caso su propia vista y usar los displays como diferentes formas de presentar una misma cosa.

Continuare leyendo...

21 de abril de 2011

Cómo agregar un formato minutos:segundos

Para agregar un nuevo formato a los disponibles en el combo que se muestra en las opciones de visualización de un nodo (o de un campo CCK), normalmente habría que agregar algunas funciones en un módulo.

El módulo custom_formatters permite hacerlo de modo más sencillo.

Requiere que además esté instalado cck y token.

Pasos
Una vez instalado custom_formatters, ir a admin/build/formatters. Hay algunos ejemplos que pueden servir de guía.

Elegir Add new formatter para agregar un nuevo formato.

Para este ejemplo:

Nombre: minuto_segundo
Etiqueta: Minuto:Segundo
Editor mode: Advanced
Field type(s): number_float
PHP: return sprintf('%02d:%02d', $element['#item']['value']/60, $element['#item']['value']%60);

Cuando se elige Editor mode: Advanced, se puede usar PHP. De otro modo es HTML y tokens.

Para este caso, el valor del elemento a formatear, está en $element['#item']['value'].

Puede leer el artículo original en Drupalab: Cómo agregar un formato minutos:segundos

6 de abril de 2011

Cómo personalizar los enlaces de nodereference_url

Nodereference_url permite indicar que se agreguen enlaces para crear un hijo al final del nodo padre.

Por ejemplo, si hay un nodo proyecto, se puede crear un nodo foto, con un campo nodereference que apunte a un determinado proyecto. Nodereference_url permite agregar automáticamente al nodo proyecto un enlace que conduzca a la creación de un nodo foto.

Esto puede ser cómodo. De ese modo podemos tener nodo foto, nodo plano, nodo noticia, nodo inscripcíon, etc, cada uno con un campo nodereference apuntando a proyecto. Y, en el nodo proyecto, automáticamente enlaces que permiten crear estos nodos hijos.

Los nodos automáticos son dispuestos en orden alfabético. ¿Qué tal si deseamos otro orden?. Además, ¿qué tal si queremos que se muestre el enlace sólo en algunas circunstancias y no en otras?.

nodereference_url_create_link
El módulo nodereference_url contiene la función nodereference_url_create_link($node, $field_name, $type_name, $attributes), donde:

  • $node es el nodo que contiene a los enlaces
  • $field_name es el nombre del campo nodereference que apunta al padre (por ejemplo field_foto_proyecto)
  • $type_name es el nombre del tipo (por ejemplo foto)
  • $attributes, opcionalmente, permite asignar atributos al enlace.

Se puede usar en el template del nodo que contiene los enlaces. Por ejemplo en node-proyecto.tpl.php:

...
<div class="nodereference_url_links">
  <ul class="links">
    <?php
      if (!in_array('manager', array_values($user->roles))) {
        if ($nid=misc_get_inscripcion_en_proyecto($node->nid)) {
          echo '<li>'.l(t('Está inscrito en este proyecto'), 'node/'.$nid).'</li>';
        } else {
          $field_name = 'field_inscripcion_proyecto';
          $type_name = 'inscripcion';
          $attributes = array();
          echo '<li>'.nodereference_url_create_link($node, $field_name, $type_name, $attributes).'</li>';
        }
      }
      echo '<li>'.nodereference_url_create_link($node, 'field_foto_proyecto', 'foto').'</li>';
      echo '<li>'.nodereference_url_create_link($node, 'field_plano_proyecto', 'plano').'</li>';
      echo '<li>'.nodereference_url_create_link($node, 'field_noticia_proyecto', 'noticia').'</li>';
    ?>
  </ul>
</div>

<?php print $links; ?>
...

En este ejemplo, el enlace Crear inscripción se muestra sólo en caso que no se haya creado una inscripción antes. misc_get_inscripcion_en_proyecto(), definida en el módulo misc, devuelve el nodo de tipo inscripción que se haya creado por este usuario para este proyecto, o FALSE en caso contrario.

Los enlaces Crear foto, Crear planos y Crear noticia se colocan manualmente como se prefiera.

23 de marzo de 2011

Como modificar el breadcrumb para un nodo de cierto tipo

El breadcrumb es el conjunto de enlaces que suele aparecer en la parte superior de las páginas para indicar el camino seguido para llegar hasta allí.

Pero, por default, no siempre queda tal cual se quiere.

En este caso, mostraré cómo se puede modificar usando el archivo template.php del tema shamrock, que es un subtema que hice del tema zen.

Ejemplo
Tengo un nodo proyecto, que tiene un campo CCK field_proyecto_tipo, que puede ser 'Nuevo' o 'Entregado'.

Se desea que el breadcrumb pueda conducir a las página proyectos-nuevos o proyectos-entregados según si el nodo presentado es de tipo 'Nuevo' o 'Entregado'.

function shamrock_preprocess_node(&$vars, $hook) {
  if ($vars['type']=='proyecto') {
    $tipo = $vars['field_proyecto_tipo'][0]['value'];
    $bc = drupal_get_breadcrumb();
    if ($tipo==t('Nuevo')) {
      $bc[] = l(t('Proyectos Nuevos'), 'proyectos-nuevos');
    } else if ($tipo==t('Entregado')) {
      $bc[] = l(t('Proyectos Entregados'), 'proyectos-entregados');
    }
    drupal_set_breadcrumb($bc);
  }

  // Optionally, run node-type-specific preprocess functions, like
  // shamrock_preprocess_node_page() or shamrock_preprocess_node_story().
  $function = __FUNCTION__ . '_' . $vars['node']->type;
  if (function_exists($function)) {
    $function($vars, $hook);
  }
}

Referencia

14 de marzo de 2011

Una forma de usar Cufon

Cufon es una alternativa para reemplazar las fuentes de la página con otras no estandar.

Cufón es un script que traza las fuentes con ayuda de archivos .js generados (desde esta página http://cufon.shoqolate.com/generate/) a partir de una fuente .ttf, otf, o pfb.

En el formulario que aparece en la página del generador de coufon, pueder ser útil (particularmente para el español) indicar que incluya además los siguientes glifos:

“”‘’…_&-–—åäöæøàèìòùáéíóúâêîôûäëïöüãõñçÅÄÖÆØÀÈÌÒÙÁÉÍÓÚÂÊÎÔÛÄËÏÖÜÃÕÑÇ©®™′″´×⁺

Aunque existe el módulo cufon, aquí describo una forma de usarlo diréctamente.

Los archivos
Coloco cufon-yui.js y los archivos .js de las fuentes en un módulo. Por ejemplo el módulo misc.

El código
Creo un bloque (llamado Cufon, por ejemplo) que contenga el código php que requiero y se muestre en cada página:

<?php
drupal_add_js(drupal_get_path('module', 'misc') . '/js/cufon-yui.js');
drupal_add_js(drupal_get_path('module', 'misc') . '/js/MyFrutigerBold_700.font.js');
drupal_add_js("$(function() {
  Cufon.replace('h1.title, .views-field-title', { fontFamily: 'MyFrutigerBold' });
})", "inline");
?>

Donde MyFrutigerBold es un nombre indicado en el formulario del generador .js de cufon.

El módulo
El módulo cufon permite manejar esto con más comodidad.

Luego de instalar el módulo, es necesario copiar cufon-yui.js al directorio modules/cufon/js y colocar los .js de las fuentes un directorio como sites/all/libraries/cufon-fonts.

La configuración se hacen en Administer, Configuración del Sitio, Cufon Settings. En Selector, se coloca algo como h1.title, .views-field-title. En Font family, se elige de entre las fuentes disponibles.

Referencia

Cómo hacer un login AJAX alternativo

El login normal de Drupal es un formulario que, en caso de error, conduce a otra página. El objetivo es un formulario que use AJAX para el login y permita mostrar los mensajes de error sin cargar otra página.



El bloque
Creo un bloque (my-login) con este contenido:

<?php
global $user;
?>

<?php if ($user->uid): ?>

  <div id="block-my-login">
    <div class="title"><?php echo l(t('Login'), 'user', array('attributes'=>array('id'=>'btn-my-login'))) ?></div>
    <div class="content">
      <ul>
        <li>
          <?php echo l(t('Mi cuenta'), 'user') ?>
        </li>
        <li>
          <?php echo l(t('Salir'), 'logout') ?>
        </li>
      </ul>
    </div>
  </div>

<?php else: ?>

  <div id="block-my-login">
    <div class="title"><?php echo l(t('Login'), 'user', array('attributes'=>array('id'=>'btn-my-login'))) ?></div>
    <div class="content">
      <form id="user-login">
        <div class="username">
          <label><?php echo t('Usuario') ?></label><input type="text" id="name" name="name" class="input text"/>
        </div>
        <div class="password">
          <label><?php echo t('Contraseña') ?></label><input type="password" id="password" name="password" class="input password"/>
        </div>
        <div class="submit">
          <input type="submit" id="submit" value="<?php echo t('Entrar') ?>" class="button submit"/>
        </div>
      </form>
      <div id="loginresult"></div>
      <div class="remember">
        <?php echo l(t('Olvidé mi contraseña'), 'user/password', array('attributes'=>array('id'=>'remember'))) ?>
      </div>
      <div class="register">
        <input type="button" id="register" value="<?php echo t('Registrarme') ?>" class="button register"/>
      </div>
    </div>
  </div>

<?php endif ?>

<?php
  drupal_add_js(drupal_get_path('module', 'misc') . '/js/login.js');
?>

El bloque muestra un formulario simple para el login y una lista de enlaces para el usuario ya logueado.

Javascript
misc/js/login.js contiene el javascript:

$(function() {
  $('#block-my-login .title')
    .click(function(e) {
      e.preventDefault();
      $('#botones-verdes .content').slideUp('slow');
      $('.content', $(this).parent()).slideToggle('slow');
      return false;
    });
  $('#user-login').submit(function() {
    var url = Drupal.settings.basePath + 'user/login.json';
    $.post(url, $(this).serialize(), function(data) {
      var result = Drupal.parseJson(data);
      if (!result.error || result.error == 0) {
        window.location.href = Drupal.settings.basePath + 'user';
      } else {
        $('#loginresult').show().html(result.result);
      }
    });
    return false;
  });
  $('#register').click(function(e) {
    e.preventDefault();
    window.location.href = Drupal.settings.basePath + 'user/register';
    return false;
  });
});

El script invoca a la acción user/login.json, definida en el módulo misc.

Acción
En el módulo misc, para la acción user/login.json:

/**
 * Implements hook_menu
 */
function misc_menu() {
  ...
  $items['user/login.json'] = array(
    'page callback' => 'misc_login_json',
    'access callback' => 'user_is_anonymous',
  );
  return $items;
}
...
function misc_login_json() {
  if (!isset($_POST['name']) && !isset($_POST['password'])) {
    echo json_encode(array('error'=>t('Se requiere $_POST[\'name\'] y $_POST[\'password\']')));
    return;
  }
  $name = $_POST['name'];
  $password = $_POST['password'];
  if ($msg = user_validate_name($name)) {
    echo json_encode(array('error'=>1, 'result'=>$msg));
    return;
  }
  $account = user_load(array('name' => $name, 'pass' => $password, 'status' => 1));
  if ($account) {
    user_external_login($account);
    echo json_encode(array('result'=>'OK'));
    return;
  } else {
    echo json_encode(array('error'=>1, 'result'=>t('Por favor verifique su nombre de usuario y contraseña.')));
    return;
  }
  return;
}

11 de marzo de 2011

Solucionando error con drush dl

Venía utilizando drush 4.2 con normalidad. Tanto en Linux (Centos 5, php 5.2.10) como en Windows 7 (xampp 1.7.1). Uno de estos días, me ocurrió, en ambos, que ya no funcionaba ejecutar algo como:

drush dl drupal-6.20

En Windows, obtenía un error como:

C:\bin\dev\xampp171\htdocs\>drush dl drupal-6.20
Source directory                                                         [error]

C:\Users\compaq\AppData\Local\Temp/drush_tmp_1299865530/drupal is not
readable or does not exist.
Project drupal (6.20) could not be downloaded to                         [error]

Investigando, se debe a un issue documentado en http://drupal.org/node/1078318.
Alli proveen también el parche http://drupal.org/files/issues/drush-1078318.patch:

diff --git a/commands/pm/pm.drush.inc b/commands/pm/pm.drush.inc
index e39096a..18ec069 100644
--- a/commands/pm/pm.drush.inc
+++ b/commands/pm/pm.drush.inc
@@ -1858,7 +1858,7 @@ function pm_parse_project_version($requests) {
 function pm_project_types() {
   // Lookup the 'Project type' vocabulary to some standard strings.
   $types = array(
-    'core' => 'Drupal project', 
+    'core' => 'Drupal core',
     'profile' => 'Installation profiles',
     'module' => 'Modules',
     'theme' => 'Themes',

Es decir, ha habido un cambio en el valor del campo core, de 'Drupal project' a 'Drupal core', debido, segun entiendo, a un cambio en la taxonomia usada en el proyecto drupal. El issue es del 2 de marzo, pero yo recién lo he notado esta semana.

Para solucionarlo, aplique el parche al drush que tiene instalado. También puede hacerlo manualmente. Por ejemplo, en Windows, ubiqué el archivo pm.drush.inc:

C:\bin\dev\drush\commands\pm\pm.drush.inc:
function pm_project_types() {
  // Lookup the 'Project type' vocabulary to some standard strings.
  $types = array(
    'core' => 'Drupal project',
    'profile' => 'Installation profiles',
    'module' => 'Modules',
    'theme' => 'Themes',
    'theme engine' => 'Theme engines',
  );
  return $types;
}

1 de marzo de 2011

Audioplayer con drPlayer

Para hacer un player de archivos de audio (mp3, etc), ayuda el plugin audio. El problema es que está incompleto (el pase de Drupal 5 a Drupal 6 tiene varios pendientes). Uno puede ir completando las partes que no funcionan, como el playlist, pero es un poco complicado. Además, requiere varios módulos de apoyo. Me dije, a lo mejor, se puede resolver de manera más simple. ¿Cómo lo haría yo?

En este artículo no pretendo instruir sobre la mejor forma de hacer un player de audio. Simplemente, hablaré del camino que voy recorriendo (es un trabajo en marcha) en desarrollar una solución. Tal vez le ayude de algún modo a alguien.

La idea
La idea es poder subir archivos de audio (empecemos con mp3) que puedan ser organizados en playlists, o listas de reproducción.

Un usuario puede subir tantos archivos como quiera y definir sus propias listas.

De preferencia con software que no tenga restricciones de uso comercial, por si acaso.

El método
Empezar con lo imprescindible, lo minimo. Avanzar hacia el siguiente mínimo e imprescindible  paso. Y así sucesivamente. Aplicar la pauta con libertad.

Esbozando la solución
Subir archivos. Se puede usar el módulo filefield.

Playlist. No encontré un módulo para esto, aparte de audio. Busqué en plugins para jQuery. Quiero uno que permita dejar corriendo la lista de canciones. Encontré drplayer y flowplayer, entre otros. Flowplayer se ve muy prometedor. Drplayer es simple de usar. Luego de dar muchas vueltas, me pareció que me estaba distrayendo demasiado en la elección del player, que quizás hasta podría ser intercambiable.

Explorando drPlayer
Hago una demo simple sin usar Drupal, para comprobar lo que requiere:

<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <link rel="stylesheet" href="drplayer.css" type="text/css" />
    <script src="jquery.js" type="text/javascript"></script>
    <script src="drplayer.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#playlist").playlist(
                {
                    playerurl: "swf/drplayer.swf"
                }
            );
        });
    </script>

</head>
<body>
    <div id="playlist">
        <div href="mp3/cancion-01.mp3" style="width: 400px;" class="item">
            <div>
                <div class="fr duration">03:00</div>
                <div class="btn play"></div>
                <div class="title"><b>Artista 1</b> - Canción 01</div>
            </div>
            <div class="player inactive"></div>
        </div>
        <div class="clear"></div>

        <div href="mp3/cancion-02.mp3" style="width: 400px;" class="item">
            <div>
                <div class="fr duration">04:00</div>
                <div class="btn play"></div>
                <div class="title"><b>Artista 2</b> - Canción 02</div>
            </div>
            <div class="player inactive"></div>
        </div>
        <div class="clear"></div>
        
    </div>
    <div class="clear"></div>
    
    <a href="javascript:void(0);" onClick="$('#playlist').playlist('prev');">Prev</a>
    <a href="javascript:void(0);" onClick="$('#playlist').playlist('next');">Next</a>
 
    <a href="javascript:void(0);" onClick="$('#playlist').playlist('pause');">Pause</a>
    <a href="javascript:void(0);" onClick="$('#playlist').playlist('play');">Play</a>
    
   
</body>
</html>

Es decir, la lista está contenida en un bloque #playlist. Cada item es un bloque .item. El campo de título es un bloque .title y el de duración es un bloque .duration.

Más tarde, encontré que es necesario considerar también un bloque .player .inactive por cada item.

Me parece que se podría usar el módulo views para construir html con esta estructura.

Usando id3
El módulo getid3 para permitir usar getid3 para obtener la metadata de un archivo.

La metadata de un archivo de audio contiene información como el título, el nombre del artista, la duración, el nombre y año del album, etc.

Una vez instalado getid3, los nodos que contengan un archivo subido tendrán un campo $node->field_audiofile[0] conteniendo esa metadata (puede leer un poco más sobre eso en el artículo Resolviendo Filefield Meta y usando Dynamic Field), que se podrán usar para llenar la estructura que se requiere para drplayer. Chévere.

Tipo de contenido Audio
Defino el tipo de contenido Audio (audio) con el campo Archivo (field_audio_file) de tipo File.

Indico además que no usará el campo Body (dejando en blanco el nombre de ese campo).

Para facilitar el llenado del campo Title, es útil el módulo auto_nodetitle, que se configura en la misma pestaña de edición del tipo de contenido. Allí, indico que el título se genere automáticamente si se deja en blanco (dando la opción al usuario de poner otra cosa, si desea). Teniendo además instalado el módulo token, indiqué que el patrón de los títulos automáticos fuera:

[field_audiofile-filefield-filename]

Es decir, el nombre del archivo subido.

Lista de audios
Defino la vista audios, que muestra nodos de tipo audio y los campos Nodo: Titulo, Contenido: Archivo. Para obtener la duración, requiero algo de código php que ingreso con ayuda del módulo views_customfield, permite usar php para generar el contenido de un campo.

Al comienzo, dejé a un lado el asunto de la duración e intenté proseguir con el nombre de archivo únicamente.

Intenté usar templates para crear la estructura que requería para drplayer. Con los view-xxx.tpl.php pude dar a cada bloque el id y la clase que requerían. #playlist conteniendo .item que contiene .title y .duration. Sin embargo, la estructura requiere que el nombre del archivo esté como href del elemento .item y no se me ocurría un modo de lograr eso con los templates, ya que el template que me permite personalizar el bloque .item no tiene disponible una variable con el nombre del archivo.

Supongo que hay algún modo de usar template.php para publicar la variable que necesita en ese caso, pero no domino mucho los hooks de la vista y se me hizo más natural imaginar que podría usar un Customfield: PHP para generar todo el html que se requiere a partir del $data->nid disponible:

<?php
echo misc_get_drplayer_html_audio($data->nid);
?>

La función misc_get_drplayer_html_audio la defino en el módulo misc:

function misc_get_drplayer_html_audio($nid) {
  $node = node_load($nid);
  $audiofile = $node->field_audiofile[0];
  $filename = base_path().$audiofile['filepath'];
  $duration = $audiofile['data']['duration'];
  $duration_h = sprintf('%02d:%02d', $duration/60, $duration%60);
  $artist = $audiofile['data']['tags']['artist'];
  $title = $audiofile['data']['tags']['title'];
  $html = '
'.$duration_h.'
'.$artist.' - '.$title.'
'; return $html; }

Lo bueno de usar este método es que me permitió acceder con más comodidad a la metadata del archivo, que no estaba directamente disponible en la vista.

Con esto, se puede conseguir que una vista que muestre las canciones con la estructura requerida por drplayer.

Módulo drPlayer
Para incorporar drPlayer a drupal, cree el módulo drplayer, vacío, simplemente para contener los archivos de esa biblioteca, en el subdirectorio modules/drplayer/drplayer/.

Luego, en la vista audios, en el display que muestra los archivos del playlist, Basic settings, Encabezado, indico el siguiente contenido php:

<?php
drupal_add_css(drupal_get_path('module', 'drplayer').'/drplayer/drplayer.css', 'module');
drupal_add_js(drupal_get_path('module', 'drplayer').'/drplayer/drplayer.js', 'module');
drupal_add_js('$(document).ready(function() {
    $("#playlist").playlist(
        {
            playerurl: "'.base_path().drupal_get_path('module', 'drplayer').'/drplayer/swf/drplayer.swf"
        }
    );
});', 'inline');
?>

Esto invoca a drplayer al momento de mostrar la vista.

Siguiente paso
Que el usuario pueda definir sus propios playlist.

Referencias

Más artículos

Archivo