Ajax, IE8 y caché

22 Marzo, 2010 por CAT Shannon Sin comentarios »

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.

Comparte y disfruta:
  • BarraPunto
  • Meneame
  • del.icio.us
  • email

Cakephp: paginación y SEO

24 Febrero, 2010 por CAT Shannon 1 comentario »

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'));
Comparte y disfruta:
  • BarraPunto
  • Meneame
  • del.icio.us
  • email

VideoRenamer

8 Diciembre, 2009 por CAT Shannon Sin comentarios »

Llevo un par de semanas jugando con python. Gracias al tutorial de python de Zootropo me ha resultado fácil y rápido aprender lo básico del lenguaje. Así que una vez que he aprendido cuatro cosas me he decidido a hacer una aplicación. He desarrollado un script para Nautilus que me permite renombrar los archivos de series que descargo e inmediatamente moverlos a la carpeta de vídeos correspondiente. Personalmente me gusta que las series estén organizadas en carpetas según una estructura similar a esta: House/Temporada 1/1×19 – Three stories.avi.

Así pues me he creado un script que me permite hacer todo esto en un momento. Se terminó limpiar los nombres de los archivos a mano.He creado una página con información en VideoRenamer.

De momento sólo lo he probado en Linux con escritorio Gnome. Cuando tenga un rato quizá pruebe a ver si se puede hacer funcionar en Windows. En principio el código de python debería valer, integrarla con el sistema será otra historia.

Esta es una captura de pantalla del programa en cuestión:

video-renamer-01

Comparte y disfruta:
  • BarraPunto
  • Meneame
  • del.icio.us
  • email

Manifiesto: En defensa de los derechos fundamentales en Internet

2 Diciembre, 2009 por CAT Shannon Sin comentarios »

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.

Comparte y disfruta:
  • BarraPunto
  • Meneame
  • del.icio.us
  • email

LAN lenta

17 Noviembre, 2009 por CAT Shannon Sin comentarios »

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.

Comparte y disfruta:
  • BarraPunto
  • Meneame
  • del.icio.us
  • email

Fotos nocturnas

19 Octubre, 2009 por CAT Shannon Sin comentarios »

Ayer estuve jugando con mi cámara de fotos: una Canon PowerShot A510. Hice unas cuantas fotografías nocturnas desde la ventana. No son buenas fotos. Algunas están subexpuestas, otras con sobreexposición; la mayoría mal encuadradas y alguna hasta sale desenfocada. Como he dicho, sólo estuve jugando. Hacía tiempo que no lo hacía.

Cuando me compré la cámara elegí este modelo porque, dentro de mi presupuesto, quería una compacta que tuviese algunas de las posibilidades que da una réflex.

He aprovechado y me he creado una cuenta en Flickr para colgar las fotos.

Bilbao - Puente San Antón

Bilbao - Puente San Antón

Comparte y disfruta:
  • BarraPunto
  • Meneame
  • del.icio.us
  • email

Retomando el blog

1 Septiembre, 2009 por CAT Shannon Sin comentarios »

Llevo unos meses de inactividad y tras las vacaciones veraniegas me he propuesto retomar el blog. Veremos lo que dura este ímpetu.

De momento le he cambiado el theme. Llevaba tiempo queriendo uno más ligero y sobre todo sencillo. He encontrado el Cordobo Green Park 2 que es un theme muy sencillo y además en tonos verdes: perfecto.

Comparte y disfruta:
  • BarraPunto
  • Meneame
  • del.icio.us
  • email

Discos duros y canon

28 Abril, 2009 por CAT Shannon Sin comentarios »

Hace tiempo que quería aumentar el disco duro de mi ordenador. Compré el PC en 2003 y tiene unos miserables 80gb (74 reales) que en su día parecía que no se iban a llenar nunca. El problema es que la placa es vieja y no soporta discos ATA. Tampoco me gustaba la idea de comprar un disco duro ATA y que dentro de un par de años lo quiera usar para otro PC y no me valga o no le saque todo el rendimiento.

Al final he comprado una controladora SATA/IDE PCI por unos 20€  y disco duro SATA de 500gb por unos 60€. Un disco IDE de la misma capacidad en la misma tienda me salía por unos 100€, así que algo he ahorrado.

El caso es que desde que la SGAE metió canon a todo lo que se mueve, si compras el disco duro como esclavo (donde no vas a instalar el Sistema Operativo) te clavan 12€+IVA más. Sólo porque ese disco duro presumiblemente es para guardar obras de sus representados. Total, que en la tienda, me han hecho firmar una papelito en el que aseguraba que el disco duro lo voy a montar como maestro. Mentira cochina. El empleado ya lo sabía, y más después de haber pedido la controladora. Ha habido un segundo de “sé que sabes que lo sé”. Divertido.

Me pregunto si a los discos duros de los HTPC les metarán canon también. Está claro que son maestros, pero son para reproducir auido y video… Siempre se puede decir que es para otra cosa.

Comparte y disfruta:
  • BarraPunto
  • Meneame
  • del.icio.us
  • email

Autocompletado de código CakePHP en Zend Studio

7 Marzo, 2009 por CAT Shannon Sin comentarios »

Para programar PHP me gusta usar Zend Studio. He probado diferentes alternativas y esta es la que más me ha convencido. Pero tiene una pega, y es que cuando uso cakePHP como framework no consigo todo el autocompletado de código que quisiera. Hasta ahora.

Cuando usamos un Model o un Component en un Controller, como se cargan dinámicamente, Zend Studio no reconoce sus métodos. La solución que he encontrado ha sido crear un atributo por cada modelo en la clase AppController y mediante el uso de documentación phpDoc indicar el tipo del atributo. Por ejemplo, supongamos que tengo un modelo llamado Post:

/**
 * @var AppModel
 */
public $Post;

Zend Studio interpreta que $Post es de tipo AppModel por lo que ya disponemos de los métodos de AppModel que Post hereda. Tiene una pega, y es que si creamos métodos propios en nuestro modelo esto no nos servirá.
Para tener autocompletado  de código en componentes es prácticamente igual:

/**
 * @var Component
 */
public $RequestHandler;

¿Y qué hay de los Helpers? En el archivo /app/config/bootstrap.php incluimos el siguiente código:

if(false) {
	$ajax = new AjaxHelper();
	$form = new FormHelper();
	$html = new HtmlHelper();
	$javascript = new JavascriptHelper();
	$number = new NumberHelper();
	$session = new SessionHelper();
	$text = new TextHelper();
	$time = new TimeHelper();
	$pagination = new PaginationHelper();
	$rss = new RssHelper();
	$xml = new XmlHelper();
	$number = new NumberHelper();
}

Como se ve en el código, por cada Helper creamos un nuevo objeto. El truco está en que esto está dentro de un if en el que nunca se entrará por lo que no afecta a nuestro código, sin embargo Zend Studio lo interpreta.
Podríamos incluirlo en cualquier archivo, pero cakePHP reserva específicamente el archivo bootstrap.php para que metamos ahí lo que queramos.

No lo he probado, pero supongo que este proceso sirve igualmente para Eclipse PDT.

Comparte y disfruta:
  • BarraPunto
  • Meneame
  • del.icio.us
  • email

Backup de MySQL y envío por correo

6 Marzo, 2009 por CAT Shannon Sin comentarios »

Para realizar los backups de las bases de datos del servidor donde tengo alojadas varias webs utilizo un script bash: AutoMySQLBackup. El script utiliza la orden mysqldump de MySQL para realizar copias diarias, semanales y mensuales. Podéis ver informácion más completa en éste artículo.

Lo tengo configurado para que me envíe por correo una copia de los backups, pero últimamente no me llegaba. Pensaba yo que era un problema de AutoMySQLBackup, pero resulta que el problema era de postfix.

Por defecto postfix viene configurado para que el tamaño de los adjuntos sea de 10mb máximo. Lo he cambiado a 20mb y problema solucionado. ¿Dónde se cambia? En /etc/postfix/main.cf añades al final la linea siguiente:

message_size_limit = 20971520

El tamaño se indica en bytes y puedes cambiarlo por el que consideres necesario. Ten en cuenta que muchas cuentas de correo no permiten adjuntos de gran tamaño.

Comparte y disfruta:
  • BarraPunto
  • Meneame
  • del.icio.us
  • email