Use get_the_terms() instead of wp_get_object_terms()

I was recently debugging the front page of a WordPress site and found a lot of queries to the terms and term relationships database tables.

Digging a little deeper, I found that the culprit were a set of functions that were calling wp_get_object_terms() to get the terms from a set of looped posts… and then I thought… “wait a minute, doesn’t WordPress should be using the object cache for this?”

Well, it turns out that wp_get_object_terms() always queries the database.

If you’re looping over WP_Query results, you should prefer get_the_terms() instead. It’s pretty much the same for most use cases, but it uses the object cache, which by default gets populated with the terms for the posts matching your query — unless you specifically set update_post_term_cache as false when instantiating WP_Query.

The are several differences, though: wp_get_object_terms() can take arrays as the first and second argument, while get_the_terms() can only take the post ID (or object) as first argument (so you can’t get the terms for a bunch of posts on one function call) and a string for taxonomy (so you can’t get the terms for several taxonomies); and you can use a third argument on the former, which the latter doesn’t have.

You could still emulate some of this, and still benefit from using the object cache; for instance, let’s see how you would get the names of the my_custom_tax terms for the current post, ordered by use on a descending way.

// using wp_get_object_terms()
$popular_terms = wp_get_object_terms( $post->ID, 'my_custom_tax', array( 
    'orderby' => 'count',
    'order'   => 'DESC',
    'fields'  => 'names'
) );

// using get_the_terms()
$popular_terms = get_the_terms( $post->ID, 'my_custom_tax' );
// $popular_terms will be ordered alphabetically, so let's order by count
$popular_terms = usort( $popular_terms, function( $a, $b ){
    if ( $a->count < $b->count ) {
        return 1;
    }
    if ( $a->count > $b->count ) {
        return -1;
    }
    return 0;
} );  
// we only need slugs, so...
$popular_terms = wp_list_pluck( $popular_terms, 'name' );

Even if it’s somewhat troublesome, it’s probably worth the effort if you’re trying to maximize for performance.

Cómo agregar un nuevo panel a WordPress Debug Bar

Una de las ventajas del plugin WordPress Debug Bar es que puedes agregar nuevos paneles según tus propias necesidades; por ejemplo, para mostrar datos de respuestas desde APIs externas o conexiones con web services u otras funcionalidades que hayas implementado de forma particualr.

// el plugin utiliza el filtro 'debug_bar_panels'
// que puedes usar para agregar nuevos paneles
add_filter( 'debug_bar_panels', 'my_custom_panel_init' );

/**
 * Inicializar tu panel personalizado
 * @param array $panels Instancias de Paneles
 * @return array
 */
function my_custom_panel_init( $panels ) {
    // Debes extender la clase Debug_Bar_Panel que forma
    // parte del plugin
    class Debug_Bar_Custom_Panel extends Debug_Bar_Panel{
        public function init(){
            // Como mínimo, debes definir el título de tu panel
            // personalizado. Se usará como título de la pestaña
            $this->title( 'Panel Personalizado' );
        }
        public function render(){
            // Construye e imprime el contenido de la pestaña
        }
    }
    // Agrega tu pestaña a la barra de depuración
    $panels[] = new Debug_Bar_Custom_Panel();
    return $panels;
}

Por supuesto, este es un ejemplo simplificado, pero es el punto de partida para tu implementación. Con un par de líneas más ya puedes tener tu propio panel:

Agregar un panel de depuración personalizado a WordPress Debug Bar

Para un ejemplo más completo, puedes revisar el código de alguno de los plugins que extienden la funcionalidad de la barra de depuración, como el repositorio de Debug-Bar-Shortcodes.

Usar DISQUS en sitios multilenguaje

Recientemente rediseñamos el sitio de nuestra aplicación para la gestión de hoteles, y uno de los cambios más importantes fue poder ofrecer una mejor experiencia a los visitantes según su idioma. También nos interesaba tener una mejor interacción en los comentarios del blog, y para ello decidimos utilizar la plataforma de Disqus, que entre otras cosas, permite suscripciones por correo electrónico a los comentarios, identificación con cuentas de terceros, moderación por e-mail, etc.

Uno de los problemas que encontramos fue que Disqus tiene una configuración global para la cuenta, por lo que la opción que configuras en su panel aplica a todas las conversaciones en el sitio. Sin embargo, escudriñando el código del plugin para WordPress pude hallar un filtro que permite indicar explícitamente el idioma en que se debe cargar la sección de comentarios:

add_filter('disqus_language_filter', function( $lang ){
	// la función pll_current_language es del plugin polylang; y devuelve el idioma de la entrada actual
	$current_language = function_exists('pll_current_language') ? pll_current_language('locale') : 'es_ES';
	// ojo que en Disqus, inglés es "en" pero español "es_ES" :-P
	return $current_language == 'en_US' ? 'en' : $current_language;
});

Y si no estás usando WordPress, puedes indicar el idioma en la configuración del embebible.

Cómo asegurar las cookies de acceso a tu sitio WordPress

Recientemente se ha dado a conocer una vulnerabilidad en el sistema de autenticación de usuarios para los sitios con WordPress, que básicamente consiste en lo siguiente:

  • La vulnerabilidad afecta tanto a sitios en WordPress.com como instalaciones propias.
  • Al acceder a la administración de tu sitio, WordPress crea una cookie que le permite validarte como un usuario que ha iniciado sesión.
  • Una gran cantidad de sitios con WordPress no funciona sobre HTTPS, por lo que la cookie se envía como texto plano.
  • Un atacante puede interceptar esa cookie, insertarla en su navegador, y luego hacerse pasar por el usuario que ha iniciado sesión. Esto es particularmente fácil en situaciones donde hay transmisiones “abiertas” de datos, por ejemplo en un café donde la conexión no tiene contraseña.
  • La cookie sigue siendo válida aun cuando el usuario cierre su sesión, por lo que podrías ingresar a tu administrador, cerrar la sesión, y el atacante aun tendría acceso.
  • El tiempo de expiración de la cookie es de 3 años en blogs de WordPress.com y 14 días en instalaciones propias.

Ahora pasemos a lo importante, ¿cómo evitarlo?

En primer lugar, la opción más sencilla pero seguramente inefectiva es la abstinencia: simplemente, evitar acceder a la administración de WordPress en redes no confiables. Pero como estamos hablando de seguridad, y en este sentido no se puede ser demasiado paranoico, desechemos esta opción.

Continue reading “Cómo asegurar las cookies de acceso a tu sitio WordPress”

Una breve actualización sobre Satorii

Puesto que Google Code ya está prácticamente muerto, transformé el antiguo repositorio de mi tema gratis para WordPress, Satorii, en un nuevo repositorio en GitHub. Y aprovechando este cambio, me puse a trabajar en algunos de los varios issues reportados, junto con definir algunos hitos para guiar su desarrollo futuro.

Actualmente, la meta fundamental es preparar el tema para poder ofrecerlo desde el repositorio de temas de WordPress, para lo cual sólo restan los detalles finales que han surgido tras un par de rondas de revisión.

Desde ya pueden bajar la versión más reciente, que soluciona varios problemas antiguos y moderniza considerablemente el funcionamiento del tema.

WordPress: get_post_meta() devuelve resultados en orden aleatorio

Una de las funciones más útiles de WordPress es get_post_meta( ), que nos permite obtener alguno de los metadatos asociados a una entrada.

Si deseamos obtener un metadato único, utilizamos get_post_meta( $post->ID, 'meta_key', true ), y si es múltiple, get_post_meta( $post->ID, 'meta_key', false), y aquí nos podemos topar un problema importante, ya que en algunas circunstancias el orden en que devuelve los resultados es aleatorio.

La razón de esto es que al ejecutar la función, WordPress carga la información de todos los metadatos asociados a la entrada en el caché de objetos, pero esta consulta no incluye un ORDER BY que permita determinar el orden, y aunque es posible especificar un orden predeterminado en una tabla, los datos de la tabla no permanecen en el mismo orden al actualizarla (por eso es que el orden es aleatorio)… esto es, si estás utilizando tablas MyISAM.

Sin embargo, existe un workaround para evitar este problema, ya que al utilizar tablas InnoDB, el orden en que se devuelven los resultados al hacer una consulta sin ORDER BY siempre es el mismo: de forma ascendente según su PRIMARY KEY.

O sea, si tienes este problema, te lo puedes ahorrar fácilmente con ALTER TABLE wp_postmeta ENGINE = InnoDB.

¡Ah! Por cierto, este problema ya está reportado como bug, y la solución (o sea, el ORDER BY) debiese venir incluído en WordPress 3.8; ¡yei!