Archivo de la categoría: programación

Impedir que se apage la pantalla al ver un vídeo flash a pantalla completa en Ubuntu

[linux] [programación]

Hace unas semanas abandoné Windows XP definitivamente para pasarme a Linux Ubuntu y la verdad es que estoy encantado. Muchos de los inconvenientes que recuerdo de veces anteriores en las que he intentado usar Linux han desaparecido y el ordenador ha encontrado una segunda juventud, funcionando más rápido de lo que ha ido nunca.

Pero como no, algunas cosillas siguen siendo muy de Linux, como por ejemplo que cuando estoy viendo un vídeo flash a pantalla completa, Ubuntu considere que no estás haciendo nada e intente apagar la pantalla. Esto supongo que es problema de flash, porque con el VLC no pasa.

En esta página explican como solucionar el problema, desactivando y activando el economizador de energía cuando se detecta un alto uso del procesador, pero esta solución no me estaba funcionado, así que opté por la solución más sencilla, mover el ratón un píxel cada 5 minutos. Así que cuando voy a ver un vídeo largo, lanzo el script y evito que se apague la pantalla.

Aquí tenéis el código, que cada 5 minutos mueve el ratón un píxel a la derecha, y después un píxel a la izquierda.

#!/usr/bin/env bash

sleep_period=5m

while true; do
  xdotool mousemove_relative 1 0
  xdotool mousemove_relative -- -1 0

  sleep ${sleep_period}
done

El programa xdotool no viene por defecto con Ubuntu,  pero lo podéis encontrar en el “Centro de software de Ubuntu”. También recordad cambiar los permisos de ejecución con chmod +x

Todo lo que sabía de CSS era mentira!

[programación]

Bueno, igual todo no, pero me he estado empapando de documentación y vídeos de conferencias, y me he dado cuenta que estoy desactualizado.

Estas son algunas de las nuevas tecnologías y metodologías que ya estoy utilizando en el desarrollo de aplicaciones web:

Y aquí algo de la documentación que me ha motivado a modernizarme:

Object Oriented CSS

Sass

Desencriptar código PHP

[programación]

Últimamente me ha tocado lidiar con código PHP que se encuentra encriptado y ofuscado. Se trata de un módulo para PrestaShop de la empresa Agile. En nuestro caso nos daba una funcionalidad muy próxima a lo deseado, pero menuda sorpresa al intentar corregir algunos elementos y descubrir que el código estaba encriptado y ofuscado.

En este caso el tipo de encriptación utilizado era hexadecimal. Tras buscar un rato, encontré esta página que realiza una desencriptación del código:

DDecode

Funciona bastante bien, pero en un par de casos parece que se queda a mitad fichero y no acaba de realizar la desencriptación. Buscando un poco más encontré en Stackoverflow este tema donde explican como desencriptar el hexadecimal utilizando JavaScript. Lo he probado y también funciona bastante bien, al menos este termina de desencriptar el fichero. Así que mi solución ha sido utilizar ambos métodos y comparar el código resultante para reconstruir el fichero original.

Por si a alguien re puede resultar este segundo método, he creado una sencilla página donde puede insertar el código para ser desencriptado. La encontraréis en:

Just Another Hex Decoder!

Importar un fichero CSV grande a MySQL

[programación]

El objetivo es importar los ficheros GeoLite que ofrece MaxMind para poder geolocalizar usuarios. Son dos ficheros, GeoLiteCity-location.csv de 21MB y GeoLiteCity-Blocks.csv de 64MB. PhpMyAdmin ofrece la posibilidad de importar ficheros CSV, pero claro, tienen que tener un tamaño razonable. Con estos no puede, y aunque consiguiéramos que funcionara, la importación sería tremendamente lenta.

Por eso la mejor opción es usar la línea de comandos de MySQL. En mi caso uso WAMP, así que para acceder a la línea de comandos hacemos lo siguiente:

  1. Inicio->Ejecutar->cmd
  2. Vamos a c:\wamp\bin\mysql\mysql5.5.24\bin. Esta ruta puede cambiar dependiendo la versión de MySQL que esté instalada.
  3. Ejecutamos: mysql.exe -u root

Ahora que estamos en la consola de MySQL haremos la importación del fichero CSV. Supongamos que el fichero está en c:\fichero.csv y lo queremos meter en la tabla locations de la base de datos geolocalizacion. Primero accedemos a la base de datos:

USE geolocalizacion

Después importamos:

LOAD DATA LOCAL INFILE 'c:/fichero.csv'
INTO TABLE location
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\n';

En este caso los campos del fichero CSV están en el mismo orden que en la tabla. Si el orden fuese diferente, añadiríamos a la importación la opción:

(campo1, campo2, campo3, ...);

Ver los últimos libros abiertos en la página principal del Sony Reader PRS-T1

[gadgets] [programación]

Por defecto, en la página principal del Sony Reader PRS-T1 aparece en grande el último libro abierto y los 3 últimos libros añadidos al dispositivo. Esto es increíblemente poco útil, teniendo en cuenta que lo más probable es que mucho de los libros que se meten en el dispositivo no los vas a leer inmediatamente. También es muy poco útil si como yo siempre estás alternando entre dos libros (en mi caso entre un libro y un manga). Como lo más probable es que los insertara hace tiempo, pues cada vez que quiero cambiar, tendría que irme a la página con todos los libros y buscarlos. Un rollo vamos.

Lo peor de todo es que esto es algo que los usuario llevan pidiendo mucho tiempo, y en ninguna actualización del firmware se les ha ocurrido añadirlo como una opción.

Por suerte, existe una forma de lograr esta funcionalidad mediante una pequeña modificación en la base de datos interna del dispositivo. Esta base de datos es accesible desde el explorador de ficheros cuando el lector está conectado al ordenador, y no hay que tener miedo a hacer algo mal, porque siempre podemos borrar el fichero de la base de datos y el dispositivo creará un nuevo automáticamente.

La solución original se encuentra en este foro, pero no acaba de estar bien detallada en el mensaje inicial. Los pasos a realizar serían estos:

  1. Descargar y descomprimir el programa SQLiteSpy.

  2. Conectar el Sony Reader PRS-T1 al ordenador y copiar el fichero [unidad:]\Sony_Reader\database\books.db a una carpeta local. Es conveniente hacer una copia adicional de este fichero por si lo queremos recuperar.

  3. Lanzamos SQLiteSpy y abrimos books.db mediante File -> Open Database.

  4. Copiamos este código en la consola:

    CREATE TRIGGER recently_opened_trigger AFTER UPDATE OF reading_time ON books
    BEGIN
       UPDATE books SET added_date = 0 WHERE _id = new._id;
       UPDATE books SET added_date = reading_time WHERE reading_time NOT NULL AND _id <> new._id;
    END
    

    Codigo en la consola del SQLiteSpy para books.db

  5. Ejecutamos el código con Execute -> Execute SQL.

  6. Comprobamos que el trigger se ha añadido correctamente expandiendo en la barra de la izquierda books y a continuación Triggers, donde veremos uno llamado recently_opened_trigger.

  7. Salimos del SQLiteSpy. Cuando pregunte si queremos guardar los cambios, decimos que no, pues la base de datos ya está actualizada.

  8. Copiamos nuestro books.db al Sony Reader PRS-T1, sobreescribiendo el original.

  9. Desconectamos el dispositivo del ordenador.

Existe un problema con este método y consiste en que es incompatible con usar dos dispositivos de almacenamiento en el Sony Reader PRS-T1. Por ejemplo, en mi caso yo tengo el manga en la memoria SD mientras que los libros los tengo en la memoria interna. El problema consiste en que cuando se lee un libro, solo se lanzará el trigger de la base de datos donde está el libro, por lo que la otra base de datos se queda sin actualizar, causando un comportamiento errático de la página principal.

La solución consiste en utilizar un trigger distinto que mantenga un comportamiento consistente en la página principal. La contrapartida es que el libro que se está leyendo actualmente aparecerá dos veces, una en grande como libro actual, y otra en pequeño dentro del listado de los últimos leídos. Es un pequeño inconveniente pero no hay manera de evitarlo. La única solución sería usar únicamente uno de los dispositivos de almacenamiento.

El código es el siguiente:

CREATE TRIGGER recently_opened_trigger AFTER UPDATE OF reading_time ON books
BEGIN
   UPDATE books SET added_date = new.reading_time WHERE new.reading_time NOT NULL AND _id = old._id;
END

El procedimiento es el mismo explicado anteriormente, pero hay que acordarse de repetir el proceso con la base de datos de la tarjeta SD.

También es interesante decir que este método también funciona en el Sony Reader PRS-T2.

phpThumb no funciona con enlaces simbólicos

[linux] [programación]

Al menos eso era aparentemente mi problema, y mi punto de partida en búsqueda de una solución.

Básicamente el problema se podía resumir de la siguiente manera:

La imagen se ve correctamente:

http://www.midominio.com/imagenes/test.jpg

La imagen se ve correctamente y redimensionada:

http://www.midominio.com/phpThumb.php?url=imagenes/test.jpg&h=150

La imagen se ve correctamente. La carpeta “colecciones” es un enlace simbólico:

http://www.midominio.com/colecciones/imagenes/test.jpg

La imagen no se ve y phpThumb muestra un mensaje sobre el correcto uso del plugin:

http://www.midominio.com/phpThumb.php?url=colecciones/imagenes/test.jpg&h=150

Da la impresión que el problema aquí es ese enlace simbólico, pero no, este comportamiento se debe al siguiente valor de configuración de phpThumb:

$PHPTHUMB_CONFIG['allow_src_above_docroot'] = false;

En mi caso el enlace simbólico estaba apuntando a un directorio que estaba a un nivel superior que el contenido de la web, lo cual unido a este parámetro estaba bloqueando su acceso. La solución era tan simple como ponerlo a ’true'.

La solución la encontré en una de las últimas respuestas de este hilo.

Tutorial de Flexi auth #1 – Primeros pasos

[programación]

Gracias a un amigo, he descubierto CodeIgniter. Es un framework de PHP para desarrollar aplicaciones rápidamente y la verdad es que es una pasada. Según va cogiendo fuerza, van saliendo extensiones para CodeIgniter, y una de ellas es Flexi auth, el cual añade toda la funcionalidad para autenticación y permisos de usuario. El problema es que a diferencia de CodeIgniter, que tiene muy buena documentación paso a paso de como hacer las cosas, la documentación de Flexi auth es muy pobre. Tiene un buen tutorial de instalación y después te dicen “Mira, ahí tienes un ejemplo de las principales funcionalidades. Mírate el código”. La demo está muy completa, y ese es su fallo, tiene demasiada información para alguien que lo único que quiere es empezar poco a poco.

Así pues, voy a poner aquí lo imprescindible para tener la funcionalidad más básica del Flexi auth, que es hacer Login y Logout.

En mi sencillo ejemplo uso un único controlador, que es pages.php, siguiendo el ejemplo del tutorial de CodeIgniter. Solo voy a ir poniendo código que no apareciera en dicho tutorial.

El constructor de la clase Pages sería el siguiente:

public function __construct() {
    parent::__construct();
    $this->auth = new stdClass;
    $this->load->library('flexi_auth');
    $this->load->helper('url');
}

La creación del objeto auth es imprescindible para el funcionamiento de la librería Flexi auth, y después cargamos la librería extendida que nos dará la funcionalidad para el Login/Logout. El ayudante para URLs es útil a la hora de componer direcciones en las vistas, así como para las redirecciones.

public function login() {
    $this->load->helper('form');

    $this->load->library('form_validation');

    // Set validation rules.
    $this->form_validation->set_rules('login_identity', 'Usuario', 'required');
    $this->form_validation->set_rules('login_password', 'Contrase&ntilde;a', 'required');

    // Run the validation.
    if ($this->form_validation->run()) {
        // Verify login data.
        $this->flexi_auth->login($this->input->post('login_identity'), $this->input->post('login_password'));

        // Save any public status or error messages
        // (Whilst suppressing any admin messages) to CI's flash session data.
        $this->session->set_flashdata('message', $this->flexi_auth->get_messages());

        // Reload page, if login was successful, sessions will
        // have been created that will then further redirect verified users.
        redirect('home');
    } else {
        // Set validation errors.
        $this->data['message'] = validation_errors('<p class="error_msg">', '</p>');

        return FALSE;
    }
}

Esto está íntegramente sacado del código de la demo del Flexi auth, pero eliminando todo lo que no vamos a necesitar. Al final, solo dejamos primero la validación del formulario de Login, y si todo está correcto, intentamos hacer Login. Si hay éxito hacemos una redirección a la página principal, y en caso contrario, podemos mostrar un mensaje al usuario.

public function logout() {
    // By setting the logout functions argument as 'TRUE', all browser sessions are logged out.
    $this->flexi_auth->logout(TRUE);

    // Set a message to the CI flashdata so that it is available after the page redirect.
    $this->session->set_flashdata('message', $this->flexi_auth->get_messages());

    redirect('home');
}

Más sencillo imposible. Creo que el código es bastante autoexplicativo. Vamos a por el formulario de Login. Este está en una vista llamada login.php:

<h2>Login</h2>
<?php echo validation_errors(); ?>
<?php echo form_open('login/login');?>

    <label for="identity">Usuario</label>
    <input type="input" id="identity" name="login_identity" /><br />
    <label for="password">Contrase&ntilde;a</label>
    <input type="password" id="password" name="login_password" /><br />
    <input type="submit" name="login_user" id="submit" value="Login" />

</form>

Dos campos de texto (nombre de usuario y contraseña) y un botón. Importante que los nombres sean los mismos que tenemos en el método de Login.

También he introducido un poco de código en la cabecera de las páginas para que se muestre la información del estado del usuario (si está logueado o no):

<p>
    <?php
    if ($this->flexi_auth->is_logged_in()) {
    ?>
        Usuario: <?php echo $this->flexi_auth->get_user_identity(); ?>
        &nbsp;|&nbsp;<a href="<?php echo base_url('logout'); ?>">Salir</a>
    <?php
    } else {
        echo 'Usuario no registrado';
    }
    ?>
</p>

Simplemente compruebo si el usuario está logueado, en cuyo caso muestro su identificación y un enlace de Logout.

Por último falta añadir un par de rutas a routes.php:

$route['login/login'] = 'pages/login';
$route['logout'] = 'pages/logout';

Y con eso ya debería funcionar. En el próximo post haré el proceso de registro.

SPAM Fight – Round 4!

[blog] [programación]

Cuando pensaba que había encontrado el método definitivo anti-spam, resultó no ser eficaz ni durante un día entero. Algunos de los comentarios se estaban marcando correctamente como SPAM, que deben ser los que atacan directamente a wp-comments-post.php. Sin embargo seguían llegando algunos comentarios de SPAM, y no tengo ni idea de como lo están haciendo. Debe haber otra manera de hacer un POST de un comentario, pero por el momento no la he encontrado.

Lo que sí que me he dado cuenta, es que estos comentarios nunca tienen establecido un correo electrónico (al menos de momento). Utilizando ese dato, he complementado el método de identificación de SPAM con el requerimiento de un correo. En wp-includes/comment.php:

if(strpos($comment_content, '[THIS#IS#SPAAAM!]') !== false
    || !is_email($comment_author_email)){
    $comment_approved = 'spam';
}

De momento con esto estoy cazando todo el SPAM. ¡Veremos si dura!

El retorno del SPAM

[blog] [programación]

La solución con la base de datos de SPAM debo reconocer que fue ingeniosa. Está muy en mi línea de “matar moscas a cañonazos”. El problema es que lo que se dice eficaz, no era. Digamos que el número de direcciones IP desde las que puede llegar SPAM, o el número de direcciones de correo o URLs que pueden usar en los comentarios están en un orden de magnitud tal que nunca voy a ser capaz de filtrar los comentarios basura.

Todo vuelve a la pregunta que me hice hace ya hace 9 meses: ¿Como estaban los bots de SPAM enviando comentarios saltándose el formulario de envío? Y por pura casualidad, encontré a que página están atacando. Probablemente están haciendo un POST directamente contra wp-comments-post.php.

Así que volvemos a la sencillez. ¿Como detectar un POST que llega a wp-comments-post.php sin pasar por comments.php? Fácil. Si los bots están lanzando un POST genérico para blogs en WordPress, añadamos un nuevo campo al formulario. Si en wp-comments-post.php detectamos que ese campo no está establecido, entonces se está saltando comments.php y debe ser SPAM. Está solución es tan eficaz que hace que mis dos soluciones anteriores queden obsoletas, así que para aligerar la página y reducir tiempo de procesado, elimino las modificaciones del código anteriores. El código definitivo quedaría de la siguiente manera:

Dentro de los ficheros del tema, en el fichero comments.php:

Añadimos un nuevo campo oculto al formulario:

<input type="hidden" id="comment_post_antirobot" name="comment_post_antirobot" value="0" />

Por si acaso el robot de SPAM está comprobando que campos tiene el formulario, cambiamos su valor por javascript:

<script type="text/javascript">
  document.getElementById('comment_post_antirobot').value = "1";
</script>

En wp-comments-post.php:

Obtenemos el valor del campo oculto, y en caso de no estar establecido o no ser igual a 1, marcamos el comentario como SPAM modificando el contenido:

$comment_antirobot    = ( isset($_POST['comment_post_antirobot']) ) ? trim($_POST['comment_post_antirobot']) : null;

if( empty($comment_antirobot) || $comment_antirobot == '0'){
    // It's SPAM
    $comment_content = '[THIS#IS#SPAAAM!] '.$comment_content;    
}

En wp-includes/comment.php, en el método wp_insert_comment:

Comprobamos si contiene la marca de SPAM en su contenido, y en ese caso lo etiquetamos como SPAM:

if(strpos($comment_content, '[THIS#IS#SPAAAM!]') !== false){
    $comment_approved = 'spam';
}

De momento, el SPAM que está llegando se está etiquetando todo automáticamente como tal. A ver cuanto dura la solución… aunque en esta tengo bastante confianza!

El SPAM contraataca!

[blog] [programación]

El método que implementé contra el SPAM, que comenté en esté articulo, ha estado funcionando bien, pero en los últimos 2 meses ha dejado de cazar todo el SPAM. Los últimos comentarios que están llegando sí que  traen el campo de email cumplimentado, por lo que ya no es efectivo. Dándole vueltas al asunto, se me ha ocurrido hacer mi propia base de datos anti-spam. La idea es que cada vez que llega un nuevo comentario, se comprueba con la base de datos si es SPAM, y en caso que lo sea, añadir sus datos para futuras comprobaciones. El código es el siguiente:

En wp_includes/comment.php en el método wp_insert_comment, justo antes de insertar el comentario hago esta comprobación:

if(!is_email($comment_author_email)){
    $comment_approved = 'spam';
}
else{
    $spam_query = "SELECT * FROM spam_data "
    ."WHERE data = '".$comment_author_IP
    ."' OR data = '".$comment_author_email
    ."' OR data = '".$comment_author_url."'";

    /*$spam_query = "SELECT * FROM spam_data "
    ."WHERE 1";*/

    $spam_results = $wpdb->get_results($spam_query);

    /*if(count($spam_results) <= 0){ // It's SPAM
        $comment_approved = 'spam';
    }*/

    if(count($spam_results) > 0){ // It's SPAM
        $comment_content .= " count: ".count($spam_results);
        $comment_approved = 'spam';

        insert_into_spam_data($comment_author_IP);
        insert_into_spam_data($comment_author_email);
        insert_into_spam_data($comment_author_url);
    }    
}

Y para añadir los datos a la BD he añadido este método:

function insert_into_spam_data($spam_data){
    global $wpdb;

    $spam_query = "SELECT * FROM spam_data "
    ."WHERE data = '".$spam_data."'";

    $spam_results = $wpdb->get_results($spam_query);

    if(count($spam_results) <= 0){
        $wpdb->insert(
            'spam_data',
            array(
                'data' => $spam_data
            ),
            array(
                '%s'
            )
        );
    }
}

Esto debería parar una buena parte del SPAM. El siguiente paso será que cuando yo marque manualmente un comentario como SPAM, sus datos también sean incluidos en la tabla con la información de SPAM. Para ello en el metodo wp_spam_comment, tras marcar el comentario como SPAM, añadimos este código:

insert_into_spam_data($comment->comment_author_IP);
insert_into_spam_data($comment->comment_author_email);
insert_into_spam_data($comment->comment_author_url);

Ahora faltará ver como de efectivas son las medidas. Me estoy planteando introducir algún otro tipo de comprobación anti-spam, viendo que el captcha no está funcionando. Pero eso para otro artículo más adelante 🙂