Creo que con el título de este artículo poca introducción es necesaria. Hoy no voy a hablar de código ni de variables, pero sí de algo que tiene que ver con la web, y es el proveedor de hosting 1and1 (España) y la pésima experiencia que tuve con su servicio de atención al cliente la primera vez que lo necesité desde que soy cliente suyo.

Hace ya más de un mes, exactamente el día 27 de Octubre de 2011 recibí un correo electrónico del departamento de soporte de 1and1.es que decía así:

Estimado/a Agustín Villalba Casás:

Con este correo electrónico deseamos informarle de antemano de un cambio
en el servidor web de 1&1:

En el marco de unos trabajos de mantenimiento del sistema, durante la
semana
del 31 de octubre al 4 de noviembre se llevará su paquete de hospedaje 1&1
a un entorno operativo actualizado y así podremos seguir ofreciéndole un
entorno
óptimo y altamente seguro.

No se requieren trabajos preparatorios o de ajuste por su parte.
La conversión se lleva a cabo de forma totalmente automática. Sin embargo,
su paquete de hospedaje no estará disponible durante 5 o 10 minutos entre
las
8:45 y las 18:45. Una vez terminada la conversión, podrá disponer de todas
las aplicaciones tal y como está acostumbrado.

Lamentamos que por motivos técnicos no podamos indicar con exactitud la
hora de conversión de su paquete de hospedaje.

¿Cuáles son las mejoras?
=============================
Además de un mejor rendimiento y de mayor seguridad, la conversión a un
entorno operativo Linux (basado en Debian Squeeze/6.0) especialmente
adaptado
a 1& 1 y a sus clientes incluye una actualización de los componentes
siguientes:

Apache 2.2

http://apache.org/

Tenga en cuenta que con la conversión pueden darse algunos cambios en la
configuración del tiempo de ejecución. Así, por ejemplo, con la instrucción
"IndexOptions FancyIndex" se accede ahora a la función que anteriormente
ponía
a disposición el comando "FancyIndex On", entretanto nulo.

http://httpd.apache.org/docs/2.0/upgrading.html

Asimismo, si usa scripts o software muy antiguos, queremos indicarle que
determinadas variables internas, como, por ejemplo, "REMOTE_USER" solo
estarán
disponibles tras un redireccionamiento del Apache
como 'REDIRECT_REMOTE_USER'.

Python 2.6
http://www.python.org
http://docs.python.org/release/2.6.7/whatsnew/2.6.html

Perl 5.10.1
http://perldoc.perl.org/
http://perldoc.perl.org/http://perldoc.perl.org/5.10.1/index.html

Para estas actualizaciones no es necesario que realice acciones.
Estarán automáticamente disponibles tras la conversión. 

A pesar de todo, si tras la conversión algo no funcionara de la forma
acostumbrada
o si tuviera más preguntas, no dude en ponerse en contacto con nuestro
servicio de
soporte.

Como se desprende del correo, la actualización únicamente iba a ser relativa a la versión del servidor web y algunos aspectos de seguridad. Como ellos mismos mencionan, “No se requieren trabajos preparatorios por su parte” y “…si tras la conversión algo no funcionara de la forma acostumbrada o si tuviera más preguntas, no dude en ponerse en con nuestro servicio de soporte.” Con esto en mente uno piensa que su sitio web no tiene porqué dejar de funcionar tras la actualización, dado que no mencionan en ningún momento que se vaya a modificar la configuración del servidor PHP ni ninguno de sus módulos disponibles antes de la actualización, aún así, inspiran cierta tranquilidad invitando a pensar que si algo no funcionara tras la actualización todo se arreglaría con llamarlos, comentarles el problema y todo volvería a la normalidad.

Pues cuál fue mi sorpresa cuando la semana pasada, el día 28 de noviembre de 2011, estaba haciendo algunas pruebas en mi sitio web alojado en sus servidores y descubro que mi servicio de “Contacto” no envía correos electrónicos. Me extrañó mucho, dado que en mi servidor de desarrollo el código funcionaba a la perfección pero en el de producción (1and1) no. Para ello utilizo la librería PHPMailer, por lo que estuve durante 2 horas examinando el código de la librería por cualquier cosa que se haya podido corromper en la subida de los archivos, pero no encontraba nada extraño. Hasta que llegado a un punto recordé la actualización de la que me habían advertido hacía más de un mes. Comencé a comparar línea por línea la configuración de mi servidor PHP de desarrollo y la configuración del servidor PHP de 1and1, hasta que descubrí que el problema era que en la configuración del servidor PHP de 1and1 había desaparecido el módulo de Sockets. Estupendo, ya tenía el motivo, tan sólo tenía que llamar a 1and1, pedirles que me activaran este módulo y todo volvería a la normalidad como me habían dado a entender en su e-mail.

Llamo al servicio de atención al cliente (un número 902… nada barato) y me atiende un hombre bastante agradable inicialmente. Antes de hablar con este hombre, una contestador me advirtió de que la conversación iba a ser grabada, ahora tras la experiencia: ¡menos mal que la grabaron!. Al susodicho operador, le comento mi problema y me pide que espere para que él pueda comprobar la configuración de mi servidor PHP. Tras 3 minutos de espera, vuelve y me dice que efectivamente el módulo de Sockets no está instalado (algo que ya le había dicho yo al inicio de mi llamada). Le pido si por favor me lo pueden activar, que una parte de mi sitio depende de ese módulo para su correcto funcionamiento y me dice que “tendré que revisar mi código y 1and1 no da soporte al código de aplicaciones realizadas por terceros” (???). Le repito que no necesito que me de soporte a mi código PHP, además de que ese mismo código tal cual está funcionaba antes de la actualización, y en su correo mencionaban que no serían necesarios cambios por mi parte, y tras la actualización ese código sin que nadie lo manipulara dejó de funcionar. A esto, el personaje al teléfono, me responde que la actualización “no afectaba a los módulos de PHP, que era únicamente de seguridad de Apache para dar un mejor servicio a los usuarios y bla bla bla…”. Si no afectaba a los módulos, ¿por qué desapareció el módulo de Sockets de PHP?

Tras 10 minutos intentando que el buen hombre entendiera que no necesito soporte a mi código de PHP y que si la actualización no afectaba a los módulos de PHP por qué desapareció el módulo de sockets, me da una nueva excusa al problema diciéndome que “la nueva versión de hostings compartidos del servidor no habilita el módulo de Sockets” y dado que yo tengo contratado hosting compartido, ese módulo no me lo pueden activar. ¿Versión de hostings compartidos del servidor? Servidor PHP hay uno y cada uno lo configura como necesita, o ¿desde cuándo PHP saca versiones de hostings compartidos de su servidor? Pues tras esta excusa, intento pedirle si hay alguna posibilidad de que se active el módulo de Sockets mediante un php.ini para mí o (a la desesperada) mediante un .htaccess en mi sitio. A todo ello responde con un rotundo “NO” sin pararse ni medio segundo a meditar cada una de las opciones.

En vista de que ya llevaba 15 minutos al teléfono y el humanoide al otro lado no dejaba de repetir cíclicamente las 3 excusas que se tenía aprendidas: “No damos soporte al código de terceros”, “La actualización era de seguridad” y “la nueva versión de hostings compartidos no habilita el módulo de Sockets”, le pedí que me pasara con un superior para aclarar el asunto, y su respuesta no fue otra que “No le voy a pasar con un superior, yo soy el agente responsable de esta llamada”. ¡Increíble! No sólo no me había dado solución a mi problema sino que además me negaba el derecho de hablar con un superior suyo. Ante mi indignación, y tras 2 minutos insistiéndole para que me dejara hablar con un superior, me dice con altanería “lo siento señor, pero tengo liberar la línea” y me colgó el teléfono. Sencillamente espectacular pensé, un servicio de atención al cliente ¡que le cuelga el teléfono al cliente!

Tras dejar pasar un rato para calmarme, volví a llamar esperando hablar con otro operador que sí tuviera ganas de solucionar mi problema. La conversación discurrió en términos parecidos a la anterior, pero esta vez el operador, además de no colgarme el teléfono, se comprometió a comunicar mi caso al departamento de sistemas y ver qué se podía hacer.

En un intento desesperado de saber si existía algún operador capaz de ayudarme, volví a llamar por tercera y última vez. Tras comentarle mi problema y él comprobar que no estaba habilitado el módulo de sockets, me dijo que “iba a ser imposible solucionarlo”. Pasamos de un “ver que se puede hacer…” a un “imposible”, no pintaba bien la cosa. Le comenté que ese módulo era muy importante para mi sitio web y me dijo que no podía hacer nada. Le comenté la impotencia que me daba el saber que 1and1 me retiraba de forma unilateral y sin previo aviso un módulo que era necesario para el correcto funcionamiento de mi web y su respuesta fue… silencio. Le dije que “con esto me están empujando a abandonar 1and1, retirar de allí mi web, e irme con otro proveedor de hosting que sí me ofrezca el módulo de Sockets”, y su respuesta fue literlamente “usted puede emprender las acciones que considere oportunas…”, ¡vaya forma de cuidar a su clientela que tiene 1and1!

Al día siguiente pedí a 2 amigos que tienen hosting con CDMon y HostMonster que me permitieran ver sus configuraciones del servidor PHP, y cuál fue mi sorpresa cuando vi que esos hostings sí tienen módulos de Sockets, con lo cual, se confirma lo que ya sabíamos, la excusa de “la nueva versión del servidor de hostings compartidos no habilita el módulo de Sockets” era una burda y penosa mentira del operador que me atendió.

En definitiva, no activaron el módulo de sockets y yo SÍ tuve que modificar mi código para que mi web funcionara (a pesar de que ellos mintieran diciendo que no sería necesario) mientras hago la migración de mi web a otro proveedor de hosting que sí tiene activados los módulos de Sockets.

Mi humilde consejo: No contrates los servicios de 1and1. Todos estos dolores de cabeza que he tenido yo no tienes porqué pasarlos tú. No permitamos que se nos trate como si estuviéramos molestando a los señores de atención al cliente cuando, muy de vez en cuando, requerimos su ayuda. Me gustaría pedirles un favor. Todos aquellos que tengan Twitter, Facebook o cualquier red social… por favor, difundan la noticia y aporten su granito por llegar al mayor número de gente posible. Nos merecemos servicios de soporte de calidad, que eso también nos los cobran. En Twitter podríamos copiar y retwittear el mensaje “@1and1_ES por un servicio de #atencionalcliente de calidad http://wp.me/pIXJS-59 @agustinvillalb Por favor, retwittea!” Muchas gracias a todos!

Hoy escribo para comunicar que, después de bastante tiempo, por fin está terminado y funcionando mi nuevo portfolio en http://www.agustinvillalba.com. Allí pueden encontrar información acerca de mí y algunos de los trabajos que he realizado hasta ahora.

Espero que les guste y espero aquí sus comentarios y/o sugerencias sobre la web si lo desean!

Hoy vamos a explicar cómo poder lanzar múltiples hilos en nuestros scripts PHP, pudiendo así paralelizar aquellos procesos que tengan una gran carga de procesador o que simplemente puedan lanzarse en paralelo dado que no tienen dependencias entre ellos y todos resuelven de forma parcial un problema común.

¿Por qué paralelizar procesos?

Cuando se programa un algoritmo, según el lenguaje en el que lo hagamos, podemos resolver las partes del algoritmo de forma secuencial o paralela. Si tenemos una parte del algoritmo que tiene una gran carga y requiere mucho tiempo para su resolución, podríamos ir resolviendo las demás partes mientras se resuelve la parte más lenta. De forma que el tiempo total de ejecución de nuestro algoritmo pasa de ser la suma de las partes a solamente la parte más lenta. Es cierto que la paralelización realmente se aprovecha cuando disponemos de recursos redundados, por ejemplo procesadores, pero en la gran mayoría de los servidores actuales disponemos de varios núcleos, por lo que tiene bastante utilidad aprender a paralelizar nuestros procesos.

La base teórica de la paralelización nos dice que cuando proceso padre lanza un proceso hijo, se crea en memoria un proceso idéntico al padre, con un identificador de proceso (pid) propio (diferente al del padre) y ejecutándose a partir de la instrucción siguiente a la que creó al propio proceso hijo.

Lanzando multiples hilos en PHP

Antes que nada hemos de tener en cuenta que para poder paralelizar procesos en PHP necesitamos tener instalada la extensión de control de procesos (Process Control Extension [http://www.php.net/manual/en/refs.fileprocess.process.php] en inglés). Dentro de esta extensión, los módulos que nos permitirán paralelizar nuestros procesos son el propio de Control de Procesos (PHP PCNTL [http://www.php.net/manual/en/book.pcntl.php] en inglés) y el de memoria compartida entre procesos (PHP Shared Memory [http://www.php.net/manual/en/book.shmop.php] en inglés). Para poder explicar la paralelización de procesos vamos a poner un ejemplo (bastante simple) en el que un proceso (padre) lanzará 10 hilos en paralelo (hijos) los cuales generarán un número aleatorio, colocarán en una zona de memoria compartida con su padre, éste recogerá cada número generado por sus hijos y devolverá la suma de todos ellos.

Veamos el código:

<?php
function multiple_forks()
{
    $array_pids = array();
    $sumatorio = 0;
    //Almacenamos el process id del proceso padre
    $parent_pid = getmypid();
    for($i=0;$i<10;$i++)
    {
        if(getmypid() == $parent_pid)
        {//Estamos en el proceso padre, asi que lanzamos el proceso hijo y guardamos si pid
            $array_pids[] = pcntl_fork(); //pcntl_fork nos permite lanzar un proceso hijo en paralelo
        }
    }

    //Una vez hemos lanzado los 10 hijos pasamos a generar los números aleatorios (en los hijos)
    //o ir sumandolos si estamos en el padre
    if(getmypid() == $parent_pid)
    {//Estamos en el proceso padre
        while(count($array_pids) > 0)
        {//Mientras queden hijos en ejecución, quedamos a la espera de que terminen
            $pid = pcntl_waitpid(-1,$status);
            //Abrimos la memoria compartida con nuestro hijo $pid
            $shared_id = shmop_open($pid,"a",0,0);
            $share_data = shmop_read($shared_id,0,shmop_size($shared_id));
            $sumatorio += $share_data;
            //Marcamos el bloque para que sea eliminado y lo cerramos
            shmop_delete($shared_id);
            shmop_close($shared_id);
            //Eliminamos el proceso de la cola de hijos en ejecucion
            foreach($array_pids as $key => $hijo)
            {
                if($pid == $hijo) unset($array_pids[$key]);
            }
        }
    }
    else
    {//Estamos en el hijo
        $num = rand(0,100);
        $shared_id = shmop_open(getmypid(),"c",0644,strlen($num));
        if(!$shared_id)
        {//No se pudo crear la memoria compartida
            echo "Error al crear la memoria compartida en el hijo ".getmypid()."\n";
        }
        else
        {
            if(strlen($num) != shmop_write($shared_id,$num,0))
            {
                echo "Error al intentar escribir el numero $num en el hijo ".getmypid()."\n";
            }
            else
            {
                shmop_close($shared_id);
            }
         }
         //Salimos indicando al padre que todo ha ido bien
         exit(0);
    }
    return $sumatorio;
}
?>

Muy probablemente el código se pueda optimizar aun más, pero no es el objetivo de este artículo. Veamos las funciones de control de procesos y memoria compartida que hemos utilizado en el código:

  • pcntl_fork. Nos permite lanzar hijos de un proceso padre, devolviendo el pid del hijo lanzado. (pcntl_fork en PHP)
  • pcntl_waitpid. Nos permite poner al padre en espera de que un hijo suyo termine su ejecución. El parámetro -1 indica que queda a la espera de que algún hijo termine, el primero que lo haga. (pcntl_waitpid en PHP).
  • shmop_open. Nos permite crear o abrir un bloque de memoria. El primer parámetro es a modo de identificador, nada mejor que usar el pid del hijo como identificador, así el padre podrá conocer el identificador con el que se creó dicho bloque de memoria y acceder a los datos compartidos. (shmop_open en PHP)
  • shmop_read. Nos permite leer un bloque de memoria compartida. (shmop_read en PHP)
  • shmop_delete. Marca el bloque de memoria para ser liberado. El bloque será liberado automáticamente por el sistema cuando todos los procesos concurrentes asociados con el bloque se desvinculen del bloque. (shmop_delete en PHP)
  • shmop_close. Cierra un bloque de memoria, indicandole al sistema que el proceso se desvincula del bloque. (shmop_close en PHP)
  • shmop_size. Nos permite conocer el tamaño que ocupa un bloque de memoria compartida. (shmop_size en PHP)
  • shmop_write. Nos permite escribir datos en un bloque de memoria compartida. (shmop_write en PHP)

Con esto tenemos la base necesaria para poder crear procesos paralelos en nuestros controladores y lanzar así tantos hilos como procesadores dispongamos en nuestro servidor.

Hoy vamos a hablar de los Microformats, un conjunto de “buenas costumbres” apliamente aceptadas y extendidas y que posiblemente acaben convirtiéndose en un estándar en Internet, que nos permiten remarcar datos de contacto, eventos de calendario, etc, entendible por humanos y robots de búsquedas.

¿Qué son los Microformats?

Los Microformats son un conjunto de formatos, simple y apliamente aceptados, construido sobre las normas existentes de HTML. En lugar de deshechar aquello que hoy en día funciona y está firmemente implantado, los Microformats intentan solucionar problemas nuevos utilizando las herramientas existentes.

Los Microformats NO SON un nuevo lenguaje de programación web. Simplemente añaden una nueva funcionalidad al lenguaje existente utilizando sus propiedades.

¿Para qué sirven los Microformats?

Los Microformats nos permiten remarcar o destacar dentro del contenido de nuestra web aquellos datos de contacto, eventos de calendario o descripciones de productos de forma sencilla, no intrusiva para el usuario que lee la información y muy clara para los robots de buscadores como Google, el cual lo reconoce a la perfección en incluso premia en su cálculo de PageRank a aquellos sitios que los utilizan adecuadamente.

Ejemplos de Microformats

Datos de contacto

Para los datos de contactos podemos crear un div con la clase “vcard”, el cual contendrá todos los datos personales que queramos mostrar. Una tarjeta de contacto tipo hCard podría ser algo así:

<div id="hcard-Juan-López" class="vcard">
 <span class="fn">Juan López</span>
 <div class="org">Google Corp.</div>
 <a class="email" href="mailto:jlopez@server.com">jlopez@server.com</a>
 <div class="adr">
  <div class="street-address">Main St.</div>
  <span class="locality">Palo Alto</span>,
  <span class="region">California</span>,
  <span class="postal-code">CA1234</span>
  <span class="country-name">Estados Unidos</span>
 </div>
 <div class="tel">123456789</div>
</div>

Podemos observar que todo se basa en añadir clases a los distintos elementos del documento. Por ejemplo:

  • vcard. Para indicar que definiremos un bloque de datos personales.
  • fn (full name). Para destacar el nombre completo del contacto.
  • org. Para destacar la organización en la que trabaja.
  • Puedes encontrar más documentación sobre vcard en hCard 1.0

Eventos de calendario

Para los datos de un evento podemos crear un div con la clase “vevent”, el cual contendrá todos los datos del evento de calendario que queramos mostrar. Una evento de calendario tipo hCalendar podría ser algo así:

<div id="hcalendar-Reunion-con-cliente" class="vevent">
<abbr class="dtstart" title="2011-05-24T10:00Z00">May 24, 2011  10</abbr> –
<abbr class="dtend" title="2011-05-24T13:00Z">1pm</abbr>: 
<span class="summary">Reunion con cliente</span> at <span class="location">Sala de reuniones</span>
<div class="description">Reunión con nuestro mejor cliente</div>
</div>

En este caso, utilizamos otras clases diferentes para remarcar los datos del evento. Por ejemplo:

  • vevent. Para indicar que vamos a comenzar un bloque de datos relativos a un evento de calendario.
  • dtstart o dtend. Para indicar la fecha y hora de inicio y fin del evento.
  • Puedes encontrar más información sobre vevent en hCalendar 1.0

A tener en cuenta…

  • Los principales sitios de referencia en Internet como Facebook, Twitter o Google hacen uso de Microformats para especificar datos de contacto o eventos.
  • Los principales buscadores de Internet reconocen los microformats a la hora de indexar páginas que los contengan.
  • Para más información sobre Microformats puedes visitar microformats.org

Hoy vamos a hacer una introducción a la modularización de aplicaciones en el framework CodeIgniter, utilizando para ello la extensión HMVC (Hierarchical Model View Controller o Modelo Vista Controlador Jerárquicos), de forma que nuestra aplicación gana en flexibilidad y podemos paralelizar el desarrollo entre varios desarrolladores una vez los módulos han sido diseñados, con el valiosísimo tiempo que ganamos.

Instalando HMVC en CodeIgniter

Lo primero que necesitamos para modularizar nuestra aplicación es descargarnos la extensión HMVC. Una vez la hemos descomprimido, nos encontramos con 2 carpetas:

core

third_party

colocamos la carpeta third_party (tal cual) dentro de la carpeta application de nuestra aplicación CodeIgniter. La ubicación final de la carpeta core dependerá de la versión de CodeIgniter que tengamos instalada. Por lo tanto, el contenido de la carpeta core irá en:

Si tenemos CodeIgniter 2.0

application/core

Si tenemos CodeIgniter 1.7.2

colocaremos el contenido de la carpeta core (3 archivos) dentro de la carpeta application/libraries

Bien, con esto tenemos instalada la extensión que nos permite modularizar nuestra aplicación, pero nos falta un último paso. Dentro de la carpeta application hemos de crear un directorio llamado modules. Este directorio (como su propio nombre indica) contendrá todos los módulos que desarrollemos.

Desarrollando módulos en nuestra aplicación

Una vez que está todo instalado y todos los directorios necesarios creados, podemos comenzar a desarrollar los módulos que darán funcionalidad a nuestra aplicación web. A partir de aquí, los límites los pone la creatividad y genialidad del programador, sólo existen algunas reglas que cumplir.

El nombre del directorio que contiene a nuestro módulo marcará el nombre de dicho módulo y por lo tanto, la url que el usuario verá en su barra de navegación. Es decir, que si el nombre de nuestro módulo es “modulo_alumnos”, dentro del cual tenemos un directorio “controllers” con 2 controladores: “alumno” y “alumna”, dentro de cada controlador tendremos una función llamada “soy_un_alumno” y “soy_una_alumna”, la url final sería:

tuaplicaciónCI.com/index.php/modulo_alumnos/alumno/soy_un_alumno/(y a partir de aquí los parámetros) o tuaplicaciónCI.com/index.php/modulo_alumnos/alumna/soy_una_alumna/(parámetros)

También es importante remarcar que si tenemos un módulo llamado, por ejemplo, “usuario”, el cual contiene un controlador llamado “usuario.php” (usuario/controllers/usuario.php) que a su vez contiene un método llamado, por ejemplo, “login”, la url de acceso al método podría ser:

miaplicacionCI.com/index.php/usuario/usuario/login o miaplicacionCI.com/index.php/usuario/login

es decir, cuando el nombre del módulo y el nombre del controlador coinciden podemos obviar este último en la url de acceso. Pero sólo cuando coinciden.

Dentro del directorio modules la estructura de directorios que debemos seguir es la misma que nos encontramos en el directorio application, de ahí el nombre HMVC (MVC jerárquico). De forma que, como mínimo, hemos de tener un directorio controllers y un directorio views, además podremos tener un directorio models, o incluso un directorio “libraries”, si necesitamos librerías específicas para nuestro módulo que otros módulos no necesitarán o no deberían tener acceso, un directorio “config”, si nuestro módulo necesita ciertas variables de configuración que no son comunes a toda la aplicación, etc. También podemos crear dentro del directorio del módulo más directorios que no forman parte de la estructura habitual de “application”, como podría ser un directorio “files”, donde podríamos almacenar temporalmente ciertos archivos que un módulo en concreto genera, etc.

A partir de aquí los límites los marcan la creatividad y genialidad del desarrollador.

Para más ayuda, pueden echar un vistazo en https://bitbucket.org/wiredesignz/codeigniter-modular-extensions-hmvc/wiki/Home.

En esta ocasión vamos a hablar sobre cómo generar una sencilla query de selección de datos en una base de datos MySQL, cuyo charset ha sido establecido como utf8_general_ci pero necesitamos distinguir entre datos con acentos y datos sin acentos, y yendo un poco más allá, si fuera posible, que no distinga entre palabras que empiecen con mayúsculas o minúsculas pero sí los acentos. Veamos cómo podríamos implementarlo.

Definición de nuestro problema

Expliquemos un poco mejor cuál es nuestro problema. Tenemos una base de datos MySQL en la que tenemos a su vez una tabla, por ejemplo, “usuarios”, con varios campos: id, nombre y apellido. Esta base de datos fue creada con un charset utf8_general_ci. Pero, ¿qué siginifica esto? Obviamente, utf8 es el charset que nuestra base de datos va a gestionar. En segundo lugar, “general” indica que no utilizaremos el juego de caracteres propio de un idioma en concreto, (podríamos tener “utf8_spanish_ci”, para el juego de carácteres propios del español), sino el utf8 de forma genérica. En último lugar, “ci” significa “Case Insensitive”, de forma que en nuestras búsquedas, el motor de MySQL no hará distinciones entre “MAYÚSCULA”, “Mayúscula” o “mayúscula”. Lo que no es tan conocido es que el ser Case Insensitive también implica que no distinga los acentos del español, de forma que tampoco hace distinciones entre “mayúscula” o “mayúscula”.

De forma que si tenemos 3 registros en nuestra tabla usuarios, en los cuales tenemos los siguientes valores para el campo nombre: “José”, “Jose” y “JOSÉ”, y ejecutamos la siguiente query:

SELECT * FROM `usuarios` WHERE `nombre` = 'jose'

Ésta nos devolverá los 3 registros de nuestra base de datos, sin distinciones.

Distinguir acentos, mayúsculas y minúsculas en MySQL

Para poder distinguir acentos, mayúsculas y minúsculas en nuestras búsquedas en una base de datos MySQL con charset utf8_general_ci necesitamos indicarle al motor MySQL que en ésta búsqueda en concreto no aplique el charset utf8_general_ci, sino que utilice otro, el conocido como utf8_bin, y para ello hemos de utilizar la palabra reservada COLLATE. Con este charset le estamos indicando a MySQL que la comparación en la búsqueda la haga a nivel binario, con lo cual ahora sí podrá distinguir entre palabras con acentos y sin acentos, mayúsculas y minúsculas dado que (obviamente) un carácter con acento es bianariamente distinto a uno sin acento así como las mayúsculas. De forma que nuestra query anterior, para que distinga acentos, sería algo así:

SELECT * FROM `usuarios` WHERE `nombre` = 'josé' COLLATE utf8_bin

Esta query nos devolvería un registro nulo, dado que no existe exactamente “josé” en nuestra base datos. Para poder obtener un valor deberíamos ejecutar la misma query pero comparando con el valor “José” o “JOSÉ”, dado con el COLLATE utf8_bin “josé” <> “jose” <> “José” <> “JOSÉ” etc.

Pero resulta que necesitamos que distinga acentos pero que no tenga en cuenta mayúsculas o minúsculas. ¿Cómo podemos solucionarlo?

Distinguir acentos, sin distinguir mayúsculas o minúsculas en MySQL

Para que MySQL pueda distinguir los acentos pero no las mayúsculas o minúsculas, no tenemos que hacer nada especial. Simplemente lo que haremos será convertir a todos los caracteres en mayúscula del valor almacenado en el campo nombre y convertir a todos los caracteres en mayúsculas del valor del parámetro a comparar, de forma que internamente en el momento de la búsqueda no existan minúsculas y así poder comparar con seguridad. Para ello utilizaremos la palabra reservada UPPER de MySQL. De forma, que nuestra query anterior, para que no distinga mayúsculas y minúsculas pero sí acentos, sería la siguiente:

SELECT * FROM `usuarios` WHERE UPPER(`nombre`) = UPPER('josé) COLLATE utf8_bin

Esta query nos devolvería como resultado los nombres “José” y “JOSÉ”, dado que no hacemos distinciones de mayúsculas, tal y como queríamos conseguir.

Hoy vamos a hablar de una potente librería disponible para el framework CodeIgniter, es: Carabiner. Esta librería nos permite incluir de forma específica para cada vista de nuestro sitio los archivos JavaScript y CSS que son necesarios, ahorrándonos tener que incluirlos todos, y además, los envía al usuario en un único archivo “.js” y “.css” con todos los que hemos ido incluyendo pero minimizados, con lo que el tamaño del “.js” y “.css” que se envía al usuario es mucho menor, con la consiguiente mejora de tiempo de carga del sitio que ello implica.

Instalando Carabiner en nuestro proyecto

Lo primero para instalar Carabiner en nuestro proyecto es descargarlo, podemos hacerlo desde su hilo en el foro de CodeIgniter. Para instalarlo simplemente hemos de colocar los archivos carabiner.php, cssmin.php y jsmin.php en el directorio /application/libraries de nuestro proyecto. Una vez hecho esto, hemos de colocar el archivo de configuración carabiner.php en el directorio application/config. En este archivo hemos de especificar la ruta de los directorios que contendrán nuestros archivos “.js” y “.css”.

Además, hemos de crear la carpeta cache dentro del directorio system/logs de nuestro proyecto. El directorio cache es donde la librería carabiner colocará los archivos .js y .css minimizados que genera y que son enviados al navegador del usuario. Este directorio necesita permisos de escritura por parte del servidor web, dado que es él quien genera los archivos.

Utilizando Carabiner

Para utilizar Carabiner en nuestras vistas simplemente hemos de cargar las tres librerías en aquellos controladores que carguen vistas con JavaScript o CSS o bien, para más comodidad, podemos añadirlas al archivo autoload.php dentro del directorio application/config, de forma que nuestro código sería algo así:

$this->load->library('carabiner'); $this->load->library('cssmin'); $this->load->library('jsmin');

Una vez cargadas las librerías, tan sólo nos queda ir añadiendo en la vistas los distintos archivos “.js” y “.css” que necesitaremos para nuestro sitio. El código sería algo parecido a esto:

$this->carabiner->js('mi_archivo1.js'); $this->carabiner->js('mi_archivo2.js');

Es importante resaltar que únicamente es necesario poner el nombre del archivo, y no la ruta de directorios, dado que la ruta ya la hemos especificado en el archivo de configuración de Carabiner. El modo de funcionamiento de carabiner es que cada archivo que nosotros incluimos él lo va incluyendo en una “cola”, de forma que al final, cuando nosotros lo indiquemos todos esos archivos se minimizarán y enviarán al cliente.

Para la inclusión de los “.css” yo recomiendo hacerlo a través de una librería, llamémosla css_loader, en la que tendremos una función que simplemente hará las inclusiones de los archivos “.css” necesarios para la correcta maquetación del sitio, de la forma:


function loader_css()
{
$ci =& get_instance();
...
$ci->carabiner->css('mi_archivo1.css');
$ci->carabiner->css('mi_archivo2.css');
$ci->carabiner->css('mi_archivo3.css');
...
}

De forma que en nuestra plantilla principal, dentro del bloque <head> nuestro código sería algo así:


<?php css_loader->load_css(); ?>

Enviando los archivos al cliente

Por último, ya sólo nos queda ordenarle a Carabiner que genere los archivos minimizados y los envíe al navegador con el resto de la página HTML. Para generar los archivos css, podríamos hacerlo así:
...
<?php
$this->css_loader->load_css();
$this->carabiner->display('css');
?>
...

De esta forma se enviarán minimizados todos los css previamente añadidos a Carabiner en nuestra librería. Para generar el archivo js minimizado, yo recomiendo hacerlo justo antes del tag </body> de nuestra plantilla principal, para no retrasar la carga de la página con la ejecución de los archivos JavaScript en el navegador, nuestro código sería algo así:


...
<?php
$this->carabiner->display('js');
$this->carabiner->empty_cache('both','yesterday');
?>
</body>

La función empty_cache(‘both’,'yesterday’) hará que Carabiner elimine del directorio cache los archivos generados ayer, tanto los “.js” como los “.css”, así evitamos que este directorio empiece a llenarse de archivos de forma innecesaria cada vez que nosotros modificamos alguno de nuestros “.js” o “.css”, dado que, aunque los mantiene en cache, y no se genera un nuevo archivo cada vez que un usuario accede a nuestro sitio, sí se genera un nuevo archivo minimizado cuando nosotros modificamos uno de nuestros “.js” o “.css”.

Con esto ya lo tendríamos, y la velocidad de carga en el navegador de nuestro sitio web ha de verse mejorada.

Hoy vamos a ver cómo podemos crearnos una barra de progreso de subida de archivos en CodeIgniter sin la necesidad de recurrir a librerías o plug-ins hechos en Flash (del tipo SWFUpload, etc) que escapan a nuestro control, dado que habitualmente estas librerías nos ofrecen los archivos .swf ya compilados, por lo que nos es imposible modificar nada en ellos, en el caso de que tengamos conocimientos de programación en ActionScript 2 o 3.

Antes que nada hemos de decir que crear una barra de progreso de subida de archivos en PHP no es tan sencillo como pudiera parecer. El primer problema es que las versiones de PHP anteriores a la 5.2 no ofrecen las herramientas necesarias para poder ofrecer información sobre cómo la subida del archivo en cada momento. El segundo problema es que AJAX, por sí solo, no nos permite consultar el estado de la subida del archivo, dado que, por razones de seguridad obvias, JavaScript no tiene acceso a los archivos del sistema operativo del cliente, por lo que necesitaremos un “truco” utilizando un iframe.

1. Preparando nuestro servidor

Para poder implementar nuestra Upload progress bar necesitamos que nuestro servidor PHP sea de la versión 5.2 o superior. Lo siguiente que necesitamos es tener instalado en el servidor PHP el módulo APC (Alternative PHP Cache). Este módulo será el que nos permita conocer el estado de la subida del archivo en todo momento. Para comprobar esto podemos escribir el siguiente código en cualquier archivo .php que tengamos en el servidor:

<?php phpinfo(); ?>

Si todo está bien, hemos de ver que nuestra versión de servidor es 5.2 o superior y que el módulo APC está instalado y con los siguientes parámetros y valores:

  • apc.rfc1867 = On
  • apc.rfc1867_freq = 5k

Con esto ya tendríamos preparado nuestro servidor para reportar el estado de la subida del archivo.

2. El formulario de subida del archivo

Ahora pasamos a preparar el formulario de subida del archivo para el que necesitamos la barra de progreso. Para ello creamos la vista de CodeIgniter que va a contener el formulario, aunque para ello no vamos a utilzar ninguna función del Form_helper, sino que lo haremos en código HTML puro:

<form name="uploadForm" id="uploadForm" action="<?php echo base_url(); ?>upload/upload_file" method="POST" enctype="multipart/form-data" target="uploadIframe" onsubmit="startProgress('<?php echo $upload_id;?>');">
<input type="hidden" name="APC_UPLOAD_PROGRESS" id="progress_key" value="<?php echo $upload_id;?>"/>
<input type="file" name="uploadedFile" size="50" id="uploadedFile"/>
<span>(max. 1.5 GB)</span>
<br />
<input type="submit" value="Upload" />
</form>
<br />
<div id="progressBar"></div>
<i frame id="uploadIframe" name="uploadIframe" src=""></iframe>
<script type="text/javascript">var url = "<?php echo base_url();?>";</script>
<script type="text/javascript" src="<?php echo base_url();?>js/uploadprogress.js"></script>

Como podemos ver, por último añadimos el archivo JavaScript que va a gestionar el progreso de la barra mediante consultas AJAX. Es un formulario HTML estándar excepto por las siguientes peculiaridades:

  • El envío se realiza contra un iframe. Esto es necesario así el código JavaScript puede seguir ejecutándose mientras se realiza la subida del archivo, de otra forma la página se recargaría y se perdería el código JavaScript (AJAX). Cuando hayamos procesado el formulario y el archivo se haya subido, en el iframe es donde mostraremos la respuesta que nos devuelva la función “upload_file” (que es la que procesará el formulario).
  • Hay un campo oculto especial llamado “APC_UPLOAD_PROGRESS” un valor único generado previamente en el controlador. Este valor se pasará también a la función que controla el estado de la subida del archivo y sirve para identificar el archivo del cual queremos obtener el estado de la subida en el servidor. Es importante que este campo oculto esté siempre antes que el campo de tipo “file” en el formulario.
  • Hay un div llamado “progressBar” que será dónde mostremos la barra de progreso.
  • Añadimos el evento “onsumbit” al formulario, para que en cuanto comience el envío del formulario se llame a la función startProgress de JavaScript que será la que lance el control de la subida del archivo por AJAX.

3. Maquetando la barra de progreso

Aquí es donde reside una de las grande ventajas de este método, y es poder maquetar y modificar los colores, tamaños, etc de los componentes de la barra como nosotros queramos y como mejor se adapte a nuestro sitio. En el archivo .css correspondiente donde queramos maquetar la barra de progreso hemos de añadir las siguientes reglas:

iframe#uploadIframe {
 border: 0px none;
 display: none;
 width: 500px;
}
.progressGrey {
 background: #ccc;
 border:1px solid #000;
 color: #666;
 font-size: 13px;
 font-weight:bold;
 height: 15px;
 overflow:hidden;
 padding:1px 0px 1px 3px;
 position:relative;
 white-space:nowrap;
 width: 500px;
}
.progressRed {
 background: #f00;
 border-right:1px solid #000;
 color: #fff;
 font-size: 13px;
 font-weight:boldwhite-space:nowrap;
 height: 15px;
 left:0px;
 overflow:hidden;
 padding:1px 0px 1px 3px;
 position:absolute;
 top:0px;
 white-space:nowrap;
}

4. Nuestra función que reporta el estado de la subida y la función que procesa la subida

Pasamos ahora a crear la función que controlará el estado de la subida del archivo en todo momento. Para ello tenemos un controlador llamado “upload” y dentro tendremos una función llamada “json_get_uploadprogress”. La razón de que comience el nombre por “json_” es porque utilzamos esta “convención” para indicar que la respuesta que nos devuelve esta función es por JSON. El código sería el siguiente:

public function json_get_uploadprogress()
 {
 $data = array();
 if($this->input->post('upload_id'))
 {
 $uploadKey = $this->input->post('upload_id');
 $status = false;
 $percentage = 0;
 $result = "INITIALISING UPLOAD";
 $data["percentage"] = $percentage;
 $data["result"] = $result;
 if (function_exists('apc_fetch'))
 {
 $status = apc_fetch('upload_'.$uploadKey);
 }
 if (is_array($status))
 {
 log_message("error", "STATUS: ".print_r($status,TRUE));
 if (array_key_exists("total", $status) && array_key_exists("current", $status))
 {
 $percentage = round((($status['current'] / $status['total']) * 100),2);
 if ($status['current'] >= $status['total'])
 {
 $percentage = 100;
 $result = "UPLOAD COMPLETE";
 }
else
 {
 $bytes = array('B','KB','MB','GB','TB');
foreach($bytes as $val)
{
if($status["total"]  > 1024)
{
$status["total"] = $status["total"] / 1024;
$status["current"] = $status["current"] / 1024;
}
else
{
break;
}
}
$result =  $percentage."% (".round($status["current"], 2)." ";
$result.= $val." of ".round($status["total"], 2)." ".$val.")";
}
$data["percentage"] = $percentage;
$data["result"] = $result;
}

}
}
$this->load->view('json/json_response_view',array("array" => $data));
}

En la vista “json_response_view” no tenemos más que la siguiente instrucción:

<?php echo json_encode($array); ?>

Esta es la función a la que llamaremos desde AJAX para que nos vaya reportando el estado de la subida del archivo.

Ahora veremos la función dentro del mismo controlador que se encarga de recoger el formulario y procesar la subida del archivo. Esta función devolverá una respuesta cuando finalice, la cual será mostrada en el iframe del formulario.

public function upload_file()
 {
 $path_file = "files/";

 if(!is_dir($path_file))
 {
 mkdir($path_file);
 }

 $config['upload_path'] = $path_file;
 $config['allowed_types'] = 'mp4';
 $tam_gigas = 1.5; //Tamanyo maximo del archivo en gigas
 $config['max_size'] = $tam_gigas * 1048576; //
 $this->load->library('upload');
 $this->upload->initialize($config);
 $div_start = "<div>";
 $div_end = "</div>";
if (!$this->upload->do_upload('uploadedFile'))
 {
 $this->data["params"] = array('success_msg' => $this->upload->display_errors());
 log_message('error','El error: '.$this->upload->display_errors());
 }
 else
 {
 $uploaded_file = $this->upload->data();
 $this->load->view("ajax/generic_response_view",array("resp" => $div_start."The file has been uploaded successfully.".$div_end));
 return;
 }

5. AJAX con jQuery

Por último, tan sólo nos falta crear el archivo JavaScript que consulta el estado de la subida del archivo por AJAX con jQuery. En nuestro caso la consulta se realiza cada 1 segundo, cada cual puede modificar este tiempo como le plazca, pero han de tener en cuenta que si hacen las consultas muy seguidas (por debajo de 1 seg) pueden saturar el navegador del cliente o provocar que las consultas y respuestas se machaquen unas a otras si la conexión del cliente no es muy buena, si el tiempo de consulta es muy grande (por encima del segundo) pueden hacer que el feedback que el usuario recibe sobre el estado de la subida sea muy esporádico, dando la sensación de que la subida se realiza “a saltos”.

Este es el código jQuery de la consulta que colocaremos en el archivo “js/uploadprogress.js”:

$(function(){
 $("input#uploadedFile").bind('change', null, function(){check_file_type()});
});

function getProgress(uploadKey){
$.post(url+"match/json_get_uploadprogress",{"upload_id":uploadKey},
function(data)
{
if(!data) return;
var result = data.result;
var percentage = 0;
percentage = data.percentage;
if (percentage > 0) {
$("#progressBar").html("<h3>Upload progress...</h3><div class='progressGrey'>"+result+"<div class='progressRed' style='width:"+percentage+"%'>"+result+"</div></div>");
} else {
$("#progressBar").html("<h3>Upload progress...</h3><div class='progressGrey'>"+result+"</div>");
}
if (percentage < 100) {
var timeoutID = window.setTimeout("getProgress(theUploadKey)", 1000); //Se consulta cada 1 segundo
}
if(percentage >= 100)
{
$("iframe#uploadIframe").css('display','block');
var body = $("iframe#uploadIframe").contents().find("body");
//Remaquetamos el body del iframe por culpa de IE
body.css('border','0px none');
body.css('background-color',$("body").css('background-color'));
body.css('text-align','center');
}
}, "json"
);
}

function startProgress(uploadKey) {
 theUploadKey = uploadKey;
 $("#progressBar").css('display','block');
 $("#progressBar").html("<h3>Upload progress...</h3><div class='progressGrey'> </div>");
 $("form#uploadForm").css('display','none');
 getProgress(uploadKey);
 return null;
}

Con esto ya tendríamos funcionando nuestra barra de progreso para la subida de archivos

Hoy vamos a hablar un poco del plug-in para jQuery scrollTo y su dependencia “oculta” con la carga de nuestros archivos CSS.

scrollTo de jQuery

El plug-in scrollTo de jQuery (desarrollado por Ariel Flesler) nos permite que nuestra página web realice un scroll (desplazamiento de la barra vertical) hasta cualquier elemento de nuestro documento web que necesitemos, de esta forma podemos centrar la atención del usuario en dicho elemento cada vez que se recarga la página (por ejemplo, una entrada del menú de la izquierda en nuestra web), evitándole al usuario tener que hacerlo él manualmente. En la web del proyecto del plug-in podrás encontrar toda (o casi toda) la documentación necesaria para integrarlo en tu proyecto web. Demás está decir que para poder utilizarlo has de incluir también en tu proyecto el propio framework JavaScript jQuery. A este plug-in que tan útil me ha sido sólo le encontré un pequeño fallo de documentación…

Interacción entre scrollTo y nuestro CSS

El fallo de documentación del que hablo es que en ningún lugar se nos explica (quizá el propio Ariel no esté al tanto de ello…) que para que el plug-in funcione a la perfección necesita que todas las reglas CSS del elemento al que le queremos aplicar el scrollTo estén cargadas en el navegador antes de la ejecución de la función scrollTo. Es decir, que si intentamos ejecutar el scrollTo y las reglas CSS del elemento al que se debe aplicar el scroll, aún no hay sido leídas e interpretadas por el navegador del cliente, el scroll no se puede realizar, y además no nos lanza ningún error, por lo que, a ojos del cliente, el scroll nunca se ha llegado siquiera a ejecutar.

La solución que a priori se nos puede ocurrir es llamar a la función scrollTo una vez que todos los elementos del DOM estén cargados en el navegador. Para ello utilizaríamos la siguiente sintaxis de jQuery:

$(function(){
$('#elemento').scrollTo($('#elementoDestino'));
});

Esto podría valer, sólo tiene un incoveniente y es que la función $(function…) de jQuery ejecuta el scrollTo una vez que nuestro DOM está cargado y listo para usarse, pero eso no significa que se hayan aplicado todas las reglas CSS al elemento “#elemento” ni al objeto “#elementoDestino”, por lo que el scrollTo puede funcionar… o no, todo depende de lo rápido que nuestro navegador cargue las reglas CSS.

La solución

La única solución que nos queda (mientras que los desarrolladores de navegadores no implementen el lanzamiento de un evento una vez que se ha cargado el CSS por completo) es utilizar el evento load del objeto window del navegador.

window.load = function(){$('#elemento').scrollTo($('#elementoDestino'));};

El evento load se lanza una vez que toda la página ha sido cargada en el navegador, incluidas las reglas CSS. Puede no ser la solución más elegante, pero es la única que nos soluciona el problema.

De todas formas, y quitando este pequeño detalle de la documentación, el plug-in scrollTo me parece un plug-in genial que nos ahorra muchos dolores de cabeza.

Hoy vamos a solucionar un bug que encontré en la actualización de ImageFlow, pasando de la versión 1.2.1 a la 1.3. La nueva versión, pretende entre otras cosas solucionar el problema que había en Internet Explorer cuando únicamente había una imagen en la galería. Este problema era que no se mostraba nada si sólo teníamos una imagen en la galería en IE. Pues bien, la versión 1.3 ya muestra la imagen si sólo tenemos una, pero si además esa imagen tiene un “caption” (pie de foto), pues resulta que el caption no nos los muestra, ni en Internet Explorer ni en ningún otro navegador. Pues bien, aquí les dejo la solución al bug para aquellos que deseen (y se atrevan) a meterle mano al código de la librería.

Solucionando el bug

Para solucionar el problema hemos de abrir el archivo imageflow.js que nos encontramos en el .zip que nos hemos descargado. Vamos a la línea 526 aproximadamente donde nos encontramos el siguiente bloque de código:

/* Only animate if there is more than one image */
 if(my.max > 1)
 {
 my.glideTo(my.imageID);
 }

En este bloque de código, el autor comprueba si hay más de una imagen, en cuyo caso llama a una función que entre otras cosas, aplica el caption correspondiente a la imagen actual (my.imageID). Pues bien, a este bloque if simplemente tenemos que añadirle un bloque else donde contemplamos el caso en el que no haya más de una imagen (es decir, exactamente 1). El bloque completo nos debería quedar algo así:

...
/* Only animate if there is more than one image */
 if(my.max > 1)
 {
 my.glideTo(my.imageID);
 }
 else
 {
 var caption = my.imagesDiv.childNodes[my.imageID].getAttribute('alt');
 if (caption === '' || my.captions === false)
 {
 caption = '&nbsp;';
 }
 my.captionDiv.innerHTML = caption;
 }
...

De esta forma, aunque sólo tenemos una imagen, nos aseguramos de que esa imagen tenga su caption correspondiente. Ya he avisado al autor de este pequeño fallo y esperemos que lo tenga en cuenta para la próxima actualización. Espero que les haya sido de ayuda!