Entradas en la categoría: PHP

Tip: Optimización de consultas SQL en plugins de WordPress

Durante el tiempo que no hubo actividad alguna en este blog y por algunos trabajos que me encomendaron relacionados al desarrollo de plugins y modificación del código de WordPress, he visto algo que se repite casi en todos los plugins que he visto hasta el momento: usan varias consultas pequeñas cuando sólo una puede hacer el trabajo (no tengo idea de porqué hacen las cosas de ese modo).

$a = query("select ID from tabla1 ...");
$b = array();
foreach($a as $v) {
	$b[] = query("select ... from tabla2 where ID = $v");
}

El problema del código mostrado es que se hacen consultas innecesarias a la base de datos cuando en este caso un simple JOIN serviría para el mismo propósito.

Como ejemplo voy a poner dos plugins que acompañaron a este blog desde hace más de un año y por los que el número de consultas SQL aunmentaba en 55 para generar la página principa. Éstos son:

  • Real Fast Latest Comments: En este caso el plugin realiza una consulta para obtener las entradas que recibieron comentarios recientemente y luego itera para mostrar los datos devueltos:

    function rflc_show_comments($comment_limit = 5, $show_trackbacks = false) {
    	global $wpdb;
    
    	if(!$show_trackbacks) {
    		$activity = $wpdb->get_results("SELECT $wpdb->comments.comment_date, $wpdb->comments.comment_author, 
    						$wpdb->comments.comment_ID, $wpdb->posts.post_title, 
    						$wpdb->posts.ID FROM $wpdb->comments INNER JOIN 
    						$wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID WHERE 
    						$wpdb->comments.comment_approved = '1' AND $wpdb->comments.comment_type 
    						NOT LIKE '%back%' ORDER BY $wpdb->comments.comment_date DESC
    						LIMIT $comment_limit");
    	} else {
    		$activity = $wpdb->get_results("SELECT $wpdb->comments.comment_date, $wpdb->comments.comment_author, 
    						$wpdb->comments.comment_ID, $wpdb->posts.post_title, 
    						$wpdb->posts.ID FROM $wpdb->comments INNER JOIN 
    						$wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID WHERE 
    						$wpdb->comments.comment_approved = '1' 
    						ORDER BY $wpdb->comments.comment_date DESC
    						LIMIT $comment_limit");
    	}
    	
    	if($activity) {
    		echo '<ul>';
    		foreach($activity as $comment) {
    			echo '<li>'.wp_specialchars($comment->comment_author).' en: <a href="'. get_permalink($comment->ID) .'#comment-'. $comment->comment_ID .'">'. wp_specialchars($comment->post_title) .'</a></li>' . "\n";
    		}
    		echo '</ul>';
    	}
    }

    El problema en este caso es que para mostrar el enlace (permalink) de una entrada, el plugin invoca en cada iteración a la función get_permalink, la misma que hace una nueva consulta (o varias dependiendo de la estructura de permalinks) para recuperar la entrada y formatear el enlace, esto pasa siempre y cuando el argumento pasado no sea un objeto o la entrada ya se encuentre en la caché de objetos.

  • Related Posts: El problema es el mismo que se comenta en el anterior, a continuación muestro la parte relevante:

    $sql = "SELECT ID, post_title, post_content,"
    	 . "MATCH (post_name, post_content) "
    	 . "AGAINST ('$terms') AS score "
    	 . "FROM $wpdb->posts WHERE "
    	 . "MATCH (post_name, post_content) "
    	 . "AGAINST ('$terms') "
    	 . "AND post_date <= '$now' "
    	 . "AND (post_status IN ( 'publish',  'static' ) && ID != '$post->ID') ";
    if ($show_pass_post=='false') { $sql .= "AND post_password ='' "; }
    $sql .= "ORDER BY score DESC LIMIT $limit";
    $results = $wpdb->get_results($sql);
    $output = '';
    if ($results) {
    	foreach ($results as $result) {
    		$title = stripslashes(apply_filters('the_title', $result->post_title));
    		$permalink = get_permalink($result->ID);
    		$post_content = strip_tags($result->post_content);
    		$post_content = stripslashes($post_content);
    		$output .= $before_title .'<a href="'. $permalink .'" rel="bookmark" title="Permanent Link: ' . $title . '">' . $title . '</a>' . $after_title;
    
    		if ($show_excerpt=='true') {
    			$words=split(" ",$post_content); 
    			$post_strip = join(" ", array_slice($words,0,$len));
    			$output .= $before_post . $post_strip . $after_post;
    		}
    	}
    	echo $output;
    }

Para evitar este comportamiento pero con algunas posibles consecuencias no deseadas**, se debe recuperar también el campo post_name en la primera consulta de ambos casos y a continuación invocar a la función get_permalink con un objeto como parámetro — get_permalink($comment); y get_permalink($result); respectivamente.

**: Al usar la función get_permalink con un objeto como parámetro, éste se almacena en el caché de objetos y cualquier función que haga uso de get_post puede mostrar entradas “incompletas”. Una forma de evitar esto es recuperar todos los campos de la tabla posts o quitar la entrada almacenada en cache luego de invocar a la función get_permalink.

Tags: , , ,

Para los que puedan estar interesados, Misión Géminis 48 hs. es un concurso de Microsoft para…

… demostrar en 2 etapas qué tanto sabes acerca de .Net y PHP. Durante la primera etapa deberás responder una serie de preguntas de selección múltiple sobre estos lenguajes de programación. Los participantes que contesten correctamente todo el cuestionario, con la posibilidad de múltiples intentos, podrán pasar a la segunda etapa – La Misión. Allí deberás estar preparado para el reto de desarrollar una aplicación web funcional, un sistema de Control Epidemiológico basado en PHP, y migrarla a ASP.NET. Tendrás sólo 48 horas para hacerlo, y los primeros 48 que lo hagan estarán participando por increíbles premios. Los 3 ganadores designados por especialistas calificados recibirán U$D 1.000, U$D 800 y U$D 400, respectivamente.

Para mayor información revisen la página del concurso.

#Permalink Comentarios (2) .NET, Microsoft, Miniposts, PHP

WordPress: Llamadas múltiples en XML-RPC

Según WikiPedia, XML-RPC es un protocolo de llamada a procedimiento remoto que usa XML para codificar los datos y HTTP como protocolo de transmisión de mensajes, creado por Dave Winer (Userland Software) y Microsoft en 1998.

A pesar de que este protocolo tiene mucho más tiempo de lo que yo con el uso de computadoras, recién hace 3 meses que conozco de la existencia de éste y algo que me llama la atención es el soporte para invocar múltiples métodos en una sola petición HTTP — aunque en realidad depende si el servidor XML-RPC implementa/soporta este tipo de llamadas.

Por ejemplo, el servidor XML-RPC de WordPress soporta este tipo de llamadas gracias a Incutio XML-RPC Library:

<?php
require_once( './class-IXR.php' );

$client = new IXR_ClientMulticall( 'http://www.buayacorp.com/xmlrpc.php' );

$client->debug = true;

$client->addCall('demo.sayHello');
$client->addCall('demo.addTwoNumbers', 10, 1);

$client->query();

print_r($client->getResponse());
?>

Lo que se envía y recibe del servidor:

POST /xmlrpc.php HTTP/1.0
Host: www.buayacorp.com
Content-Type: text/xml
User-Agent: The Incutio XML-RPC PHP Library (multicall client)
Content-length: 652

<?xml version="1.0"?>
<methodCall>
<methodName>system.multicall</methodName>
<params>
<param><value><array><data>
  <value><struct>
  <member><name>methodName</name><value><string>demo.sayHello</string></value></member>
  <member><name>params</name><value><array><data>
</data></array></value></member>
</struct></value>
  <value><struct>
  <member><name>methodName</name><value><string>demo.addTwoNumbers</string></value></member>
  <member><name>params</name><value><array><data>
  <value><int>10</int></value>
  <value><int>1</int></value>
</data></array></value></member>
</struct></value>
</data></array></value></param>
</params></methodCall>


<?xml version="1.0"?>
<methodResponse>
<params>
<param>
<value>
<array><data>
<value><array><data>
<value><string>Hello!</string></value>
</data></array></value>
<value><array><data>
<value><int>11</int></value>
</data></array></value>
</data></array>
</value>
</param>
</params>
</methodResponse>


Array
(
    [0] => Array
        (
            [0] => Hello!
        )

    [1] => Array
        (
            [0] => 11
        )

)

Haciendo uso de este tipo de llamadas y gracias a ciertas características de WordPress, fue posible hacer funcionar el tan publicitado exploit para una vulnerabilidad que afecta a casi todas las versiones de este CMS.

PHP: Uso adecuado de parse_str

La siguiente porción de código es una práctica muy extendida en WordPress, sirve para que las funciones puedan tener un buen número de parámetros opcionales sin que el código sea visualmente feo:

<?php
/* Funciones de WordPress */
function stripslashes_deep($value) {
	 $value = is_array($value) ?
		 array_map('stripslashes_deep', $value) :
		 stripslashes($value);

	 return $value;
}
function wp_parse_str( $string, &$array ) {
	parse_str( $string, $array );
	if ( get_magic_quotes_gpc() )
		$array = stripslashes_deep( $array );
	return $array;
}
/* Fin */

function get_posts( $args = '' ) {
	$defaults = array(
		'limit' => 5,
		'post_type' => ''
	);
	$args = wp_parse_str($args, $defaults);
	
	extract($args, EXTR_SKIP);
	
	$where = '';
	if ( !empty($post_type) ) 
		$where = "WHERE post_type = '$post_type'";
	
	$sql = "SELECT * FROM posts $where LIMIT $limit";
	// ejecutar la consulta en una base de datos MySQL
	echo htmlspecialchars($sql);
}

$limit = empty($_GET['limit']) ? 5 : (int) $_GET['limit'];

if (get_magic_quotes_gpc())
	$_GET['type'] = stripslashes($_GET['type']);
// Se usa addslashes sólo para el ejemplo.
// $type = mysql_real_escape_string($_GET['type']);
$type = addslashes($_GET['type']);
get_posts("limit=$limit&post_type=$type");
?>

Sobre el ejemplo mostrado:

  1. ¿Tiene algún problema de seguridad?
  2. ¿Qué pasa si cambiamos la línea resaltada por esta otra, tiene algún problema de seguridad?
    $where = "WHERE post_type = '". addslashes($post_type) . "'";

Para los interesados en este pequeño quiz, subí una página en la que pueden realizar sus pruebas.

Hoy acaban de anunciar que ya existe un versión estable de PHPIDS, un sistema de detección de intrusos basado en expresiones regulares. Pueden descargar el código desde el repositorio o hacer pruebas para ver como funciona esta pequeña librería.

Por otro lado, también existe .NETIDS, que es una versión en .NET — realizada por Martin Hinks — de PHPIDS.

#Permalink Comentarios (0) .NET, ASP.NET, Miniposts, PHP, Seguridad, Sql Injection, Web, XSS