Escritura de plugins para Joomla!

A la hora de acometer una nueva versión de nuestros programas nos encontramos, en algunos casos, con la temida compatibilidad hacia atrás. Como ejemplo, no hay más que ver todos los problemas que arrastra el sistema operativo de MS desde la época del MS-DOS. Tras la mudanza de la sede del Grupo Albor a un nuevo CMS esa compatibilidad hacia atrás suponía permitir a aquellos usuarios registrados en nuestros foros poder identificarse en la nueva sede sin necesidad de pasar por un nuevo proceso de registro. Afortunadamente, Joomla!, el gestor de contenidos sobre el que se ha desarrollado el nuevo sitio, proporciona mecanismos para extender sus funcionalidades y realizar cualquier tarea que PHP, el lenguaje de programación en el que está escrito, nos permita.

Desde el punto de vista de su arquitectura, Joomla! está desarrollado en tres capas. La capa superior la constituyen las extensiones de la interfase de usuario: módulos, componentes y plantillas. La capa intermedia la forman las aplicaciones que gestionan distintas partes del sitio: JAdministrator, que proporciona todas las funcionalidades de administración; JSite, encargada de la presentación de los contenidos a los visitantes; JInstallation, empleada únicamente en la fase de instalación y XML-RCP, que permite la gestión por procedimientos remotos. Por último y al más bajo nivel se encuentra el Framework, la capa más interna y sobre la que se apoyan y construyen las capas restantes. En este nivel encontramos también las distintas bibliotecas de apoyo, propias y de terceras partes (SimplePie, DomIt, Geshi, tcPDF, y otras) y con los conectores (plugins), que permiten ampliar las funcionalidades del Framework.

Los plugins son clases que se registran asignándose a eventos del núcleo del sistema. Al igual que el paso del puntero del ratón sobre un control dispara el evento onMouseOver de ese control en Delphi, el núcleo de Joomla! lanza determinados eventos en respuesta a acciones. En función de su especialización, tenemos plugins de autenticación (Authentication), de contenido (Content), para editores (Editors), extensiones de editores (Editors-xtd), de búsqueda (Search), del sistema (System), de usuario (User) y de administración remota (xmlrpc). Cada uno de estos tipos responderá ante determinados eventos.

En el caso que nos ocupa, cuando un usuario intenta identificarse se dispara el evento onAuthenticate (asociado a los plugins de autenticación) y se ejecutarán secuencialmente todos los plugins que se hayan registrado para manejarlo. De manera estándar, Joomla! proporciona plugins de autenticación contra su propio sistema de gestión de usuarios, contra directorios LDAP, OpenID o contra GMail. Como ninguno de estos sirven a nuestros propósitos, llega el momento de escribir un plugin de autenticación personalizado.

Podemos encontrar la estructura básica de un plugin de autenticación en /plugins/authentication/example.php , que aquí vemos un poco retocada para ganar en claridad:

 

 
// Nos aseguramos de que se llama desde Joomla!
defined( '_JEXEC' ) or die( 'Restricted access' );
// Incluimos la biblioteca /libraries/joomla/plugin/plugin.php
jimport( 'joomla.plugin.plugin' );
// Clase de autenticación
class plgAuthenticationExample extends JPlugin
{
  // Constructor
  function plgAuthenticationExample(&$subject, $config)
  {
    parent::__construct($subject, $config);
  }
  // Función a ejecutar en el evento onAuthenticate
  function onAuthenticate($credentials, $options, &$response)
  {
    $success = true;
    if ($success)
    {
      $response->status = JAUTHENTICATE_STATUS_SUCCESS;
      $response->error_message = '';
      return true;
    }
    else
    {
      $response->status = JAUTHENTICATE_STATUS_FAILURE;
      $response->error_message = 'No se ha podido autenticar';
      return false;
    }
  }
}

 

Como vemos, después de una comprobación de seguridad básica y la importación de la biblioteca requerida, se define la clase plgAuthenticationExample que únicamente declara su constructor y la función que será llamada durante el proceso de autenticación: onAuthenticate. Recibe como parámetros el nombre y contraseña del usuario en el array $credentials, un array de opciones adicionales $options, y un objeto pasado por referencia: $response de tipo JAuthenticationResponse. En la siguiente tabla tiene las claves del array y las propiedades del objeto:

 

Credenciales
$credentials[‘username’] Nombre de usuario
$credentials[‘password’] Contraseña
Opciones
$options[‘remember’] Si debe recordarse la sesión del usuario
$options[‘return’] URL de retorno en caso de no validar al usuario
$options[‘entry_url’] URL de inicio en caso de validar al usuario
Respuesta
$response->status Código de estado devuelto por la función:
JAUTHENTICATE_STATUS_SUCCESS
JAUTHENTICATE_STATUS_FAILURE JAUTHENTICATE_STATUS_CANCEL
$response->type Plugin que ha realizado la autenticación
$response->error_message Mensaje de error o de estado
$response->username Nombre de usuario
$response->password Contraseña
$response->email Correo electrónico del usuario
$response->fullname Nombre completo del usuario
$response->birthdate Fecha de nacimiento en formato YYYY-MM-DD
$response->gender Sexo
$response->postcode Código postal
$response->country Pais
$response->language Idioma
$response->timezone Zona horaria

 

La función onAuthenticate combrobará que la combinación de nombre de usuario y contraseña pasadas en $credentials son correctas y devolverá en $response los datos adicionales que se precisen. Si la autenticación ha fallado, se pasa a ejecutar el siguiente plugin, si ha tenido éxito, se detiene la ejecución de los plugins de autenticación y se otorga acceso al usuario.

Manos a la obra

La contraseña de usuarios del foro se almacena encriptada aplicando el algoritmo SHA1 a la concatenación del nombre de usuario, en minúsculas, y su contraseña: passwd = sha1(strtolower($credentials[‘username’]) . $credentials[‘password’]); Por tanto, para verificar que un usuario ha introducido correctamente sus credenciales debemos comparar el contenido del campo donde se almacena la clave encriptada por el método anterior con el resultado que obtengamos de aplicar el mismo algoritmo al nombre de usuario y contraseña pasada en $credentials. Si el valor almacenado en la tabla y el calculado por nosotros coincide, las credenciales son válidas, en caso contrario, la combinación nombre de usuario/contraseña es incorrecta.

Con todos estos ingredientes, aquí tenemos el resultado:

 
<?php
/**
 */
 
defined('_JEXEC') or die( 'Restricted access' );
 
jimport( 'joomla.plugin.plugin' );
 
/**
 * Plugin de autenticación contra SMF
 *
 * @author Demetrio Quirós
 * @since 1.5
 */
class plgAuthenticationga_SMF extends JPlugin
{
  /**
   * Constructor
   *
   * @param object $subject The object to observe
   * @param array  $config  An array that holds the plugin configuration
   * @since 1.5
   */
  function plgAuthenticationga_SMF(&$subject, $config) {
    parent::__construct($subject, $config);
  }
 
  /**
   * This method should handle any authentication and report back to the subject
   *
   * @access      public
   * @param       array   $credentials Array holding the user credentials
   * @param       array   $options     Array of extra options
   * @param       object  $response    Authentication response object
   * @return      boolean
   * @since 1.5
   */
  function onAuthenticate($credentials, $options, &$response)
  {
    $message = '';
    $success = 0;
 
    // Tomar los datos de configuración
    $mysql_host = $this->params->get('mysql_host');
    $mysql_base = $this->params->get('mysql_db');
    $mysql_user = $this->params->get('mysql_user');
    $mysql_password = $this->params->get('mysql_password');
    $mysql_table = $this->params->get('mysql_table');
 
    // Conectar con la base de datos
    $mysql_conn = mysql_connect($mysql_host, $mysql_user, $mysql_password);
    $mysql_sdb = mysql_select_db($mysql_base, $mysql_conn);
 
    if (!($mysql_conn) || !($mysql_sdb))
    {
      $message = 'Error conectando con '.$mysql_host.':'.$mysql_base;
    }
    else 
    {
      if(strlen($credentials['username']) && strlen($credentials['password']))
      {
        $sql = 'select memberName, realName, passwd, emailAddress from ' . $mysql_table .
               ' where is_activated = 1 and memberName = "' . $credentials['username'] .'"';
        $res = mysql_query($sql, $mysql_conn);
        if ($row = mysql_fetch_assoc($res))
        {
          if ($row['passwd'] === sha1(strtolower($credentials['username']) . $credentials['password']))
          {
            $success = 1;
            $message = 'Acceso autorizado';
          } 
          else
          {
            $message = 'Acceso denegado';
          }
        }
        else
        {
          $message = 'Usuario inexistente';
        }
        mysql_close($mysql_conn);
      }
      else
      {
        $message = 'El nombre de usuario o la contraseña no pueden estar en blanco';
      }
    }
 
    if ($success)
    {
      $response->status = JAUTHENTICATE_STATUS_SUCCESS;
      $response->error_message = '';
      $response->email = $row['emailAddress'];
      $response->fullname = $row['realName'];
      return true;
    }
    else
    {
      $response->status = JAUTHENTICATE_STATUS_FAILURE;
      $response->error_message = 'Fallo de autenticación: ' . $message;
      return false;
    }
  }
}
?>

 

En primer lugar, obtenemos los parámetros de conexión a la base de datos para realizar la consulta a la tabla de usuarios. Si el nombre pasado en $credentials[‘username’] existe en la base de datos, comparamos la contraseña almacenada con nuestro cálculo de SHA1 para determinar si permitimos o no el acceso.

Es necesario que nos detengamos por un momento en una particularidad de Joomla! que, a poco que nos descuidemos, puede darnos más de un dolor de cabeza: el nombre de determinadas clases y funciones. Si quiere ir a lo rápido, le bastará con saber que el nombre de la clase del plugin debe comenzar por ‘plg’, seguido del tipo de plugin (Authentication) y terminando con el nombre que tenga el archivo donde reside sin extensión. Podemos deducir que en este caso, el nombre de archivo es ga_smf.php, en caso contrario, no funcionaría el invento.

Pero como nos gusta saber el porqué de las cosas, vamos a investigar un poco. En /libraries/joomla/user/authentication.php se define la clase JAuthentication y la función authenticate que realiza la carga de todos los plugins de autenticación registrados llamando a la función getPlugin de la clase JPluginHelper:

 
function authenticate($credentials, $options)
{
  // Get plugins
  $plugins = JpluginHelper::getPlugin('authentication');

 

getPlugin, que puede ver en /libraries/joomla/plugin/helper.php, después de otras operaciones, termina llamando a _import, que aquí vemos resumida:

 

 
function _import(&$plugin, $autocreate = true, $dispatcher = null)
{
  $path  = JPATH_PLUGINS.DS.$plugin->type.DS.$plugin->name.'.php';
  if (file_exists( $path ))
  {
    require_once( $path );
    $className = 'plg'.$plugin->type.$plugin->name;
    if(class_exists($className))
    {
      // load plugin parameters
      $plugin = &JPluginHelper::getPlugin($plugin->type, $plugin->name);

 

La constante JPATH_PLUGINS contiene la ruta al directorio de plugins y DS el carácter separador de directorios en función de la plataforma donde trabajemos. En nuestro ejemplo, el tipo de plugin es Authenticate y su nombre ga_smf, con lo que en la variable $path contendra /plugins/authentication/ga_smf.php y $className plgauthenticationga_smf.

Finalmente, de nuevo en la función authenticate de la clase JAuthenthication, se van llamando a los plugins registrados hasta que alguno devuelva JAUTHENTICATE_STATUS_SUCCESS en la propiedad $response->status en respuesta a la llamada a la función onAuthenticate, como vimos anteriormente:

 
foreach ($plugins as $plugin)
{
  $className = 'plg'.$plugin->type.$plugin->name;
  if (class_exists( $className )) 
  {
    $plugin = new $className($this, (array)$plugin);
  }
  // Try to authenticate
  $plugin->onAuthenticate($credentials, $options, $response);
  // If authentication is successfull break out of the loop
  if($response->status === JAUTHENTICATE_STATUS_SUCCESS)
  {
    // ........;
    break;
  }

 

Instalación del Plugin en el Framework de Joomla!

Aunque ya tenemos nuestro plugin funcional, aún nos queda un último paso para poder registrarlo en el Framework y, además, posibilitar su distribución para su uso en cualquier instalación de Joomla!. Para este cometido se precisa de un archivo XML que tendrá el mismo nombre que el plugin, con la extenisón .xml, y que podemos ver aquí:

 

 
<?xml version="1.0" encoding="utf-8"?>
<install group="authentication" type="plugin" version="1.5">
  <name>Authenticación en los foros SMF</name>
  <author>Demetrio Quirós</author>
  <creationdate>Septiembre 2009</creationdate>
  <copyright>Copyright (C) 2009 Demetrio Quirós.</copyright>
  <license>http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL</license>
  <authoremail>demetrio en grupoalbor.com</authoremail>
  <authorurl>www.grupoalbor.com</authorurl>
  <version>1.0</version>
  <description>Autenticación de usuarios contra la tabla del Foro SMF</description>
  <files>
    <filename plugin="ga_smf">ga_smf.php</filename>
  </files>
  <params>
    <span name="mysql_host" type="text" size="30" default="localhost"
          label="Host MySQL" description="Servidor MySQL"></span>
    <span name="@spacer" type="spacer"></span>
    <span name="mysql_db" type="text" size="30"
          label="Base de datos" description="Nombre de la base de datos"></span>
    <span name="mysql_user" type="text" size="30"
          label="Usuario" description="Usuario de la base de datos"></span>
    <span name="mysql_password" type="text" size="30"
          label="Contraseña" description="Contraseña de acceso a la base de datos"></span>
    <span name="mysql_table" type="text" size="30"
          label="Tabla" description="Nombre de la tabla de usuarios del foro"></span>
  </params>
</install>

 

Una vez instalado mediante el asistente de Joomla!, ya tenemos nuestro plugin disponible para su uso:

 

En la sección <params> se definen los parámetros que pueda necesitar el plugin para su correcto funcionamiento, como los información de conexión a la base de datos. Una vez instalado, la lista de parámetros que hayamos definido aparecerá en la parte de administración de Joomla! y pueden ser invocados desde el plugin mediante

 
$this->params->get('nombre_de_parametro');

 

 

Empaquetamos todo en un zip (o tar, gz, bz2…) y ya está listo para su distribución e instalación.

El resultado de todo esto puede obtenerlo en la sección de descargas.

  • Buscar
Comentarios (0)

 

Para escribir un comentario debes estar registrado