miércoles, 24 de julio de 2013

Codeigniter 2: cómo insertar multiples filas en una tabla

Insertar múltiples filas en CodeIgniter 2, útil para manejadores como MySQL y PostgreSQL

Este artículo lo tomé de http://codefury.net/2009/12/insert-multiple-rows-into-a-database-with-codeigniter/, pero lo he adaptado para funcionar con CI2

Paso 1: Extender la clase Model.php

En la carpeta application/core/ crear la clase MY_Model.php, el prefijo MY_ debe ser el mismo que se encuentra establecido en el archivo config.php en el elemento 'subclass_prefix'

<?php

/**
 * @link http://codefury.net/2009/12/insert-multiple-rows-into-a-database-with-codeigniter/ Hacer multiples inserts
 * @author KENNY KATZGRAU
 */
class MY_Model extends CI_Model
{

    public function __construct()
    {
        parent::__construct();
    }

    /**
     * A method to facilitate easy bulk inserts into a given table.
     * @param string $table_name
     * @param array $column_names A basic array containing the column names of the data we'll be inserting
     * @param array $rows A two dimensional array of rows to insert into the database.
     * @param bool $escape Whether or not to escape data that will be inserted. Default = true. Para otros drivers diferentes a MySQl, habría que poner FALSE
     * @author Kenny Katzgrau 
     */
    public function insert_rows($table_name, $column_names, $rows, $escape = true)
    {
        /* Build a list of column names */
        $columns = array_walk($column_names, array($this, 'prepare_column_name'));
        $columns = implode(',', $column_names);

        /* Escape each value of the array for insertion into the SQL string */
        if ($escape){
            array_walk_recursive($rows, array($this, 'escape_value'));
        }
        
        # asignar un table-prefix si está definido en el archivo database.php
        $table_name = $this->db->dbprefix($table_name);

        /* Collapse each rows of values into a single string */
        $length = count($rows);
        for ($i = 0; $i < $length; $i++){
            $rows[$i] = implode(',', $rows[$i]);
        }

        /* Collapse all the rows into something that looks like
         *  (r1_val_1, r1_val_2, ..., r1_val_n),
         *  (r2_val_1, r2_val_2, ..., r2_val_n),
         *  ...
         *  (rx_val_1, rx_val_2, ..., rx_val_n)
         * Stored in $values
         */
        $values = "(" . implode('),(', $rows) . ")";

        $sql = "INSERT INTO {$table_name} ({$columns}) VALUES {$values}";
        
        /*
        $querie_file = PATH_APP_DEBUGS . 'queries_' . date('Y-m-d') . '.log';
        file_put_contents($querie_file, $sql, FILE_APPEND);
         * 
         */

        return $this->db->simple_query($sql);
    }

    public function escape_value(& $value)
    {
        if (is_string($value)) {
            $value = "'" . mysql_real_escape_string($value) . "'";
        }
    }

    public function prepare_column_name(& $name)
    {
        $name = "`$name`";
    }

}

Paso 2: Ejemplo A

He aquí un ejemplo para probar la inserción de varias filas en un mismo query:

class Messages extends MY_Model 
{   
  /* Code .. */   
  function insert_test_data()   
  {     
    /* Prepare some fake data (10000 rows, 40,000 values total) */
    $rows = array_fill(0, 10000, array(34239, 102438, "Test Message!", '2009-12-12'));
    $columns = array('to_user_id', 'from_user_id', 'message', 'created');
    $this->insert_rows('messages', $columns, $rows); 
  }
}

Paso 1: Ejemplo B

Otro ejemplo, de cómo se usaría en un método típico de un modelo en CodeIgniter:

<?php

class M_usuarios extends MY_Model
{

    private $_enc_key;
    protected $_usuario_id;

    public function asignar_permisos()
    {
        $this->_usuario_id = (int) $this->input->post('sel_campo01');
        $this->db
                ->where('usuario_id', $this->_usuario_id)
        ;
        try {
            $this->db->trans_start();

            $this->db->query('LOCK TABLES t_ci_sessions LOW_PRIORITY WRITE');
            $this->db->query('LOCK TABLES t_usuarios_t_submenus LOW_PRIORITY WRITE');

            # primero, eliminar todos los permisos asignados
            $delete = $this->db->delete('usuarios_t_submenus');

            # segundo, insertar todos los permisos recién asignados
            $a_columns = array('usuario_id', 'submenu_id');
            $a_rows = $this->_preparar_datos_permisos($this->_usuario_id, $this->input->post('sel_campo02'));

            # método insert_rows() que nosotros definimos en la clase MY_Model.php
            $insert = $this->insert_rows('usuarios_t_submenus', $a_columns, $a_rows);
            
            $this->db->query('UNLOCK TABLES');
            $this->db->trans_complete();
            
            return array('insert' => TRUE, 'mensaje' => '');
        } catch (Exception $e) {
            $this->db->query('UNLOCK TABLES');
            $this->db->trans_complete();

            return array('insert' => FALSE, 'mensaje' => $e->getMessage());
        }
    }

    private function _preparar_datos_permisos($usuario_id, $permisos)
    {
        $filas = array();
        foreach ($permisos as $_submenu_id){
            $filas[] = array($usuario_id, $_submenu_id);
        }
        
        return $filas;
    }
}

Eso es todo, espero que les sea de utilidad.

viernes, 12 de julio de 2013

Cómo traducir y personalizar los mensajes del plugin jQuery Validation

Te paso un archivo de traducción de los mensajes predeterminados del plugin jQuery Validation, además en la segunda parte de este post, te mostraré como personalizar mensajes en lugar de usar los textos generales.

Puedes descargar el archivo de traducción (internacionalización) desde aquí. El ejemplo es en español mexicano.

1. El archivo

(function($) {
    $(document).ready(function(e) {
        if ($.fn.validate) {
            jQuery.extend(jQuery.validator.messages, {
                required: "Este campo es obligatorio.",
                remote: "Revisar este campo.",
                email: "La dirección de correo no es válida.",
                url: "La URL no es válida.",
                date: "La fecha no es válida.",
                dateISO: "La fecha no es válida, debe tener formato ISO.",
                number: "El número no es válido.",
                digits: "Solamente se deben ingresar números.",
                creditcard: "El número de tarjeta no es válido.",
                equalTo: "El valor no coincide.",
                maxlength: $.validator.format("Máximo {0} caracteres."),
                minlength: $.validator.format("Mínimo {0} caracteres."),
                rangelength: $.validator.format("Debe tener entre {0} y {1} caracteres."),
                range: $.validator.format("Debe tener un valor entre {0} y {1}."),
                max: $.validator.format("Debe tener un valor menor o igual a {0}."),
                min: $.validator.format("Debe tener un valor mayor o igual a {0}.")
            });
        }
    });
})(jQuery);

2. La implementación

Invocas los archivos .js, algo parecido a esto:




Implementas el plugin jQuery Validate


Eso es todo, espero que les haya sido de utilidad

miércoles, 10 de julio de 2013

Encriptar y desencriptar cadenas con PHP

Crear una función para encriptar - desencriptar (encrypting two way) usando PHP

Útil para enmascarar urls, variables por post, get, datos, números de tarjetas de crédito, etc.

Puedes usarla para programación scriptera, orientada a objetos, como helper en algún framework, tales como CodeIgniter, Zend, Cake, Yii, etc.

Bueno, aquí vamos:

<?php
function _enc($s)
{
    $str = (string) $s;
    
    if ('' == trim($str)) {
        return '';
    }
    for ($i = 0; $i < strlen($str); $i++) {
        $r[] = hexdec(decoct(ord($str[$i]) + 3));
    }

    return implode(',', $r);
}

function _dec($s)
{
    $str = (string) $s;
    
    if ('' == trim($str)) {
        return '';
    }
    $s = explode(',', $str);
    for ($i = 0; $i < count($s); $i++) {
        $s[$i] = chr(octdec(dechex($s[$i])) - 3);
    }

    return implode('', $s);
}
?>

El artículo original de este código viene desde www.hawkee.com/snippet/5086/, yo le añadí doble cifrado, uno en octal y el resultado obtenido lo paso a hexadecimal, esto lo hice porque si lo dejaba en decimal es bien fácil encontrar que los caracteres corresponden a códigos ASCII y cualquiera podría descifrar el contenido.

Espero les sea de utilidad.

jueves, 27 de junio de 2013

Cómo crear un sitemap con CodeIgniter 2 para enviarlo a Google Webmasters Tools

Para crear un sitemap en formato xml con CodeIgniter 2, para enviarlo a google, necesitamos de los siguientes pasos.

Paso 1: Modificar el archivo routes.php, ubicado en la carpeta config

El cual se encuentra en la carpeta config/. Agregamos una nueva línea al arreglo:

            $route['sitemap.xml'] = 'sitemap/index';
        

Paso 2: Crearemos entonces el controlador sitemap.php

            <?php

            class Sitemap extends CI_Controller
            {

                public function __construct()
                {
                    parent::__construct();
                }

                public function index()
                {
                    # este helper se puede cargar en el archivo autoload.php
                    # para evitar cargarlo cada vez
                    $this->load->helper('url');

                    $this->load->model('sitemap_model');

                    $view_data['vw_content'] = $this->sitemap_model->get_articulos();

                    $this->load->view('sitemap_view', $view_data);
                }

            }
        

Paso 3: Creamos el modelo sitemap_model.php

            <?php

            class Sitemap_model extends CI_Model
            {

                public function __construct()
                {
                    parent::__construct();

                    # esta librería se puede cargar desde el archivo autoload.php
                    # para evitar cargarlo cada vez
                    $this->load->database();
                }

                public function get_articulos()
                {
                    $this->db
                            ->select('articulo_id, alias, modificado')
                            ->from('articulos')
                            ->where('activo', 1)
                            ->order_by('articulo_id')
                    ;

                    $query = $this->db->get();

                    return $query->result();
                }

            }
        

En nuestro ejemplo, vamos a suponer que la estructura de la tabla es como la siguiente:

Paso 4: Creamos la vista sitemap_view.php

            <?php ob_start(); ?>
            <?php echo '<?xml version="1.0" encoding="UTF-8"?>'."\n"; ?>
            <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
                <url>
                    <loc><?php echo base_url(); ?></loc>
                    <priority>1.0</priority>
                </url>

                <!-- Sitemap de mi blog.com -->
                <?php foreach ($vw_content as $url) : ?>
                <url>
                    <loc><?php echo base_url() . 'index.php/blog/articulos/' . $url->articulo_id . '/' . $url->alias . '/'; ?></loc>
                    <lastmod><?php echo substr($url->modificado, 0, 10); ?></lastmod>
                    <changefreq>monthly</changefreq>
                    <priority>0.5</priority>
                </url>
                <?php endforeach; ?>
            </urlset>
            <?php ob_end_flush(); ?>
        

Paso 5: Probar tu sitemap ejecutando (por ejemplo) http://localhost/ci2/index.php/sitemap.xml

¡Listo! Si todo salió bien pueder ver el codigo fuente generado y verás un archivo .xml bien formado, el cual estará listo para enviarlo a Google en su página de herramientas para Webmasters.

martes, 18 de junio de 2013

Cómo hacer una prueba de estrés con JMeter

Les presento sencillos pasos para diseñar, ejecutar y obtener los resultados de una prueba de estrés usando JMeter con una aplicación basada en Web construida en PHP (para este ejemplo).

Tenemos que crear un escenario o plan de pruebas, para ello, nos vamos a auxiliar de JMeter, el cual puedes descargar desde el sitio de apache http://jmeter.apache.org/download_jmeter.cgi.

Paso 1: Instalar y ejecutar JMeter

La instalación simplemente es descomprimir el .zip o el .tgz en tu disco duro. Una vez hecho esto, en la carpeta de JMeter hay una carpeta bin, ejecutar el archivo jmeter.bat

Paso 2: Configurar JMeter para el escenario de prueba

Para efectos de este ejemplo, prefiero usar el idioma en inglés, pues la traducción al castellano es literal y a menudo es difícil navegar entre las opciones.

Así que para cambiar el idioma de español a inglés: Menú Opciones >> Elija lenguaje >> Inglés

Seleccionamos el nodo de Test Plan y le ponemos un nombre que nos ayude a identificarlo, luego le damos clic derecho y Add >> Thread Group

Llenamos los campos Name, y en el apartado de Action to be taken after a Sample error

En el apartado de Thread Properties, Number of Threads (users), Ramp-up Period (in seconds), Loop Count __ Forever, como se muestra en la imagen siguiente:

Paso 3: Configurar los valores de la petición HTTP

Sobre el nodo de tu nuevo Thread Group, haz clic derecho y Add >> Config Element >> HTTP Request Defaults

Selecionamos el elemento agregado y configuramos sus parámetros.

El campo Name, le pones un nombre descriptivo o lo puedes dejar así, en este ejemplo, prefiero dejarlo como viene el default

En el apartado Web Server, llenar los campos Server Name or IP:, Port Number, con los datos del servidor que quieras probar, en este ejemplo, el servidor está en una LAN y accedemos al aplicativo que queremos probar mediante el puerto 8084, pero una configuración típica si pruebas en tu localhost es poner 127.0.0.1 y en puerto 8080.

En el apartado HTTP Request, en la lista Implementation, seleccionar HttpClient4, en el campo Protocol [http], no hay más que poner http, en el campo Content encoding poner el que tú manejas en tus páginas web, en mi caso, utilizo UTF-8.

En el campo Path, poner la ruta de la raíz de tu aplicativo, puede ser / o en mi caso, utilizo /ejemplo_jmeter

Paso 4: Configurar el HTTP Proxy Server

En el nodo Workbench, acer clic derecho y Add >> Non-Test Elements >> HTTP Proxy Server

Puedes darle un nombre o dejar el que tiene por default, en mi ejemplo he decidio dejar el default. En el apartado Global Settings, en Port: 8080

En el apartado Test plan content, en la lista Target Controller seleccionar de la lista el plan de pruebas que vamos a generar y este va a ser el repositorio que va a recibir todas las peticiones que se van a "grabar".

En la imagen de abajo verán los demás parámetros, después con más práctica y de acuerdo a la necesidad específica de cada proyecto, pueden modificar los demás parámetros.

Paso 5: Configurar el navegador donde se va a ejecutar la prueba

Es necesario configurar un servidor proxy, pues una vez que se ejecute el escenario de pruebas que dimos de alta en JMeter, éste se convierte en un proxy, por el cual van a pasar todas peticiones HTTP.

Para el ejemplo, asumo que como todo buen programador, usan cualquier navegador que no sea Internet Explorer, así que en la imagen van a ver la configuración proxy para Mozilla Firefox:

Paso 6: Ejecutar el proceso de grabación de nuestro escenario de pruebas

Ahora en la pantalla de HTTP Proxy Server que acabamos de configurar, presionamos el botón Start, para iniciar el servidor proxy.

Ingresamos al navegador al cual tiene configurado que use el proxy, e introducimos la url de nuestro aplicativo que queremos probar.

Navegamos dentro de nuestra aplicación para que comience a "grabar" nuestro escenario de pruebas, el concepto es parecido a grabar una macro.

Una vez que ya recorrimos todo lo que nos interesa "testear", podemos regresar a JMeter y damos Stop al servidor proxy (que se ubica en el nodo Workbench >> HTTP Proxy Server).

Entonces, ahora nuestro escenario de pruebas ya tiene varias ramas, que debe verse más o menos como en la siguiente imagen:

Paso 7: Hay que agregar unos "Listeners" que van a recopilar los resultados de la prueba y nos darán ciertos resultados

Hay algunos que son muy interesantes, pero para esta primera prueba, te recomiendo agregar el listener View Results in Table, entonces haz clic derecho en el Thread Group, que en este ejemplo le pusimos por nombre "Proceso de login", Add >> Listener y finalmente View Results in Table.

Se pueden agregar varios listeners a la vez. Todo depende de qué tipo de resultados deseas obtener y cómo quieres que sean presentados.

Paso 8: Ejecutar el escenario de pruebas ¡por fin!

Ahora sí, seleccionas el nodo Thread Group (el cual le pusimos por nombre "Proceso de login" y presionamos Ctrl + R, ó menú Run >> Start ó podemos hacer clic en el botón verde de start (el típico ícono de play).

¡A disfrutar!

Tip extra:

Voy a ponerte un posible escenario: en mi ejemplo, vamos a ejecutar una prueba de estrés usando un usuario para hacer el login, pero como hay una regla de negocio que indica que no pueden haber un mismo usuario conectado más de 1 vez simultáneamente, la solución es introducir diferentes usuarios para ejecutar todas las conexiones concurrentes para la prueba de estrés.

Para lograr esto, lo que tenemos que hacer es agregar parámetros de usuario los cuales podemos ingresar haciendo clic derecho en el nodo Thread Group, que en este ejemplo le pusimos por nombre "Proceso de login", Add >> Pre Processors >> User Parameters.

Con el botón Add Variable agregamos las dos variables que necesitamos para username y password.

Con el botón Add User agregamos varios usuarios con su password de nuestro aplicativo, en mi ejemplo introduje 100 usuarios de mi base de datos, ver imagen de abajo:

Luego, debes ubicar en el árbol de requests el recurso que envía los datos de usuario y contraseña, en mi ejemplo, lo señalo en la imagen de abajo, y verás como llenar los campos para inyectar los diferentes valores usando unas variables, típicamente se colocan con el siguiente formato: ${variable}

Finalmente, volver a ejecutar como lo dijimos en el paso 8.

Bueno, espero haber que este post les sea de utilidad, creo que todo programador aunque no sea tester, debe hacer pruebas de rendimiento, la prueba de estrés es solamente una de ellas.

martes, 4 de junio de 2013

Linux: unzip convierte todo a minúsculas de un archivo creado con Winzip

Me ocurrió que cuando usaba unzip para descomprimir un archivo en Linux, que fue creado en Windows (con Winzip), me convertía todos los nombres de archivo en minúsculas, ocasionándome problemas al ejecutar mi código de una aplicación web.

La solución que encontré fue la siguiente:

Revisé si el comando unzip tenía algún alias que forzara la conversión a minúsculas con el parámetro -L

# alias

y efectivamente, ahí estaba el alias para unzip:

...
alias unzip='unzip -L'
...

Así que todo lo que hice fue quitar el alias de la siguiente manera:

unalias unzip

Si deseas que el cambio sea permanente puedes agregarlo a tu .bashrc, en este link puedes ver cómo hacerlo: "Cómo crear un shell .bashrc con servidores Linux de 1and1 Business Package"

jueves, 30 de mayo de 2013

Equivalente de OFFSET y LIMIT en Informix

El equivalente a OFFSET y LIMIT pero en Informix son SKIP y FIRST respectivamente

He aquí unos ejemplos entre MySql e Informix, para el muy recurrido ejemplo de paginación de resultados.

/* En MySql: */
--traer los registros del 1 al 10
SELECT * FROM mitabla LIMIT 10 OFFSET 0;

--traer los registros del 11 al 20
SELECT * FROM mitabla LIMIT 10 OFFSET 10;

--traer los registros del 21 al 30
SELECT * FROM mitabla LIMIT 10 OFFSET 20;


/* En Informix: */
--traer los registros del 1 al 10
SELECT SKIP 0 FIRST 10 * FROM mitabla;

--traer los registros del 11 al 20
SELECT SKIP 10 FIRST 10 * FROM mitabla;

--traer los registros del 21 al 30
SELECT SKIP 20 FIRST 10 * FROM mitabla;

Espero que les sea de utilidad

Crear una función en Informix para reemplazar acentos y caracteres especiales

Sencillo script para crear una función de usuario dentro de Informix, hay que tener en cuenta que a partir de la versión 10 de Informix ya no hay soporte para el comando CREATE FUNCTION, por eso este código funciona a partir de la versión 10+.

/* Editar este archivo con codificación UTF-8 */

/* DROP PROCEDURE mibasededatos:fun_quitar_acentos; */


CREATE PROCEDURE mibasededatos:fun_quitar_acentos (CADENA lvarchar)
RETURNING lvarchar;
  DEFINE TempString lvarchar;
  LET TempString = CADENA;
  
  LET TempString =  REPLACE(TempString, 'à', 'a');
  LET TempString =  REPLACE(TempString, 'è', 'e');
  LET TempString =  REPLACE(TempString, 'ì', 'i');
  LET TempString =  REPLACE(TempString, 'ò', 'o');
  LET TempString =  REPLACE(TempString, 'ù', 'u');
  LET TempString =  REPLACE(TempString, 'À', 'A');
  LET TempString =  REPLACE(TempString, 'È', 'E');
  LET TempString =  REPLACE(TempString, 'Ì', 'I');
  LET TempString =  REPLACE(TempString, 'Ò', 'O');
  LET TempString =  REPLACE(TempString, 'Ù', 'U');
  LET TempString =  REPLACE(TempString, 'ñ', 'n');
  LET TempString =  REPLACE(TempString, 'Ñ', 'n');
  LET TempString =  REPLACE(TempString, 'á', 'a');
  LET TempString =  REPLACE(TempString, 'é', 'e');
  LET TempString =  REPLACE(TempString, 'í', 'i');
  LET TempString =  REPLACE(TempString, 'ó', 'o');
  LET TempString =  REPLACE(TempString, 'ú', 'u');
  LET TempString =  REPLACE(TempString, 'Á', 'A');
  LET TempString =  REPLACE(TempString, 'É', 'E');
  LET TempString =  REPLACE(TempString, 'Í', 'I');
  LET TempString =  REPLACE(TempString, 'Ó', 'O');
  LET TempString =  REPLACE(TempString, 'Ú', 'U');
  LET TempString =  REPLACE(TempString, 'ç', 'c');
  LET TempString =  REPLACE(TempString, 'Ç', 'C');
 
  RETURN TempString;
END PROCEDURE;

/* comprobaciones */
execute procedure fun_quitar_acentos('Prueba: áéíóú Nñ ÁÉÍÓÚ');

SELECT descripcion, fun_quitar_acentos(descripcion) AS desc_sin_acentos FROM mi_tabla ORDER BY descripcion;

Este script va a crear una función de usuario dentro de Informix

Espero que les sea de utilidad

Como convertir un archivo o string XML en un array en PHP 5, usando SimpleXML

La solución es muy sencilla, para este ejemplo, utilizaremos la librería SimpleXML. Esta libería requiere la extensión de PHP libxml y funciona a partir de PHP 5.

Voy a usar el xml de ejemplo que viene en la documentación de PHP.
# aquí debes poner la ruta y el nombre de tu archivo xml
$xmlstring = realpath(dirname(__FILE__)) . '/xmls/prueba.xml';

if (!file_exists($xmlstring)) {
   trigger_error(__FILE__: {$xmlstring}", E_USER_ERROR);
   return;
}

$xml = simplexml_load_file($xmlstring);
        
$json = json_encode($xml);
$array = json_decode($json, TRUE);
echo '<pre>';
print_r($xml);
echo '</pre>';

echo '<pre>';
print_r($array);
echo '</pre>';
Nota: el inconveniente de pasar el objeto XML a un array con esta técnica, es que se pierden los atributos de los elementos, como en el caso de los atributos del elemento <puntuacion>, que se pierden los atributos "tipo", pero seguramente para determinados casos esta sencilla técnica puede servir muy bien.

El resultado de este ejemplo para el objeto simpleXML sería:

SimpleXMLElement Object
(
    [pelicula] => SimpleXMLElement Object
        (
            [titulo] => PHP: Tras el Parser
            [personajes] => SimpleXMLElement Object
                (
                    [personaje] => Array
                        (
                            [0] => SimpleXMLElement Object
                                (
                                    [nombre] => Srta. Programadora
                                    [actor] => Onlivia Actora
                                )

                            [1] => SimpleXMLElement Object
                                (
                                    [nombre] => Sr. Programador
                                    [actor] => El Actor
                                )

                        )

                )

            [argumento] => 
            Así que, este lenguaje. Es como, un lenguaje de programación. ¿O es un
            lenguaje interpretado? Lo descubrirás en esta intrigante y temible parodia
            de un documental.
        
            [grandes-lineas] => SimpleXMLElement Object
                (
                    [linea] => PHP soluciona todos los problemas web
                )

            [puntuacion] => Array
                (
                    [0] => 7
                    [1] => 5
                )

        )

)

El resultado de este ejemplo para el array sería:

Array
(
    [pelicula] => Array
        (
            [titulo] => PHP: Tras el Parser
            [personajes] => Array
                (
                    [personaje] => Array
                        (
                            [0] => Array
                                (
                                    [nombre] => Srta. Programadora
                                    [actor] => Onlivia Actora
                                )

                            [1] => Array
                                (
                                    [nombre] => Sr. Programador
                                    [actor] => El Actor
                                )

                        )

                )

            [argumento] => 
            Así que, este lenguaje. Es como, un lenguaje de programación. ¿O es un
            lenguaje interpretado? Lo descubrirás en esta intrigante y temible parodia
            de un documental.
        
            [grandes-lineas] => Array
                (
                    [linea] => PHP soluciona todos los problemas web
                )

            [puntuacion] => Array
                (
                    [0] => 7
                    [1] => 5
                )

        )

)

viernes, 17 de mayo de 2013

CodeIgniter 2: Implementar mecanismo de idiomas para tus aplicaciones PHP

CodeIgniter 2 trae su propio sistema de regionalización de idiomas, pero francamente me parece poco práctico, pues se basa en crear arreglos asociativos con las diferentes cadenas de traducción, el problema de esta técnica es que se requiere consultar constantemente el archivo de configuración para ver el nombre de la llave del arreglo y poder obtener la cadena, en cambio, sugiero usar un sistema más parecido al que usa Magento, que traduce la cadena en función de la cadena misma, y utiliza un sencillo sistema de archivos separados por comas y en pares.

Así que los pasos para implementar un sistema de regionalización de idiomas son:

Paso 1.

Crear en la carpeta application/language/, los diferentes códigos de idiomas, en mi caso, utilzaré en_US/ y es_MX/

Paso 2.

Nuestra base siempre va a ser el inglés de estados unidos, así que vamos a crear en la carpeta en_US un archivo llamado core_general.csv

Paso 3.

Es importante señalar que uno puede organizar los archivos de idiomas según su ámbito, por ejemplo, podemos crear uno llamado core_exceptions.cvs, otro para core_database.csv, otro para sales.csv, otro para checkout.csv, otro crm.csv, etc.

Paso 4.

La técnica es usar los archivos separados por comas en pares: tal como se muestra el ejemplo, recordemos que la base es el idioma inglés de E.U., en este ejemplo te muestro los dos archivos, el que colocaremos en la carpeta en_US/ y el que colocaremos en la carpeta es_MX/, con esto ya se va comprendiendo más la lógica:

en_US/core_general.csv

"ERP","ERP"
"MRP","MRP"
"CRM","CRM"
"FRM","FRM"
"HRM","HRM"
"SCM","SCM"
"Enterprise Resource Planning","Enterprise Resource Planning"
"Material Resource Planning","Material Resource Planning"
"Customer Relationship Management","Customer Relationship Management"
"Financial Resource Management","Financial Resource Management"
"Human Resource Management","Human Resource Management"
"Supply Chain Management","Supply Chain Management"
"Inbox","Inbox"
"Personal","Personal"
"Important","Important"
"Pending","Pending"
"Archived","Archived"
"archived","archived"
"Schedule","Schedule"
"Calendar","Calendar"
"Notes","Notes"
"Groups","Groups"
"Messages","Messages"
"Create/Join Groups","Create/Join Groups"
"Go!","Go!"
"Search...","Search..."
"Edit Profile","Edit Profile"
"View Profile","View Profile"
"View All","View All"
"Logout","Logout"
"Login","Login"
"",""
"",""
"",""
"",""
"",""
"",""

es_MX/core_general.csv

"ERP","PRE"
"MRP","PRM"
"CRM","GAC"
"FRM","GRF"
"HRM","GRH"
"SCM","GCS"
"Enterprise Resource Planning","Planificación de recursos empresariales"
"Material Resource Planning","Planificación de recursos materiales"
"Customer Relationship Management","Gestión de atención al cliente"
"Financial Resource Management","Gestión de recursos financieros"
"Human Resource Management","Gestión de recursos humanos"
"Supply Chain Management","Gestión de la cadena de suministro"
"Inbox","Bandeja de entrada"
"Personal","Personal"
"Important","Importante"
"Pending","Pendiente"
"Archived","Archivados"
"archived","archivados"
"Schedule","Agenda"
"Calendar","Calendario"
"Notes","Notas"
"Groups","Grupos"
"Messages","Mensajes"
"Create/Join Groups","Crear/Unir grupos"
"Go!","Ir..."
"Search...","Buscar..."
"Edit Profile","Modificar perfil"
"View Profile","Ver perfil"
"View All","Ver todo"
"Logout","Desconectar"
"Login","Conectar"
"",""
"",""
"",""
"",""
"",""
"",""

Paso 5.

Ahora, vamos a crear un helper, llamado translate_helper.php y lo vamos a colocar en la carpeta application/helpers/

<?php

/**
 * Regresa una cadena con la traducción especificada.
 * Si se especifica $code, se regresa la cadena en ese idioma, 
 * si no se envía el parámetro opcional $code, entonces la 
 * cadena es regresada en el idioma especificado en el archivo 
 * config.php de CI.
 * 
 * @param string $texto La cadena a traducir
 * @param string $archivo El archivo de traducción, por default, el core_general.csv
 * @param string $codigo El código idioma_región, pe: es_MX
 * @return string
 */
function __($texto, $archivo = 'core_general', $codigo = NULL)
{
    $texto_str = (string) $texto;

    # validar si la cadena viene vacía
    if ('' == $texto_str) {
        return '';
    }

    # quitar espacios a la cadena
    $texto_ok = trim($texto);

    # inicializar locale por si no se encuentra la cadena a traducir
    $text_locale = $texto_ok;

    # validar código de idiomas
    if (!is_string($codigo)) {
        $codigo = config_item('locale_default');
    } else {
        $valid_code = array_search($codigo, config_item('locale_list'));

        # si el código locale no está en la lista de paquetes de idioma disponibles,
        # entonces, tomar el default.
        if (FALSE === $valid_code) {
            $codigo = config_item('locale_default');
        }
    }

    $archivo = PATH_RAIZ . URL_ROOT_FOLDER . APPPATH . DIR_LANG . $codigo . '/' . $archivo . '.csv';

    try {
        $handle = @fopen($archivo, 'rb');
    } catch (Exception $e) {
        return $text_locale;
    }
    
    if (FALSE !== $handle) {
        while (FALSE !== ($data = fgetcsv($handle, 1000, ','))) {
            if ($texto_ok == $data[0]) {
                $text_locale = $data[1];
                break;
            }
        }
        fclose($handle);
    }

    return $text_locale;
}

/*  EOF  */

Paso 6.

Ahora necesitamos crear unas constantes, así que editamos el archivo application/config/constants.php y declaramos las siguientes constantes:

define('PATH_RAIZ', $_SERVER['DOCUMENT_ROOT'] . '/');
define('URL_ROOT_FOLDER', ''); // si tu aplicación corre desde un(os) subdirectorio(s), ponerlo aquí por ejemplo: define('URL_ROOT_FOLDER', 'misubfolder/');
define('DIR_LANG', 'language/');

Paso 7.

Editamos el archivo application/config/autoload.php y agregamos el nuevo helper (translate) a la lista, en mi ejemplo se vería así:

$autoload['helper'] = array('url', 'form', 'directory', 'file', 'html', 'date', 'cookie', 'translate');

Paso 8.

En el mismo archivo autoload.php debemos colocar en una sección llamada "Auto-load config files", lo siguiente:

/*
| -------------------------------------------------------------------
|  Auto-load Config files
| -------------------------------------------------------------------
| Prototype:
|
| $autoload['config'] = array('config1', 'config2');
|
| NOTE: This item is intended for use ONLY if you have created custom
| config files.  Otherwise, leave it blank.
|
*/

$autoload['config'] = array('locale');

Paso 9.

Ahora debemos crear el archivo de configuración locale.php en la carpeta application/config/ y colocamos el siguiente contenido:

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

$config['locale_default'] = 'es_MX';

//$config['locale_list'] = array('en_US', 'en_GB', 'es_ES', 'es_AR', 'es_CL', 'es_MX', 'es_UY', 'es_VE', 'fr_FR', 'pt_BR');

# idiomas disponibles, en mi caso, solamente manejaré éstos dos:
$config['locale_list'] = array('en_US', 'es_MX');

/* EOF */

Paso 10.

Listo, ahora ya estamos preparados para usar nuestro archivo de traducciones en cualquier tipo de script: vista, controlador, modelo, helpers, librerías, etc., aquí un ejemplo para usarlo en una vista:

<ul>
    <li class="admin-username"><?php echo $username; ?></li>
    <li><a href="#"><?php echo __('Edit Profile'); ?></a></li>
    <li><a href="#"><?php echo __('View Profile'); ?></a><a href="#"><i class="icon-lock"></i><?php echo __('Logout'); ?

></a></li>
    <li class="active"><a href="#messages" class="icon-inbox" title="<?php echo __('Messages'); ?>"></a></li>
    <li><a href="#schedule" class="icon-calendar" title="<?php echo __('Schedule'); ?>"></a></li>
    <li><a href="#groups" class="icon-group" title="<?php echo __('Groups'); ?>"></a></li>
</ul>

11. Por último, las variantes para este helper, serían por ejemplo:

__('String to translate', 'sales'); // usar el archivo sales.csv
__('String to translate', 'sales', 'fr_FR'); // usar el archivo sales.csv y además forzar a usar el idioma francés de Francia.

Espero que le sea de utilidad.

Datos personales

Mi foto
Podrás encontrar códigos recursos y artículos sobre PHP, JavaScript, jQuery, MooTools, Ajax, CSS, HTML, UML, RUP, AUP, XP (eXtreme Programming), Six-Sigma, CMMI, FrameWorks, Zend Framework, Magento, CodeIgniter, CakePHP, Joomla 1.5, Doctrine, Active Record, ORM, POO, MVC, MySql, PostgreSql. Este espacio está destinado a ayudar y compartir un poco de lo mucho que he recibido de la comunidad en la Red.