Archivado en: ‘Internet’ .

Ajax, IE8 y caché

22 Marzo, 2010

El otro día me surgió un problema con IE8. Estaba usando el paginador para cakephp del que hablé en el artículo anterior pero en IE8 no funcionaba correctamente. De vez en cuando, en lugar de cargar sólo el nuevo contenido en la capa de los contenidos paginados, cargaba toda la página entera en esa capa, con su cabecera y demás.
Lo estuve mirando un rato pero sin demasiadas ganas. Hoy he vuelto a mirarlo más a fondo y he descubierto que el problema es la caché de IE8. En mi paginador, el enlace a la página 1 no tiene ningún parámetro de página, así que esta página ya se había cargado en caché la primera vez que accedemos. Para IE8 eso incluye todo el HTML completo.
La solución la he encontrado en stackoverflow, como muchas veces. Sólo vale para jQuery pero supongo que otros frameworks tendrán algo parecido.
En nuestro javascript añadimos esto:

$.ajaxSetup({
    cache: false
});

Muy simple.

Cakephp: paginación y SEO

24 Febrero, 2010

En un proyecto nuevo que estoy desarrollando me he dado cuenta que hay un problema con la paginación en CakePHP y la posibilidad de generar contenido duplicado de cara a los buscadores.

Cuando paginamos unos resultados en la URL se añade el parámetro page que nos indica el número de página de resultados que veremos. El problema surge cuando hay un enlace a la página 1 de los resultados cuyo contenido es el mismo que cuando no indicamos ese parámetro.
Veamoslo con un ejemplo. Supongamos que mi web es www.example.com y que en la página de inicio tenemos un listado de noticias, además, usando el potencial de Router de CakePHP consigo URLs del tipo www.example.com/p-2 para mostrar la segunda página de noticias. Cuando estemos en la página número dos, el paginador mostrará un enlace a la página número 1 de la forma www.example.com/p-1 cuyo contenido es exactamente el mismo que www.example.com. Resultado: los buscadores penalizan por considerar contenido duplicado.

Solución. He creado un Helper que extiende de PaginatorHelper y que modifica este comportamiento. El nuevo Helper admite un valor en el parámetro options que permite hacer que en el pagindor la primera o la última página no lleven el parámetro page en la URL. El nuevo valor de options se llama no_page y admite como valores first y last. Con first indicamos que los enlaces del paginador a la primera página no llevarán el parámetro page. Con last hacemos lo mismo pero para la última página del paginador.
Usar la opción no_page = “last” tiene sentido si desde nuestro Controller hacemos que la página que se muestre por defecto en nuestra paginación sea la última de los resultados. Es decir, si tenemos 3 páginas de comentarios, que nos muestre por defecto la página 3, donde se verán los últimos comentarios añadidos.

Código. Este es el código del Helper que he creado. Hay que incluirlo en el archivo /app/views/helpers/pager.php

<?php
App::import('Helper', 'Paginator');
class PagerHelper extends PaginatorHelper
{
 
 
    public function numbers($options = array())
    {
        if($options === true)
        {
            $options = array('before' => ' | ', 'after' => ' | ', 'first' => 'first', 'last' => 'last');
        }
 
        $defaults = array('tag' => 'span', 'before' => null, 'after' => null, 'model' => $this->defaultModel(), 'modulus' => '8', 'separator' => ' | ', 'first' => null, 'last' => null, 'no_page' => 'first');
        $options += $defaults;
 
        $params = (array) $this->params($options['model']) + array('page' => 1);
        unset($options['model']);
 
        if($params['pageCount'] <= 1)
        {
            return false;
        }
 
        extract($options);
        unset($options['tag'], $options['before'], $options['after'], $options['model'], $options['modulus'], $options['separator'], $options['first'], $options['last'], $options['no_page']);
        $out = '';
 
        if($modulus && $params['pageCount'] > $modulus)
        {
            $half = intval($modulus / 2);
            $end = $params['page'] + $half;
 
            if($end > $params['pageCount'])
            {
                $end = $params['pageCount'];
            }
            $start = $params['page'] - ($modulus - ($end - $params['page']));
            if($start <= 1)
            {
                $start = 1;
                $end = $params['page'] + ($modulus - $params['page']) + 1;
            }
 
            if($first && $start > 1)
            {
                $offset = ($start <= (int) $first) ? $start - 1 : $first;
                if($offset < $start - 1)
                {
                    $out .= $this->first($offset, array('tag' => $tag, 'separator' => $separator));
                }
                else
                {
                    $out .= $this->first($offset, array('tag' => $tag, 'after' => $separator, 'separator' => $separator));
                }
            }
 
            $out .= $before;
 
            for($i = $start; $i < $params['page']; $i++)
            {
                if(($no_page == 'first' and $i == 1)) $out .= $this->Html->tag($tag, $this->link($i, array('page' => null), $options)) . $separator;
                else $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options)) . $separator;
            }
 
            $out .= $this->Html->tag($tag, $params['page'], array('class' => 'current'));
            if($i != $params['pageCount'])
            {
                $out .= $separator;
            }
 
            $start = $params['page'] + 1;
            for($i = $start; $i < $end; $i++)
            {
                $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options)) . $separator;
            }
 
            if($end != $params['page'])
            {
                if($no_page == 'last') $out .= $this->Html->tag($tag, $this->link($i, array('page' => null), $options)) . $separator;
                else $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options)) . $separator;
            }
 
            $out .= $after;
 
            if($last && $end < $params['pageCount'])
            {
                $offset = ($params['pageCount'] < $end + (int) $last) ? $params['pageCount'] - $end : $last;
                if($offset <= $last && $params['pageCount'] - $end > $offset)
                {
                    $out .= $this->last($offset, array('tag' => $tag, 'separator' => $separator, 'no_page' => $no_page));
                }
                else
                {
                    $out .= $this->last($offset, array('tag' => $tag, 'before' => $separator, 'separator' => $separator, 'no_page' => $no_page));
                }
            }
 
        }
        else
        {
            $out .= $before;
 
            for($i = 1; $i <= $params['pageCount']; $i++)
            {
                if($i == $params['page'])
                {
                    $out .= $this->Html->tag($tag, $i, array('class' => 'current'));
                }
                else
                {
                    if(($no_page == 'first' and $i == 1) or ($no_page == 'last' and $i == $params['pageCount']))
                    {
 
                        $out .= $this->Html->tag($tag, $this->link($i, array('page' => null), $options));
                    }
                    else
                    {
                        $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options));
                    }
                }
                if($i != $params['pageCount'])
                {
                    $out .= $separator;
                }
            }
 
            $out .= $after;
        }
 
        return $out;
    }
 
 
    public function __pagingLink($which, $title = null, $options = array(), $disabledTitle = null, $disabledOptions = array())
    {
        $check = 'has' . $which;
        $_defaults = array('url' => array(), 'step' => 1, 'escape' => true, 'model' => null, 'tag' => 'span', 'class' => strtolower($which), 'no_page' => 'first');
        $options = array_merge($_defaults, (array) $options);
        $paging = $this->params($options['model']);
        if(empty($disabledOptions))
        {
            $disabledOptions = $options;
        }
 
        if(!$this->{$check}($options['model']) && (!empty($disabledTitle) || !empty($disabledOptions)))
        {
            if(!empty($disabledTitle) && $disabledTitle !== true)
            {
                $title = $disabledTitle;
            }
            $options = array_merge($_defaults, (array) $disabledOptions);
        }
        elseif(!$this->{$check}($options['model']))
        {
            return null;
        }
 
        foreach(array_keys($_defaults) as $key)
        {
            ${$key} = $options[$key];
            unset($options[$key]);
        }
        $url = array_merge(array('page' => $paging['page'] + ($which == 'Prev' ? $step * -1 : $step)), $url);
 
        if(($no_page == 'first' and $url['page'] == 1) or ($no_page == 'last' and $url['page'] == $paging['pageCount'])) $url['page'] = null;
 
        if($this->{$check}($model))
        {
            return $this->link($title, $url, array_merge($options, compact('escape', 'class')));
        }
        else
        {
            return $this->Html->tag($tag, $title, array_merge($options, compact('escape', 'class')));
        }
    }
 
 
    public function first($first = '<< first', $options = array())
    {
        $options = array_merge(array('tag' => 'span', 'after' => null, 'model' => $this->defaultModel(), 'separator' => ' | ', 'no_page' => 'first'), (array) $options);
 
        $params = array_merge(array('page' => 1), (array) $this->params($options['model']));
        unset($options['model']);
 
        if($params['pageCount'] <= 1)
        {
            return false;
        }
        extract($options);
        unset($options['tag'], $options['after'], $options['model'], $options['separator'], $options['no_page']);
 
        $out = '';
 
        if(is_int($first) && $params['page'] > $first)
        {
            if($after === null)
            {
                $after = '...';
            }
            for($i = 1; $i <= $first; $i++)
            {
                if($no_page == 'first' and $i == 1) $out .= $this->Html->tag($tag, $this->link($i, array('page' => null), $options));
                else $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options));
 
                if($i != $first)
                {
                    $out .= $separator;
                }
            }
            $out .= $after;
        }
        elseif($params['page'] > 1)
        {
            $out = $this->Html->tag($tag, $this->link($first, array('page' => 1), $options)) . $after;
        }
        return $out;
    }
 
 
    public function last($last = 'last >>', $options = array())
    {
 
        $options = array_merge(array('tag' => 'span', 'before' => null, 'model' => $this->defaultModel(), 'separator' => ' | ', 'no_page' => 'first'), (array) $options);
 
        $params = array_merge(array('page' => 1), (array) $this->params($options['model']));
        unset($options['model']);
 
        if($params['pageCount'] <= 1)
        {
            return false;
        }
 
        extract($options);
        unset($options['tag'], $options['before'], $options['model'], $options['separator'], $options['no_page']);
 
        $out = '';
        $lower = $params['pageCount'] - $last + 1;
 
        if(is_int($last) && $params['page'] < $lower)
        {
            if($before === null)
            {
                $before = '...';
            }
            for($i = $lower; $i <= $params['pageCount']; $i++)
            {
                if($no_page == 'last' and $i == $lower) $out .= $this->Html->tag($tag, $this->link($i, array('page' => null), $options));
                else $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options));
 
                if($i != $params['pageCount'])
                {
                    $out .= $separator;
                }
            }
            $out = $before . $out;
        }
        elseif($params['page'] < $params['pageCount'])
        {
            $out = $before . $this->Html->tag($tag, $this->link($last, array('page' => $params['pageCount']), $options));
        }
        return $out;
    }
}
?>

Este es un ejemplo de cómo se usaría el nuevo Helper.

echo $this->Pager->prev('« Anterior', array('class' => 'nextprev','no_page' => 'first'), '« Anterior', array('class' => 'nextprev', 'tag' => 'span'));
echo $this->Pager->numbers(array('separator' => '', 'modulus' => 5, 'no_page' => 'first', 'first' => 1, 'last' => 1));
echo $this->Pager->next('Siguiente »', array('class' => 'nextprev','no_page' => 'first'), 'Siguiente »', array('class' => 'nextprev', 'tag' => 'span'));

Manifiesto: En defensa de los derechos fundamentales en Internet

2 Diciembre, 2009

Ante la inclusión en el Anteproyecto de Ley de Economía sostenible de modificaciones legislativas que afectan al libre ejercicio de las libertades de expresión, información y el derecho de acceso a la cultura a través de Internet, los periodistas, bloggers, usuarios, profesionales y creadores de internet manifestamos nuestra firme oposición al proyecto, y declaramos que…

1.- Los derechos de autor no pueden situarse por encima de los derechos fundamentales de los ciudadanos, como el derecho a la privacidad, a la seguridad, a la presunción de inocencia, a la tutela judicial efectiva y a la libertad de expresión.

2.- La suspensión de derechos fundamentales es y debe seguir siendo competencia exclusiva del poder judicial. Ni un cierre sin sentencia. Este anteproyecto, en contra de lo establecido en el artículo 20.5 de la Constitución, pone en manos de un órgano no judicial -un organismo dependiente del ministerio de Cultura-, la potestad de impedir a los ciudadanos españoles el acceso a cualquier página web.

3.- La nueva legislación creará inseguridad jurídica en todo el sector tecnológico español, perjudicando uno de los pocos campos de desarrollo y futuro de nuestra economía, entorpeciendo la creación de empresas, introduciendo trabas a la libre competencia y ralentizando su proyección internacional.

4.- La nueva legislación propuesta amenaza a los nuevos creadores y entorpece la creación cultural. Con Internet y los sucesivos avances tecnológicos se ha democratizado extraordinariamente la creación y emisión de contenidos de todo tipo, que ya no provienen prevalentemente de las industrias culturales tradicionales, sino de multitud de fuentes diferentes.

5.- Los autores, como todos los trabajadores, tienen derecho a vivir de su trabajo con nuevas ideas creativas, modelos de negocio y actividades asociadas a sus creaciones. Intentar sostener con cambios legislativos a una industria obsoleta que no sabe adaptarse a este nuevo entorno no es ni justo ni realista. Si su modelo de negocio se basaba en el control de las copias de las obras y en Internet no es posible sin vulnerar derechos fundamentales, deberían buscar otro modelo.

6.- Consideramos que las industrias culturales necesitan para sobrevivir alternativas modernas, eficaces, creíbles y asequibles y que se adecuen a los nuevos usos sociales, en lugar de limitaciones tan desproporcionadas como ineficaces para el fin que dicen perseguir.

7.- Internet debe funcionar de forma libre y sin interferencias políticas auspiciadas por sectores que pretenden perpetuar obsoletos modelos de negocio e imposibilitar que el saber humano siga siendo libre.

8.- Exigimos que el Gobierno garantice por ley la neutralidad de la Red en España, ante cualquier presión que pueda producirse, como marco para el desarrollo de una economía sostenible y realista de cara al futuro.

9.- Proponemos una verdadera reforma del derecho de propiedad intelectual orientada a su fin: devolver a la sociedad el conocimiento, promover el dominio público y limitar los abusos de las entidades gestoras.

10.- En democracia las leyes y sus modificaciones deben aprobarse tras el oportuno debate público y habiendo consultado previamente a todas las partes implicadas. No es de recibo que se realicen cambios legislativos que afectan a derechos fundamentales en una ley no orgánica y que versa sobre otra materia.

Este manifiesto, elaborado de forma conjunta por varios autores, es de todos y de ninguno. Si quieres sumarte a él, difúndelo por Internet.

LAN lenta

17 Noviembre, 2009

Llevo unos días con la red local de casa bastante lenta. Pasando archivos a sólo 3mB/s. La red la componen un PC, un HTPC y un portatil. Los dos primeros con Ubuntu 9.10 y el portatil con 9.04. Se conectan a un router CT-536+ y la distancia del cable no es demasiada. Hace un mes funcionaba correctamente pero he cambiado el PC por uno nuevo y había actualizado la versión de Ubuntu así que no estaba seguro de cual podría ser el problema. Hasta que he preguntado en este hilo de BandaAncha. La solución era fácil: tenía las tarjetas de red en 100mbps Half Duplex. Gracias a este tutorial he cambiado a Full Duplex y ahora todo va como debe.

Incluyo aquí los pasos que yo he seguido por si algún día deja de funcionar el enlace.

Suponemos que nuestra tarjeta es eth0. Para ver cómo la tenemos configurada actualmente tecleamos en la terminal lo siguiente:

ethtool eth0

Nos mostrará todos los detalles. A continuación, si queremos configurar a 100 mbps Full Duplex y que no negocie la condición, introducimos la siguiente línea:

ethtool --change eth0 speed 100 autoneg off duplex full

Si deseamos que este cambio se conserve para la próxima vez que iniciemos el ordenador deberemos modificar el archivo /etc/network/interfaces. Yo tengo configurados los ordenadores con ip estática así que introduzco el siguiente código:

auto eth0
iface eth0 inet static
post-up /usr/sbin/ethtool -s eth0 speed 100 duplex full autoneg off
address 192.168.1.4
netmask 255.255.255.0
gateway 192.168.1.1

La línea de address indica la IP local que tomará el equipo. La configuración con IP estática la había podido hacer ya desde “Conexiones de red” pero la he incluido en /etc/network/interfaces porque sino no me cambiaba a Full Duplex.

Dos extensiones para Firefox

26 Enero, 2009

Hoy me han mandado un par de extensiones para Firefox que no conocía:

  • BabelFish Instant Translation. Es un traductor de textos con múltiples idiomas incluidos. Lo bueno es que puedes configurarlo para que al hacer doble click sobre una palabra te la traduzca. También traduce párrafos completos y además permite elegir entre el traductor de Google y el de Yahoo.
  • WebMail Notifier. Comprueba las cuentas de correo webmail y te avisa de los nuevos correos. Vale para Gmail, Yahoo, Hotmail y otros.

Quedan añadidas a mi lista de extensiones de Firefox.