Steve Frécinaux

DotClear : des permaliens sans numéros de billets

S’il y a un détail qui m’a toujours embêté dans DotClear, c’est bien le fait qu’il y ait le numéro du billet dans son URL. Je trouve que ça n’a rien à faire là, à partir du moment où ce n’est pas vraiment une information pertinente sur le billet, et que ça m’empêche par exemple de republier d’anciens trucs, parce que le numéro serait à la suite des derniers posts alors que la date serait plus ou moins loin dans le passé (ceci est surtout d’ordre esthétique, j’avoue, et pas forcément indispensable). Enfin, je trouve que ça s’accorde mal avec le fait de mettre la date dans l’URL, je trouve que ça fait en quelque sorte un peu doublon…

Or s’il était difficile de se passer de ce numéro dans DotClear 1.0 rc3, la version actuelle (1.2 bêta) fournit tout ce qu’il faut pour s’en passer, car elle stocke en base la date du billet et le titre mis en forme pour être intégré dans l’URL. Je vais donc détailler ici une modification pour que DotClear ne se repose plus sur le numéro mais sur ces deux informations. Notez que vous devrez faire gaffe à ne pas avoir deux billets portant le même titre la même journée, car DotClear ne le vérifiera pas pour vous…

(Je ne saurais que trop vous conseiller de faire une sauvegarde de ces fichiers avant de faire ceci, car… « on se sait jamais ! »)

Étape 1 : récupérer un billet

Ouvrez le fichier /dotclear/inc/class/class.blog.php. Nous allons ajouter une méthode pour récupérer un billet selon sa date et son titre plutôt que selon son numéro. Copiez donc ces lignes juste avant la méthode getPostById de la classe Blog :

# Sélection d'un billet par sa date et son titre
function getPostBySlug($y,$m,$d,$slug)
{
   $reqPlus = 'AND DATE_FORMAT(post_dt,\'%Y\') = \''.(integer) $y.'\''
            . ' AND DATE_FORMAT(post_dt,\'%c\') = \''.(integer) $m.'\''
            . ' AND DATE_FORMAT(post_dt,\'%e\') = \''.(integer) $d.'\''
            . ' AND post_titre_url = \'' . $this->con->escapeStr($slug) . '\'';
   
   $strReq = $this->SQL($reqPlus);
   
   if (($rs = $this->con->select($strReq,$this->rs_blogpost)) !== false) {
      $rs->setBlog($this);
      return $rs;
   } else {
      $this->setError('MySQL : '.$this->con->error(),2000);
      return false;
   }
}

« Slug » est un mot anglais que j’ai souvent vu associé au titre mis en forme pour être placé dans l’URL. Il faut à présent analyser l’URL pour en retirer les informations que cette méthode requiert, c’est à dire l’année, le mois, le jour et le « slug ».

Étape 2 : analyser l’URL

C’est cette fois le fichier /dotclear/layout/lib.mod.php qui nous intéresse, et en particulier les lignes entre 81 et 85. Nous allons en effet remplacer ces lignes :

if(preg_match('!^[0-9]{4}/[0-9]{2}/[0-9]{2}/([0-9]+)!',$args,$match))
{
   $_GET['p'] = $match[1];
}

par celles-ci :

if(preg_match('!^[0-9]{4}/[0-9]{2}/[0-9]{2}/([0-9a-zA-Z\_\-]+)$!',$args,$match))
{
   $_GET['p'] = $match[1];
}

Ce qui est fait n’est guère difficile à comprendre : plutôt que récupèrer simplement un numéro qui se trouve juste après la date, on récupère tout le texte qui suit celle-ci.

Étape 3 : tout relier

Nous allons maintenant modifier le cœur de DotClear, à savoir le fichier /dotclear/layout/prepend.inc. Commençons par la ligne numéro 33, et remplaçons :

$post_id = (!empty($_GET['p'])) ? $_GET['p'] : NULL;

par :

$slug = (!empty($_GET['p'])) ? $_GET['p'] : NULL;

La raison est que $post_id sera toujours utilisé dans notre version modifiée pour stocker le numéro du billet, car celui-ci intervient de nombreuses autres fois que celles que nous allons modifier.

Cherchez ensuite la ligne elseif($mode == 'post' && $post_id) qui marque le début du traitement du cas où l’on désire afficher un billet en particulier. Remplacez-la dans la foulée par elseif($mode == 'post' && $slug) pour prendre en compte la nouvelle variable fournie par l’analyse de l’url. Juste en dessous se trouve la ligne :

$news = $blog->getPostById($post_id);

Remplaçons-la par :

$news = $blog->getPostBySlug($year,$month,$day,$slug);

if ($news->isEmpty()) {
   preg_match('#^([0-9]+)\-#',$slug,$matches);
   $post_id = $matches[1];
   if ($post_id) $news = $blog->getPostById($post_id);
} else {
   $post_id = $news->f('post_id');
}

Ce qui est exécuté si aucun billet n’est trouvé ($news->isEmpty()) l’est pour assurer la compatibilité avec les anciennes URLs. En effet on récupère alors l’éventuel numéro du billet pour le rechercher avec l’ancienne méthode fournie dans ce but.

Étape 4 : génération des permaliens

Étape très rapide, il suffit de remplacer une ligne dans /dotclear/inc/post_config.php (ou dans votre fichier de configuration (/dotclear/conf/config.php) suivant les cas) :

En ligne 47, remplacez :

if (!defined('dc_format_post_url')) { define('dc_format_post_url','%04d/%02d/%02d/%d-%s'); }

par :

if (!defined('dc_format_post_url')) { define('dc_format_post_url','%04d/%02d/%02d/%5$s'); }

Et pour finir…

Voilà, c’est terminé. Pour réduire le nombre de requêtes dans le cas des anciens billets, surtout si ceux-ci sont nombreux et que vous voulez conserver leur ancienne URL pour le principe qui dit que Les URLs sympas ne changent pas (ce que j’ai fait ici), vous pouvez exécuter cette requête SQL sur votre table dc_post :

UPDATE dc_post SET post_titre_url = CONCAT(post_id,'-',post_titre_url);

Dans le cas contraire, une nouvelle url de permalien sans numéro sera proposée sur les pages de ces anciens billets, et une requête supplémentaire sera nécessaire pour les afficher à partir des anciens liens hypertextes pointant vers ceux-ci.