Migration de données… destination WordPress !

Cet article est le résumé d’une conférence que j’ai eu la chance d’animer au WordCamp Bordeaux 2017. Les WordCamps sont des événements portés par la communauté WordPress au cours desquels des intervenants partagent des outils, des techniques et des retours d’expériences en lien avec WordPress.

J’y ai présenté ma méthodologie pour migrer tous types de données vers WordPress.

Photo par WPFR | WordCamp Bordeaux

Qu’est-ce qu’une migration de données ?

Commençons par définir de quoi il s’agit… Migrer des données vers WordPress, c’est l’action de récolter des informations inexploitables par le CMS, les mettre en forme et les injecter dans WordPress de façon à ce qu’il puisse les utiliser.

La plupart du temps, ces informations ne sont pas exploitables car elles se trouvent sur un système extérieur, dans un fichier, une base de données, une API rest ou un autre outil de gestion de contenu (Joomla, Drupal, Spip…). Parfois, les données sont déjà enregistrées sur WordPress, mais simplement mal formatées ou mal organisées ; il est donc nécessaire de les transformer ou de les déplacer.

Bien sûr, le cas typique où il faut migrer des données, c’est lors de la refonte d’un site : on a créé un nouveau WordPress et on a besoin d’importer les articles, les pages, les utilisateurs et les catégories de l’ancienne plateforme. On passe alors par un plugin d’import pour transférer les contenus.

Les solutions d’import existantes

Il existe une grande quantité d’extensions consacrées au transfert de données entre CMS. Ces modules, appelées « importeurs » sont disponibles sur le « plugin repository » officiel de WordPress et apparaissent dans la section « Outil > Importer » de l’administration de WordPress une fois qu’ils sont installés.

L’écran des imprteurs

Dans les anciennes versions du CMS, tous les importeurs étaient inclus par défaut. Ce n’est plus le cas car ces outils sont rarement utilisés. Cependant, à l’installation d’un site, WordPress vous propose via ce panneau de télécharger les systèmes d’import les plus populaires (Blogger, Typepad, Tumblr, WordPress…) et vous invite à faire une recherche dans le répertoire des extensions si vous ne trouvez pas l’outil qui vous convient.

En plus des extensions gratuites d’import, il en existe aussi des payantes (proposant plus d’options de transfert). Il y a aussi des plugins d’import « multipurpose » qui permettent des réaliser des migrations à partir de fichiers (CSV ou XML) en s’adaptant à un référentiel personnalisé.

Pour finir, on peut aussi tomber sur des scripts de migration gratuits (distribués sous la forme de fichier PHP) à déposer à la racine d’une installation WordPress, et à supprimer après utilisation.

Vous l’aurez compris : il y a plein d’outils disponibles, sous divers formats. Je ne vais pas en parler dans cet article.

Non-pas que je n’ai pas confiance en ces dispositifs, mais simplement parce que je souhaite vous expliquer comment ils fonctionnent.
Utiliser des outils sans les comprendre, c’est risquer d’être dépourvu au moindre pépin (au moindre transfert échoué, en l’occurence). Alors que si vous comprenez l’outil, vous saurez comment le patcher… et peut-être même préférerez-vous concevoir vos propres scripts 😋

Comment fonctionne une migration ?

La migration se fait en trois étapes :

  1. On récupère les informations qui nous intéressent ;
  2. On les nettoie et on les met en forme dans un tableau PHP ;
  3. On les insert dans WordPress en utilisant les fonctions natives du CMS.

Il est important d’utiliser les fonctions de WordPress : en les contournant on risque de ne pas déclencher certaines actions (hook) et de perdre certaines informations (le compte des commentaires, les formats d’images…) qui sont normalement calculées lors de la création d’un article, par exemple.

Identifier et parcourir la source des contenus à importer

La première étape est sans doute la plus compliquée, dans le sens où il n’y a pas une unique façon de faire.

Il existe une multitude de sources : cms (Joomla, Drupal, Pluxml, Magento, Spip, Typo3…), fichiers (csv, tsv, xml, txt), bases de données, API (webservices)…  ces sources stockent leurs données sous des formes différentes, selon des référentiels qui varient, et des méthodes de lecture dépendant de leurs natures…

Heureusement, l’outil dont vous souhaitez extraire les données dispose certainement d’une documentation. Lisez-la pour comprendre comment sont strucutrées les données et comment vous pouvez les lire.

Ensuite, selon la nature de la source, vous devez trouver le moyen de parcourir ces données en PHP. Voici quelques exemples :

  • Pour un fichier CSV / TSV, vous pouvez ouvrir le fichier avec la fonction fopen() puis parcourir les lignes en utilisant fgetcsv()
  • Pour un flux XML, les classes SimpleXMLElement ou DOMDocument permettent de parcourir le flux comme un objet (avec des méthodes pour accéder aux différents nœuds ou attributs)
  • Pour du JSON, un simple json_decode() suffira pour récupérer un objet PHP à parcourir
  • Pour une base de données, vous pouvez créer une nouvelle instance de la classe WPDB afin de l’utiliser pour faire des requêtes SQL à la façon de WordPress
  • Vous exportez les données d’un site WordPress ? Ne vous gênez pas pour utiliser get_posts, get_users, get_terms

Pour chaque import, vous devez identifier votre source, sa nature (son format) et choisir un parseur PHP.

Une fois arrivés ici vous aurez déjà fait le plus dur 👏

Construire un tableau PHP des éléments à insérer

Si on regarde le schéma de la base de données de WordPress, on constate qu’il stock des objets de 4 natures différentes :

  • les posts (qui regroupent les articles, les pages, les types de contenus personnalisés) ;
  • les termes de taxonomie (catégories, étiquettes…) ;
  • les utilisateurs ;
  • les commentaires.

… et pour chacun de ces objets, il peut aussi enregistrer des « meta ». Les meta-données sont des informations secondaires qu’on ne peut pas enregistrer dans la table principale. Il y a donc des post_meta, des term_meta, des user_meta et des comment_meta.

Si je vous présente une partie du « modèle de données » de WordPress, c’est que pour chaque information à migrer, il va falloir déterminer sa nature et utiliser LA fonction native associée (vous allez voir, c’est simple) :

  • wp_insert_{$type}() pour insérer l’élément principal (ou $type peut-être post, user, term ou comment);
  • update_{$type}_meta() pour enregistrer les informations annexes, relatives à un élément.

C’est en utilisant ces fonctions que nous réaliserons l’insertion des contenus.

Mais attention il y’a un ordre à respecter ! Si vous créer un article sans avoir créé son auteur, il faudra alors revenir éditer l’article pour lui attribuer l’auteur fraichement inséré. De même, on insère pas un commentaire sans pouvoir l’attacher à un article !
Je vous invite à procéder dans cet ordre :

  1. D’abord les termes de taxonomie (vous serez débarrassés) ;
  2. ensuite les utilisateurs ;
  3. puis les posts (et fichiers attachés au passage) ;
  4. les commentaires pour finir.

Faites des sauvegardes

Attention !! On va bientôt insérer des données dans votre site tout neuf, faites des sauvegardes avant d’aller plus loin !
Il est très probable que le transfert ne fonctionne pas du premier coup : gardez une copie du site et de la base de données sous le coude afin de pouvoir revenir au dernier « checkpoint » si ça se passe mal.

Les sauvegardes seront votre seul parachute !

Et faites des backups à chaque étape significative.
Une migration peut prendre beaucoup de temps (plusieurs jours à s’exécuter dans certains cas), il serait vraiment dommage de devoir repartir à zéro…

Importer des termes de taxonomies

Comme prévu, commençons par créer nos termes de taxonomie. Ici je prend pour exemple un tableau qu’on aurait construit à partir des données d’un ancien site. On peut préciser de nombreuses choses, tels que le nom du terme, son slug, sa description, un éventuel terme « parent »…

Ensuite on envoie chaque éléments dans la base à l’aide de la fonction wp_insert_term().

require '../wp-load.php';

$taxonomies = [ 'category' => [
    [
        'name'        => 'Catégorie 1',
        'slug'        => 'cat-1',
        'parent'      => false,
        'description' => 'C’est ma catégorie',
    ],
    [
        'name'        => 'Catégorie 2',
        'slug'        => 'cat-2',
        'parent'      => 'cat-1',
        'description' => 'C’est ma sous-catégorie',
    ],
] ];

foreach ( $taxonomies as $tax => $terms ) {
    foreach ( $terms as $term ) {
        if ( $term['parent']
          && $parent = term_exists( $term['parent'], $tax ) ) {
            $term['parent'] = $parent['term_id'];
        }
        wp_insert_term( $term['name'], $tax, $term );
    }
}

Avant de créer un terme, il est possible de vérifier s’il existe déjà dans la taxonomie ciblé, grâce à la fonction term_exists().

Attention, nous allons parler d’un élément important pour toute migration de données…

Enregistrer un identifiant extérieur

À chaque fois que vous importez une donnée externe dans WordPress, il est important de lui associer un « identifiant extérieur ».  Il s’agit d’une clé qui nous permet d’identifier cet objet dans la source d’origine. Il l’insérant en tant que meta donnée, il nous permettra :

  • De différencier les contenus importés dans le site WordPress, et ceux qui y était déjà (si on souhaite les supprimer par exemple) ;
  • de pouvoir retrouver l’élément dans le flux originel, si on a oublié de migrer une information ;
  • de pouvoir mettre à jour un élément importé ;
  • de mettre en place des redirections automatiques (voir plus loin dans l’article).

Pour enregistrer un identifiant extérieur, on utilise donc la fonction update_{$type}_meta() (ici un term), en reprenant l’ID de la catégorie fraichement insérée :

require '../wp-load.php';

$taxonomies = [ 'category' => [
    [
        'name'        => 'Catégorie 1',
        'slug'        => 'cat-1',
        'parent'      => false,
        'description' => 'C’est ma catégorie',
        'meta'        => [ 'extId' => '12' ], // new
    ],
    [
        'name'        => 'Catégorie 2',
        'slug'        => 'cat-2',
        'parent'      => 'cat-1',
        'description' => 'C’est ma sous-catégorie',
        'meta'        => [ 'extId' => '14', 'old_uri' => '/categorie/foo/bar/' ],  // new
    ],
] ];

foreach ( $taxonomies as $tax => $terms ) {
    foreach ( $terms as $term ) {
        if ( $term['parent']
          && $parent = term_exists( $term['parent'], $tax ) ) {
            $term['parent'] = $parent['term_id'];
        }
        $t = wp_insert_term( $term['name'], $tax, $term );

        // -> add meta
        if ( is_wp_error( $t ) ) return false;
        if ( ! empty( $term['meta'] ) ) {
            foreach ( $term['meta'] as $key => $meta ) {
                update_term_meta( $t['term_id'], $key, $meta );
            }
        }
    }
}

Comme vous le constatez, on peut insérer plusieurs identifiants externes : l’ID, l’URL d’origine… tout ce qui pourrait nous servir par la suite.

L’insertion des utilisateurs

La migration des utilisateurs se fait d’une façon relativement similaire à celle des termes, en utilisant la fonction WordPress wp_insert_user(). Par contre, il faut savoir que beaucoup des paramètres de cette fonction ne sont pas enregistrés dans la table des utilisateurs, mais directement dans la table des meta données d’utilisateurs (comme le nom, le prénom, les méthodes de contact, le rôle…).

Il faut aussi prêter une attention particulière au transfert des mots de passe : dans l’ancienne base ils étaient cryptés, cela ne sert donc à rien de transférer le mot de passe dans le champ user_pass, par contre il faut l’importer en tant que meta donnée, cela nous servira plus loin dans cet article…

require '../wp-load.php';

//-> tableau
$users = [
    [
        'user_login' => 'joe', 'user_email' => 'joe@wabeo.fr', 
        'meta' => [ 'old_hashed_password' => '97f2c9b46d3e0a1516e742fff64a242d'],
    ],
    [
        'user_login' => 'jack', 'user_email' => 'jack@wabeo.fr', 
        'meta' => [ 'old_hashed_password' => '406dd62f66c7df1b11ab947b962ee2ff'],
    ],
    [
        'user_login' => 'avrel', 'user_email' => 'avrel@wabeo.fr', 
        'meta' => [ 'old_hashed_password' => 'ab00c7b425f94a002397581420269bc0'],
    ],
];


//-> traitement
array_map( 'willy_insert_users', $users );
function willy_insert_users( $user ) {
    $meta = $user['meta'];
    $user['user_pass'] = '????';
    $user['role'] = 'editor';

    unset( $user['meta'] );
    $id = wp_insert_user( $user );
    if ( ! is_wp_error( $id ) ) {
        foreach ( $metas as $k => $m ) {
            update_user_meta( $id, $k, $m );
        }
    }
}

Importer le contenu

À partir de là, j’imagine que vous avez compris la logique : on va utiliser la fonction wp_insert_post() pour insérer les contenus dans WordPress. Cependant, regardez la petite vidéo :

Il y a un comme un couac : Le contenu est complètement cassé !

  • Les URLs relatives des liens ont forcément été brisées ;
  • les images sont « hotlinkées » (chargées à distance) et aux mauvaises dimensions ;
  • certaines URLS d’images sont aussi cassées…

Avant d’insérer les contenus en provenance d’une source externe, il faut le nettoyer le balisage et insérer les médias tels que les images.

Transmettre les images à WordPress

Déjà, voyons comment transférer les images !

Je le précise, même si c’est évident : il ne sert à rien de copier/coller les fichier dans le FTP à la main. WordPress a besoin d’inscrire les données relatives aux images dans sa base de données ; il pourra ainsi en extraire des informations exifs, et générer des vignettes adaptées à votre thème.

Pour transférer des attachments, WordPress met à disposition les fonctions wp_handle_sideload() et download_url(). Ces fonctions sont normalement disponibles en back-office uniquement, il faut donc charger manuellement les fichiers (ligne 6 à 10).

Dans l’exemple ci-dessous, j’importe des articles, et au passage les images qui seront mises « à la une ».

require '../wp-load.php';

// Importer les fonctions d’admin
if ( ! function_exists( 'media_handle_upload' ) ) {
	require_once( ABSPATH . 'wp-admin/includes/image.php' );
	require_once( ABSPATH . 'wp-admin/includes/file.php' );
	require_once( ABSPATH . 'wp-admin/includes/media.php' );
}

// Notre fonction d’import
function import_image( $url, $post_id ) {

	$tmp = download_url( $url );
	$file_array = array();

	preg_match( '/[^\?]+\.(jpg|jpe|jpeg|gif|png|pdf)/i', $url, $matches );
	$file_array['name'] = basename( $matches[0] );
	$file_array['tmp_name'] = $tmp;

	if ( is_wp_error( $tmp ) ) {
		@unlink( $file_array['tmp_name'] );
		$file_array['tmp_name'] = '';
	}

	$id = media_handle_sideload( $file_array, $post_id, $desc );

	if ( is_wp_error( $id ) ) {
		@unlink( $file_array['tmp_name'] );
		return $id;
	}
	update_post_meta( $id, 'old_url', $url );

	return $id;
}

// Une fonction pour retrouver les fichiers déjà importés
function wp_get_attachment_by_post_meta( $url ) {
	$args = array(
		'post_per_page' => 1,
		'post_type'     => 'attachment',
		'no_found_rows' => 1,
		'meta_query'    => array(
			array(
				'key'   => 'old_url',
				'value' => trim( $url ),
			),
		),
	);
	$get_posts = new WP_Query( $args );
	if ( isset( $get_posts->posts[0] ) ) {
		return $get_posts->posts[0];
	}
	return false;
}

// Nos articles à insérer
$posts = [
	[
		'titre'     => 'titre 1',
		'content'   => 'description',
		'image_une' => 'http://exemple.com/image.jpg',
		'meta'      => [ 'extId' => 1 ],
	],
	[
		'titre'     => 'titre 2',
		'content'   => 'description 2',
		'image_une' => 'http://exemple.com/image.jpg',
		'meta'      => [ 'extId' => 2 ],
	],
];

// La boucle d’insertion
foreach ( $posts as $post ) {
	$post['post_type']   = 'post';
	$post['post_status'] = 'publish';

	$meta = $post['meta'];
	$img  = isset( $post['image_une'] ) ? $post['image_une'] : false;
	unset( $post['meta'] );
	unset( $post['image_une'] );

	$p = wp_insert_post( $post, true );
	if ( is_wp_error( $p ) ) {
		return false;
	}
	if ( $img ) {
		// On regarde si l’image existe, sinon on l’insère
		if ( ! $id_attachment = wp_get_attachment_by_post_meta( $img ) ) {
			$id_attachment = import_image( $img, $img, $p );
		}
		if ( ! is_wp_error( $id_attachment ) ) {
			$meta['_thumbnail_id'] = $id_attachment;
		}
	}
	if ( ! empty( $meta ) ) {
		foreach ( $meta as $k => $m ) {
			update_post_meta( $p, $k, $m );
		}
	}
}

Vous pouvez voir qu’on a créé, ligne 13, une fonction pour l’insertion des images, et ligne 42, une autre fonction qui nous sert à vérifier que le média n’a pas déjà été inséré (pour ne pas uploader de doublons qui chargeraient inutilement le serveur).

Une fois chaque image trouvée/insérée, on pousse son identifiant dans la meta donnée thumbnail_id de l’article importé : c’est comme cela que l’on design une vignette d’article.

Nettoyer le post_content avant l’insertion

Avant d’injecter le contenu d’un article, il est souvent nécessaire de le nettoyer :

  • Afin de supprimer certaines balises ou styles en ligne ;
  • pour remettre en forme correctement le HTML (balise imbriquées ou dépréciées…) ;
  • pour isoler des données, ou changer la nature de certains éléments.

Je vais donc vous présenter quelques outils que vous pouvez utiliser…

La fonction WP_KSES et ses filtres

WP_KSES (dont le nom est l’acronyme récursif de KSES Strips Evil Scripts) est une fonction de WordPress qui filtre, dans un bloc HTML, les balises et les attributs à conserver.

Voici un exemple où cette astuce permet de supprimer les table, attributs target="_blank" et autres éléments non désirés…

require '../wp-load.php';

$content = '<table><tr>
        <td>Zenoque, eorum princeps</td>
        <td>Respondeat totidem verbis</td>
    </tr></table>
	<p>Lorem ipsum dolor sit amet, <i style="font-weight: bold;">
	<span>consectetur adipiscing</i></span> elit.
	<a href="http://loripsum.net/" target="_blank">Ut pulsi recurrant?</a> Duo Reges: constructio</p>
	<blockquote cite="http://loripsum.net">
		Vides igitur te aut ea sumere, quae non concedantur, aut ea, quae etiam concessa te nihil iuvent.
	</blockquote>
	<img src="http://lorempixel.com/400/200/sports/1/">
	<h2><span style="text-decoration:underline;color:red;font-weight:bold;">Hanc 
	quoque iucunditatem, si vis, transfer in animum;</span></h2>';

	//-> Filtre les styles inline
	add_filter( 'safe_style_css', 'adapte_safe_style' );

	//-> Filtre le markup
	$content = wp_kses( $content, array(
		'p' => true, // ok pour p
		'i' => true, // ok pour i, sans attribut
		'a' => array( // ok pour les liens…
			'href' => true, // … mais juste avec href
		),
		'span' => array( // ok pour les span avec style
			'style' => true,
		),
	) );


function adapte_safe_style( $safe ) {
	$safe = array_diff( $safe, [ 'text-decoration', 'font-weight' ] );
	return $safe;
}

La fonction prend en paramètre un tableau des tags autorisés et les attributs possibles pour chacun d’entre-eux (ligne 23 à 32).

Vous l’avez sans doute remarqué, mais grâce au filtre safe_style_css ligne 20, vous pouvez préciser quels attributs CSS sont autorisés dans les styles en ligne. Dans cette exemple, je supprime text-decoration et font-weight.

La librairie HTML Purifier

La fonction précédente est très pratique mais elle requiert que le code HTML soit correctement écrit… et vous savez que ce n’est pas toujours le cas ! Lors d’une migration on peut très bien trouver des balises imbriqueés, dépréciées, avec des fautes de frappe… La librairie HTML Purifier vous permet d’en venir à bout !

Pour l’utiliser, il faut préalablement la télécharger et l’appeler dans votre script PHP (ligne 3).

require '../htmlpurifier-4.8.0/library/HTMLPurifier.auto.php';

$content = '<p>foo <br//>
    <span><i>bar</span></i></p>
    <strike>must not exist';

// -> configuration
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Trusted', true);
$config->set('HTML.Doctype', 'HTML 4.01 Strict');
$purifier = new HTMLPurifier( $config );

// -> exécution
$content = $purifier->purify( $content );
// Content devient :
// <p>foo <br>
// <span><i>bar</i></span></p>
// <span style="text-decoration:line-through">
// must not exist</span>

Ensuite on initialise la librairie, en précisant le doctype, le niveau de nettoyage,  les options diverses… puis on applique la correction (ligne 16).

Les expressions rationnelles (Regex)

Les regex, c’est le couteau suisse en dernier recours… pour tout ce qui ne peut pas être corrigé par les précédents outils.

Brièvement, les expressions rationnelles sont des suites de caractères dont l’agencement (qui dépend d’un standard commun à plusieurs languages), permet d’intercepter des motifs dans une chaîne de caractères. Les éléments reconnus peuvent alors être supprimés ou remplacés.

Par exemple ici, je veux remplacer ce qui est écrit en rouge par des button :

$re = '/<([\D]+)([^>]*)?color[\s]*?:[\s]*?red([^>]*)?>(.*)<\/\1>/i';
$subst = '<button$2$3>$4</button>';

$content = 'Est-ce un bouton : <span style="color: red">foo</span> ?';
$content = preg_replace( $re, $subst, $content );
// Content est maintenant :
// 'Est-ce un bouton : <button style="">foo</button> ?'

La lecture, l’écriture, voir l’apparence des Regex peuvent paraitre repoussants pour les néophytes, et pourtant certains développeurs et développeuses averti/e/s adorent s’y frotter 🙂

Voici quelques outils pratiques au sujet des expressions rationnelles :

  • Regex101.com est un site qui permet de composer des expressions à l’aide d’un dictionnaire et d’annotations.
  • VerbalExpression est une suite de librairies permettant d’écrire des Regex « avec des vrais mots ».

Améliorer le système

Ca y est ! 🎉 Vous pouvez maintenant faire tourner votre propre script de migration !

Je passe sur wp_insert_comment mais le principe est identique au reste et il n’y a pas de subtilité. Vous pouvez maintenant créer un système qui fonctionne et commencer à jouer avec des passerelles de contenus.

Voici un exemple d’exécution de code complet qui m’a permi, lors du WordCamp Bordeaux 2017, d’aspirer le fil de conversation du #wcbordeaux :

… mais on peut encore améliorer quelques points. Premièrement revenons sur la migration des utilisateurs.

Permettre aux utilisateurs importés de se connecter

Vous vous souvenez que l’on a enregistré les utilisateurs en leur assignant un mot de passe bidon ? Migrer le mot de passe tel-quel n’aurait été d’aucune utilité : WordPress et les autres CMS cryptent les mots de passe avant de les enregistrer en base de données. Les décoder n’est pas possible et les techniques d’encryption diffèrent selon les systèmes…

Les tentatives de connexion échoueraient alors lorsque WordPress comparerait deux chaines de caractères qui n’ont pas été encodés de la même manière (et donc différentes).

Heureusement, WordPress dispose du filtre check_password pour greffer un système d’authentification secondaire (en cas d’échec de connexion sur son système natif). C’est pour cela que l’on a enregistré l’ancien mot de passe dans une meta donnée à part : si une tentative de connexion échoue, on peut comparer le mot de passe en utilisant le cryptage de l’ancien CMS.

Pour l’exemple j’ai créé un utilisateur avec la fonction ligne 4, dont l’ancien mot de passe est hashé d’un façon « exotique ».
Ligne 23 j’utilise le hook check_password pour ajouter une méthode de connexion secondaire. J’imite alors le système d’encryption original pour vérifier le mot de passe hashé en base, et celui fourni par l’utilisateur (ligne 34).

Si les chaines sont identiques, je mets à jour le mot de passe en $_POST via la fonction WordPress wp_set_password, et j’autorise la connexion 🙂

// Mon utilisateur
function insert_willy() {

	// Le mot de passe « azerty! » …
	// … est sallé avec les mots « foo » et « bar » …
	// … puis hashé en md5 (pas sécure, juste pour l’exemple ^)
	$pass = md5( 'foo' . 'azerty!' . 'bar' );

	$id = wp_insert_user( array(
		// new random password
		'user_pass'     => wp_generate_password(12),
		'user_login'    => 'will',
		'user_nicename' => 'Willy',
		'user_email'    => 'bonjour@wabeo.fr',
	) );
	if ( ! is_wp_error( $id ) ) {
		update_user_meta( $id, 'old_hashed_password', $pass );
	}
}

// Mon système d’auth secondaire
add_filter( 'check_password', 'willy_check_password', 20, 4 );
function willy_check_password( $check, $password, $hash, $user_id ) {
	// Si la connexion est réussie, où que l’utilisateur …
	// … n’a pas de mot de passe migré …
	// … on quitte
	if ( $check
	  || false === $old_hash = get_user_meta( $user_id, 'old_hashed_password', true ) ) {
		return $check;
	}

	// Si ça correspond
	if ( hash_equals( $old_hash, md5( "foo{$password}bar" ) ) ) {
		// On supprime la meta
		delete_user_meta( $user_id, 'old_hashed_password' );
		// et on met à jour le mot de passe
		wp_set_password( $password, $user_id );
		return true;
	}

	return $check;
}

Avec ce système, les utilisateurs importés n’auront pas besoin de régénérer leurs identifiants pour se connecter, et la base de données se nettoiera toute seule…

Automatiser les redirections 301

Suite au transfert des données de l’ancien vers le nouveau, il est probable que le format d’URL ai été modifié. Il est indispensable de rediriger les personnes accédant aux anciennes URLs vers les contenus qu’il cherchent !

Il y a deux possibilités :

  1. On connait la liste de toutes les anciennes URLs du site, et l’on choisit d’écrire toutes les redirections à la main ;
  2. on a pensé à enregistrer les identifiants externes des contenus importés, et l’on choisit d’écrire un petit script pour rediriger automatiquement les visiteurs.

La première option sera rarement envisageable, surtout si vous avez migré beaucoup de contenus. Je vous propose donc de coder un script. Nous allons écouter le chargement des pages en s’accrochant sur l’action de redirection des templates template_redirect ; si une page appelée est introuvable, c’est probablement qu’elle a été déplacée.

En s’inspirant des formats d’URL sur l’ancien site, on recherche une clé nous permettant d’identifier le contenu à charger. Dans mon cas, l’ID était auparavant dans l’URL (cet identifiant a été sauvegardé lors de l’import), je le recherche donc avec une regex (ligne 8). S’il est présent, je fais une requête de type get_posts pour retrouver le contenu (ligne 23), puis enfin je redirige vers la nouvelle URL grâce à wp_redirect (ligne 32).

add_action( 'template_redirect', 'redirect_old_forum_uri', 1 );
function redirect_old_forum_uri() {
	if ( is_404() ) {
		//Ancienne URL -> ://forum/15-nom-categorie-arbitraire/
		$url = $_SERVER['REQUEST_URI'];
		$pattern = "/^\\/forum\\/(\\d+)[a-z0-9-_]*/i";
		if ( preg_match( $pattern, $url, $matches ) ) {
			$ancien_id = $matches[1];
			$type      = 'forum';
			$metakey   = '_ancien_id_forum';

			// Y-a-t’il un contenu correspondant ?
			$post = get_posts( array( 
			        'posts_per_page'   => 1,
					'suppress_filters' => false,
                    'no_found_rows'    => '1',
 					'post_type'        => $type,
					'meta_query'       => array( 
	                    'relation' => 'AND',
	                    array( 
							'key'     => $metakey,
							'value'   => $ancien_id,
							'compare' => '=',
	                        )
	                    ),
	                )
				);
			if ( ! empty( $post ) ) {
				// Oui : on redirige !
				wp_redirect( get_permalink( $post[0] ), '301' );
				exit();
			}
		}

	}
}

Optimiser le processus d’import

Si vous avez beaucoup de données, faire tourner le script de migration peu prendre beaucoup de temps, et beaucoup de mémoire PHP. C’est lié au fait que le script effectue un nombre conséquent d’opérations pour chaque insertion (lire la source, la parcourir, la nettoyer, importer les images, calculer les différentes miniatures, importer les termes, les utilisateurs, les articles, les meta…).

Par exemple, la migration du forum wpfr.net, à laquelle j’ai participé, à mis plusieurs jours à s’exécuter… (pour transférer plus de 750 000 articles).

Donc à moins que vous n’ayez qu’une infime quantité de données à migrer, je vous propose de découvrir quelques astuces pour optimiser l’opération.

Booster la mémoire php

Un site WordPress ne requiert généralement pas beaucoup de mémoire PHP pour fonctionner (40MB par défaut). C’est une valeur trop faible pour faire tourner notre moulinette… je vous recommande d’augmenter temporairement la mémoire (et pas qu’à moitié) :

define( 'WP_MEMORY_LIMIT', '1G' );
define( 'WP_MAX_MEMORY_LIMIT', '1G' );
wp_memory_limit

Attention, il est probable que la quantité de mémoire maximum autorisée par votre serveur soit inférieure… demandez à votre administrateur système 😉

Exécutez le script en ligne de commande

La durée maximum d’exécution d’un script PHP est limité lorsque celui-ci est lancé via le serveur web. Si vous disposez d’un accès SSH, appeler directement le script en ligne de commande lui permettra d’être exécuté plus longtemps.

php /path/to/migrate-script.php
appel du script en ssh

Faire un import « par lots »

Vous n’êtes pas obligé d’importer vos données d’une traite ! Je sais que ce conseil peu paraitre évident à certains, et pourtant on y pense pas systématiquement. Il est plus simple – et plus sûr – de réaliser de petits imports successifs qu’une seule grosse et longue opération.

Découpez le tableau des contenus à migrer en petit lots d’une dizaine de contenus, de façon à ce que chaque insert ne dure que quelques secondes. Ensuite il faut procéder par itération : à chaque fin d’exécution, rappeler la fonction pour traiter un nouveau lot de données.

En plus de garantir le bon déroulement des opérations, l’import par lot permet d’avoir un feedback sur ce qui se passe. En faisant remonter des infos entre chaque itération, vous pourrez savoir où en est le script, combien de contenu ont été transférés, et combien il en reste à migrer.

Voici par exemple une capture vidéo de l’import par itération d’une grande quantité d’informations dans un système annuaire que j’ai réalisé pour l’un de mes clients. Chaque opération est lancée en ajax, ce qui permet d’avertir l’utilisateur de l’avancement du processus.

Voilà, ce tutoriel sur ma méthode de migration de contenu touche à sa fin. J’espère qu’il vous sera utile !

N’hésitez pas à me faire part de vos avis en commentaire ou me contacter si vous avez besoin d’assistance.

See you soon…

Soyez le premier à commenter !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Publié le 06 avril 2017
par Willy Bahuaud
Catégorie Développement WordPress, Conférences & formations