Migration des tags Dotclear vers WordPress

Il y a quelques jours, je vous ai expliqué comment je m’y suis pris pour migrer mon blog Dotclear vers WordPress et mettre en place une redirection des anciennes URL vers les nouvelles. Une solution relativement simple, s’appuyant sur des plug-ins existants pour WordPress et sur quelques lignes de PHP. Avec tout de même un défaut, auquel j’ai remédié ce week-end : les mots-clefs n’étaient pas migrés. C’est désormais chose faite.

N’ayant pas trouvé de solution clé en main pour effectuer cette migration, j’ai décidé d’en faire une moi même, ce qui est en fait relativement simple, même si la structure de base de WordPress est un poil plus complexe que celle de Dotclear.

Côté Dotclear, c’est simple, voir simpliste : une unique table meta contient des triplets composés de l’ID de la méta-donnée (qui est le mot-clé lui même dans notre cas), du type de la méta-donnée et de l’ID du billet auquel elle est associée. Par exemple, si le billet numéro 912 a le tag mot-clé adsl, on va trouver une ligne (‘adsl’, ‘tag’, 912) dans la table meta.

Côté WordPress, il y a une distinction entre le stockage des mots-clés et leur association à des articles. Le mécanisme s’appuie sur trois tables différentes :

  • terms qui contient les mots-clés, leur slug (l’identifiant pour les reconnaitre dans l’URL), leur groupe (pas cherché à comprendre ce que c’est ^^) et un term_id unique,
  • term_taxonomy qui associe un term_id avec une taxonomie (type de terme, par exemple post_tag pour un tag, category pour une catégorie), un term_taxonomy_id unique, une description et un parent optionnel, et un compteur d’occurences,
  • term_relationships qui associe un term_taxonomy_id avec un ID d’article.

Si l’article numéro 912 a le mot-clé adsl (et est le seul à l’avoir), on va trouver une entrée (x, ‘adsl’, ‘adsl’, 0) dans la table terms, une entrée (y, x, ‘post_tag’,  », 0, 1) dans term_taxonomy et (912, y, 0) dans term_relationships.

De ces structures découle donc l’algorithme de migration suivant :

  1. récupérer l’ensemble des mots-clés et la liste des billets associés dans la base Dotclear,
  2. insérer les mots-clés dans la base WordPress (tables terms et term_taxonomy),
  3. récupérer les term_taxonomy_id des mots-clés,
  4. faire la correspondance entre les id des billets Dotclear et ceux des articles WordPress,
  5. pour chaque mot-clé récupéré à l’étape 1, prendre son term_taxonomy_id trouvé à l’étape 3 et la liste des id d’articles trouvée à l’étape 4 pour insérer les entrées correspondantes dans term_relationships,
  6. mettre à jour le champ count de term_taxonomy en fonction du nombre d’enregistrements correspondants dans term_relationships.

Traduit en PHP, ça donne quelque choses comme ça :

<?php
$dc2Base = "dc2base"; // nom de la base Dotclear
$wpBase = "wpbase"; // nom de la base WP
$sqlServer = "localhost"; // nom du serveur SQL
$sqlUser = "user"; // utilisateur SQL
$sqlPass = "pass"; // mot de passe SQL

// Suppression des accents et des majuscules pour la création du slug
// Source :  http://www.weirdog.com/blog/php/supprimer-les-accents-des-caracteres-accentues.html
function wd_remove_accents($str, $charset='utf-8')
{
    $str = htmlentities($str, ENT_NOQUOTES, $charset);

    $str = preg_replace('#&([A-za-z])(?:acute|cedil|circ|grave|orn|ring|slash|th|tilde|uml);#', '\1', $str);
    $str = preg_replace('#&([A-za-z]{2})(?:lig);#', '\1', $str); // pour les ligatures e.g. '&oelig;'
    $str = preg_replace('#&[^;]+;#', '', $str); // supprime les autres caractères

    return strtolower($str);
}

$tagsdc = Array();
$tagswp = Array();
$dcid2wpid = Array();

// Recherche des mots clés DC
$sql = mysql_connect($sqlServer, $sqlUser, $sqlPass);
mysql_query("SET NAMES 'utf8'", $sql);
$query = "SELECT meta_id, post_id
          FROM $dc2Base.dc2_meta
		  WHERE meta_type = 'tag'";
$result = mysql_query($query, $sql);
$nb_enr = mysql_num_rows($result);
for ($i = 0; $i < $nb_enr; $i++) {
	$tagsdc[mysql_result($result, $i, "meta_id")][] = mysql_result($result, $i, "post_id");
}

// Ajout des tags dans WP
foreach ($tagsdc as $tag => $posts) {
	$slug = wd_remove_accents($tag);
	$query = "REPLACE INTO $wpBase.wp_terms (name, slug)
			  VALUES ('$tag', '$slug')";
	mysql_query($query, $sql);
	if (mysql_affected_rows($sql) > 0) {
		$id = mysql_insert_id($sql);
		$query = "REPLACE INTO $wpBase.wp_term_taxonomy (term_id, taxonomy)
		          VALUES ($id, 'post_tag')";
		mysql_query($query, $sql);
	}
}

// Récupération des id des tags dans WP
$query = "SELECT ta.term_taxonomy_id as id, t.slug as slug
		  FROM $wpBase.wp_terms t, $wpBase.wp_term_taxonomy ta
		  WHERE t.term_id = ta.term_id
		    AND ta.taxonomy = 'post_tag'";
$result = mysql_query($query, $sql);
$nb_enr = mysql_num_rows($result);
for ($i = 0; $i < $nb_enr; $i++) {
	$tagswp[mysql_result($result, $i, "slug")] = mysql_result($result, $i, "id");
}

// Récupération des id des posts dans WP
$query = "SELECT wp.ID as wpid, dc2.post_id as dcid
		  FROM $wpBase.wp_posts wp, $dc2Base.dc2_post dc2
		  WHERE dc2.post_title = wp.post_title";
$result = mysql_query($query, $sql);
$nb_enr = mysql_num_rows($result);
for ($i = 0; $i < $nb_enr; $i++) {
	$dcid2wpid[mysql_result($result, $i, "dcid")] = mysql_result($result, $i, "wpid");
}

// Association des tags aux posts
foreach ($tagsdc as $tag => $posts) {
	if (!isset($tagswp[wd_remove_accents($tag)])) {
		echo "Erreur : tag $tag introuvable dans WP pour les posts ".implode(",", $posts)."<br/>";
		continue;
	}
	$idwp = $tagswp[wd_remove_accents($tag)];
	$postswp = Array();
	foreach ($posts as $post) {
		if (!isset($dcid2wpid[$post])) {
			echo "Erreur : post $post introuvable dans WP pour le tag $tag<br/>";
			continue;
		}
		$postswp[] = "(".$dcid2wpid[$post].", $idwp)";
	}
	if (count($postswp) > 0) {
		$query = "REPLACE INTO $wpBase.wp_term_relationships (object_id, term_taxonomy_id)
		          VALUES ".implode(",", $postswp);
		mysql_query($query, $sql);
	}
}

// Mise à jour des cardinalités
$query = "UPDATE $wpBase.wp_term_taxonomy t
          SET t.count = (SELECT COUNT(*)
		                 FROM wp_term_relationships r
						 WHERE r.term_taxonomy_id = t.term_taxonomy_id)";
mysql_query($query, $sql);
?>

Si la migration de certains mots clés échoue (ce qui peut arriver notamment si le slug est déjà utilisé pour une catégorie, ce cas n’étant pas pris en compte dans le code), un message s’affiche, listant les id des billets correspondant, ce qui permettra de traiter manuellement les cas non traités automatiquement.

J’ai également mis à jour le script de redirection pour gérer la redirection des pages de mots clés et de catégories :

<?php
$redir = "http://www.domaine.tld/wp/"; // URL de base du WP
$dc2Base = "dc2base"; // nom de la base Dotclear
$wpBase = "wpbase"; // nom de la base WP
$sqlServer = "localhost"; // nom du serveur SQL
$sqlUser = "user"; // utilisateur SQL
$sqlPass = "pass"; // mot de passe SQL
$mail = "user@domain.tld"; // mail de destination du rapport d'erreur
$query = "index.php/"; // à remplacer par index.php? si le blog est en mode QUERY_STRING

$isFeed = false;
$isCommentFeed = false;
$context = "";
$slug = "";

$request = urldecode($_SERVER["REQUEST_URI"]);
$identifier = str_replace($query, "", $request);
// Suppression des informations relatives aux flux
if (strpos($identifier, "feed") !== false) {
    $context = "feed";
    $isFeed = true;
    if (strpos($identifier, "comments") !== false) {
        $isCommentFeed = true;
    }
    $identifier = str_replace("feed", "", $identifier);
    $identifier = str_replace("comments", "", $identifier);
    $identifier = str_replace("rss2", "", $identifier);
    $identifier = str_replace("atom", "", $identifier);
    while (strpos($identifier, "//") !== false) {
        $identifier = str_replace("//", "/", $identifier);
    }
}
// Suppression des pages
if (($pos = strpos($identifier, "page")) !== false) {
    $identifier = substr($identifier, 0, $pos);
}
if ($identifier[0] == "/") {
    $identifier = substr($identifier, 1);
}
if ($identifier[strlen($identifier) - 1] == "/") {
    $identifier = substr($identifier, 0, strlen($identifier) - 1);
}
// Traitement des posts
if (strpos($identifier, "post/") === 0) {
    $context = "post";
    $url = substr($identifier, 5);
    $sql = mysql_connect($sqlServer, $sqlUser, $sqlPass);
    $query = "SELECT wp.post_name as redir
              FROM $wpBase.wp_posts wp, $dc2Base.dc2_post dc2
              WHERE LCASE(dc2.post_url) = LCASE('$url')
                AND dc2.post_title = wp.post_title";
    mysql_query("SET NAMES 'utf8'", $sql);
    $result = mysql_query($query, $sql);
    $nb_enr = mysql_num_rows($result);
    if ($nb_enr == 1) {
        $slug = mysql_result($result, 0, "redir");
    }
}
else if (strpos($identifier, "tag/") === 0) {
    $context = "tag";
    $tag = substr($identifier, 4);
    $sql = mysql_connect($sqlServer, $sqlUser, $sqlPass);
    $query = "SELECT t.slug as redir
              FROM $wpBase.wp_terms t, $wpBase.wp_term_taxonomy ta
              WHERE t.name = '$tag'
                AND t.term_id = ta.term_id
                AND ta.taxonomy = 'post_tag'";
    mysql_query("SET NAMES 'utf8'", $sql);
    $result = mysql_query($query, $sql);
    $nb_enr = mysql_num_rows($result);
    if ($nb_enr == 1) {
        $slug = mysql_result($result, 0, "redir");
    }
}
else if (strpos($identifier, "category/") === 0) {
    $context = "categorie";
    $cat = substr($identifier, 9);
    $sql = mysql_connect($sqlServer, $sqlUser, $sqlPass);
    $query = "SELECT t.slug as redir
              FROM $wpBase.wp_terms t, $wpBase.wp_term_taxonomy ta
              WHERE t.name = '$cat'
                AND t.term_id = ta.term_id
                AND ta.taxonomy = 'category'";
    mysql_query("SET NAMES 'utf8'", $sql);
    $result = mysql_query($query, $sql);
    $result = mysql_query($query, $sql);
    $nb_enr = mysql_num_rows($result);
    if ($nb_enr == 1) {
        $slug = mysql_result($result, 0, "redir");
    }
}
if ($context == "feed") {
    $redir = $base . "feed";
    if ($isCommentFeed) {
        $redir .= "/comments";
    }
}
else if ($slug != "") {
    if ($context == "tag" || $context == "categorie") {
        $redir = $base . $context . "/" . $slug;
        if ($isFeed) {
            $redir .= "/feed";
        }
    }
    else {
        $redir = $base . $slug;
        if ($isCommentFeed) {
            $redir .= "/feed";
       }
    }
}
if (isset($redir)) {
    header("HTTP/1.1 301 Moved Permanently");
    header("Location: $redir");
    exit;
}

if ($mail != "") {
    mail($mail, "Problème de redirection DC2 vers WP", "Aucune correspondance trouvée pour l'URL $request");
}

define('DC_BLOG_ID','default');
require 'inc/public/prepend.php';
?>

4 réflexions sur « Migration des tags Dotclear vers WordPress »

  1. MErci pour ce script
    je l’ai un peu modifié car il y avait quelques bugs
    mais au final, cela ne marche pas non plus..
    il n’arrive pas à associer les etiquettes et les posts
    de plus, les slugs ne sont pas correctement insérés non plus

    et la j’avoue que mes connaissances php ne suffisent pas.. 🙁

    1. Ça avait bien fonctionné à l’époque pour la migration d’Infobidouille, mais depuis le temps c’est possible que les évolutions respectives de Dotclear et de WordPress aient rendu ce script inutilisable 🙁

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.