jhc_most_visited_articles: un plugin para Textpattern

En el desarrollo de mi última colaboración tuvimos que implementar el listado de los artículos más visitados de una sección, cosa que no había hecho antes con Textpattern (bien por vagancia, porque no me tocó o vete tú a saber). El tema es que buscando en el repositorio de plugins de Textpattern encontré el plugin jas_popular_articles de Jose Antonio Solís que a priori me pareció válido.

Pero como suele ocurrir, y con esto no desmerezco el trabajo de nadie, lo que a unos les vale a otros no. Y este fué mi caso. El plugin jas_popular_articles no realiza correctamente los cálculos si durante un periodo de tiempo Textpattern trabaja con messy URLs (URL sucias) y en otro lo hace con non-messy URLS o URLs amigables.

Después de implementar una primera versión para salir del paso me apunté, en mi lista particular de TO-DOs, el modificar, más todavía, el plugin de Jose Antonio Solís para que funcionase con independencia del tipo de URL con el que Textpattern trabajase.

Manos a la obra

Para refereirnos a un mismo artículo, con identificador 23 y título “Primer microformato en castellano”, Textpattern nos permite trabajar con 5 URLs diferentes.

  1. messy o sucia, son URLs del tipo http://www.example.com/?id=23
  2. mes/dia/año/titulo: http://www.example.com/12/01/2008/primer-microformato-en-castellano
  3. secion/titulo: http://www.example.com/section/primer-microformato-en-castellano
  4. titulo: http://www.example.com/primer-microformato-en-castellano
  5. blabalbal

Esta versatilidad en el uso de una u otra URL dificulta la extracción de los artículos más visitados a partir de los registros almacenados en la tabla de accesos, ya que debemos tener en cuenta que el hecho de que un usuario visite la página http://www.example.com/?id=23 es lo mismo que si visitase cualquiera de las demás opciones que Textpattern nos proporciona.

Por otro lado, la información de la que disponemos en la tabla de accesos dependerá de cómo Textpattern esté configurado para registrar las visitas. Si lo tenemos preparado para que registre todos los hits nos encontraremos con que tenemos registro de todas las peticiones que se realicen a URLs dentro de nuestra web, desde la que el usuario usó para acceder a la Web hasta la última antes de abandonarla. En cambio, si Textpattern está registrando sólo referentes únicamente almacenará información sobre la primera vez que un nuevo usuario accede a un documento de nuestra Web.

Así pués, a la hora de extraer los artículos más visitados debemos tener en cuenta dos cosas.

  • Sólo hemos de contabilizar entradas que sean artículos, no valen secciones o categorías.
  • Teniendo en cuenta los tipos de URLs para un mismo artículo, hemos de evitar duplicidades.

Con estas dos consideraciones en mente, decidí crear un plugin que se apoyase más en la base de datos que en el PHP, que mediante consultas preparase la mayor cantidad de datos posible y los dejase lo más preparados posible para que con una posterior extracción vía PHP tuvieramos los artículos del modo más sencillo. Pués bien, esto es lo que me salió.

function jhc_most_visited_article($atts) {
	global $prefs, $s;
	$siteurl = $prefs['siteurl'];
	extract($atts);
	$break    = (empty($break))    ? '<br>' : $break;
	$wraptag  = (empty($wraptag))  ? 'li' : $wraptag;
	$limit    = (empty($limit))    ? 10 : $limit;
	$range    = (empty($range))    ? '' : $range; // y = yearly; m = monthly
	$section  = (empty($section))  ? '' : $section;

	$output = ''; // output string

	// create the query string
	if ($range == 'm') { // Monthly
		$range = 'AND SUBSTR(time,1,7) = "' .  date("Y") . '-' . date("m") . '"';
	}
	if ($range == 'y') { // Yearly
		$range = 'AND SUBSTR(time,1,4) = "' . date("Y") . '"';
	}
	// temp table
	$sql = 'CREATE TEMPORARY TABLE IF NOT EXISTS tmp_articles (id int(12), title varchar(255), max int(12))';
	safe_query($sql, false);
	// messy urls
	$sql = 'INSERT INTO tmp_articles SELECT SUBSTR(page, LOCATE("id", page)+3)+0 AS ID, "", COUNT(*) AS max FROM ' . safe_pfx_j('txp_log') .
			' WHERE page LIKE "%id=%" ' . $range . ' GROUP BY ID ORDER BY ID DESC LIMIT ' . $limit;
	safe_query($sql, false);
	// no messy urls
	$what = 'COUNT(*) AS max, SUBSTRING_INDEX(page, "/", -1) AS page2 ';
	$where = ' LENGTH(SUBSTRING_INDEX(page, "/", -1)) > 0 AND EXISTS (SELECT * FROM textp_textpattern AS t WHERE t.url_title = page2) ' . $range .' GROUP BY page2 ORDER BY max DESC LIMIT ' . $limit;
	$rest = '';
	// Get URLs ordered by number of visits
	$rs = safe_rows($what,'txp_log', $where . $rest, false);
	// Extract article IDs
	while(list($c,$data)=each($rs)){
		$where2 = 'url_title = "' . $data["page2"] . '"';
		if(!empty($section)) $where2 .= ' AND Section = "'.$section.'"';
		if($rs2 = safe_row('ID, Title, url_title', 'textpattern', $where2, false)){
			$sql = 'INSERT INTO tmp_articles (id, title, max) VALUES('.$rs2['ID'].',"'.$rs2['Title'].'",'.$data['max'].')';
			safe_query($sql, false);
		}
	}
	// final sql     
	$sql = 'SELECT DISTINCT textpattern.ID, textpattern.Title FROM tmp_articles AS t, ' .safe_pfx_j('textpattern').
			' WHERE t.id = textpattern.ID ';
	if(!empty($section)) $sql .= ' AND textpattern.Section = "'.$section.'" ';
	$sql .= 'ORDER BY t.max DESC LIMIT ' . $limit;
	$rsF = getRows($sql, false);
	if($rsF){
		while(list($c,$v)=each($rsF)){
			$content = "<txp:permlink id='".$v['ID']."'>".$v['Title']."</txp:permlink>";
			$output .= (empty($wraptag)) ? $content . $break : tag($content, $wraptag) . "\n";
		}
	}
	return $output;
}

Sé que hay mejores modos de hacerlo, como cambiar el modo en que generamos los enlaces, pero este es el que se me ocurrió y me funciona 🙂 Si tenéis algún comentario al respecto no dudéis en compartirlo con todos, ciao.