Ce guide s’adresse aux propriétaires et aux administrateurs qui veulent déplacer un site Drupal vers WordPress sur un serveur cPanel/WHM sans perdre de contenu, d’URL ni de trafic de recherche. Le déclencheur habituel est la fin de vie de Drupal 7, soit le 5 janvier 2025, date à laquelle le projet Drupal a cessé de publier des correctifs de sécurité pour la version 7. Passer de Drupal 7 à Drupal 10 ou 11 est déjà une reconstruction, parce que les thèmes et les modules sur mesure doivent être réécrits. Beaucoup de propriétaires comparent donc ce coût à celui d’un passage à WordPress. La voie pratique consiste à bâtir WordPress à côté de Drupal, dans le même compte cPanel, et à laisser un importateur lire directement la base de données Drupal. Vous ajoutez ensuite une carte de redirections, puis vous basculez le domaine. À la fin, WordPress servira le domaine, les anciennes adresses Drupal répondront par des redirections 301, et vous aurez un plan clair pour les connexions des utilisateurs.
Ce que vous allez accomplir
- Décider, avec de vrais critères, si la bonne destination est WordPress ou une reconstruction sous Drupal 10/11.
- Inventorier le site Drupal à partir de sa propre base de données : types de contenu, champs, taxonomie, alias de chemin et nombre de fichiers.
- Faire rouler WordPress sur un sous-domaine de préproduction dans le même compte cPanel pendant que Drupal continue de servir les visiteurs.
- Importer le contenu avec FG Drupal to WordPress ou un script maison, et associer les champs Drupal à des champs personnalisés WordPress.
- Garder vos URL existantes comme permaliens, rediriger chaque adresse
/node/<id>, et basculer le domaine avec un plan de retour arrière.
Prérequis
- Un compte cPanel avec accès SSH, ou un accès root à WHM pour en créer un. Les étapes supposent que Drupal se trouve dans
~/public_html. - WP-CLI, l’outil en ligne de commande de WordPress. Vérifiez avec
wp --info. - Le client
mysql, présent sur tout serveur cPanel. Drush, l’outil en ligne de commande de Drupal, est utile mais facultatif ; les sites Drupal 7 vont habituellement avec Drush 8, et les sites Drupal 10/11 avec Drush 12 ou 13. - Une sauvegarde complète prise avant de commencer, fichiers et base de données compris. Notre guide d’inventaire avant migration liste ce qu’il faut noter d’autre.
- Une version actuelle de PHP 8, comme 8.3, pour le sous-domaine WordPress. Un site Drupal 7 peut encore dépendre d’une version plus ancienne de PHP, et le MultiPHP Manager de cPanel permet à chaque domaine d’avoir la sienne.
- Une note sur les versions. Drupal 7 range ses données dans d’autres tables que Drupal 8 et les versions suivantes. Chaque étape montre les deux quand elles diffèrent.
Comment ça fonctionne
Drupal et WordPress rangent les mêmes idées sous des formes différentes. Drupal appelle chaque contenu un nœud (node), et chaque nœud appartient à un type de contenu, comme Article ou Page de base. Les données supplémentaires, comme un sous-titre ou la date d’un événement, vivent dans des champs, et chaque champ a sa propre table : field_data_field_<name> dans Drupal 7, node__field_<name> dans Drupal 8 et plus récent. WordPress garde le contenu dans wp_posts et les valeurs supplémentaires dans wp_postmeta, une rangée par valeur.
Lisez le diagramme rangée par rangée. Les articles Drupal deviennent habituellement des articles WordPress, et les pages de base deviennent des pages. Tout autre type de contenu devient un type de publication personnalisé, c’est-à-dire un type de contenu WordPress que vous déclarez vous-même. Les champs deviennent des métadonnées d’article, et ACF (Advanced Custom Fields, une extension répandue qui ajoute des écrans d’édition de champs à WordPress) donne aux rédacteurs un formulaire pour les remplir. Les vocabulaires de taxonomie Drupal, ses listes nommées de termes, deviennent des catégories, des étiquettes ou des taxonomies personnalisées.
Deux rangées demandent une attention particulière. Les alias de chemin sont les URL lisibles que Drupal affiche au lieu de /node/123. Ils vivent dans la table url_alias sous Drupal 7, et dans path_alias à partir de Drupal 8.8. Si WordPress utilise les mêmes chemins, ni les visiteurs ni les moteurs de recherche ne remarquent quoi que ce soit. Les mots de passe ne suivent pas. Drupal 7 hache les mots de passe avec un procédé SHA-512 étiré dont les valeurs commencent par $S$, et WordPress ne peut pas les vérifier. Drupal 8 à 10.0 utilisent le même format. Drupal 10.1 et les versions suivantes stockent plutôt des hachages bcrypt standard, que les versions récentes de WordPress peuvent accepter ; testez donc un compte importé. Pour les hachages $S$, prévoyez d’envoyer une réinitialisation de mot de passe à chaque utilisateur.
Avant d’importer quoi que ce soit, décidez si WordPress est vraiment la bonne destination.
Étape par étape
-
Trouvez les identifiants de la base de données Drupal. Drupal les garde dans
sites/default/settings.php.grep -n -A 9 '^\$databases' ~/public_html/sites/default/settings.phpRésultat attendu, sur un site Drupal 7 :
251:$databases = array ( 252- 'default' => 253- array ( 254- 'default' => 255- array ( 256- 'database' => 'exmpl_d7', 257- 'username' => 'exmpl_d7', 258- 'password' => 'REDACTED', 259- 'host' => 'localhost', 260- 'port' => '',Cherchez plus bas une ligne
'prefix'. Si elle contient une valeur comme'dr_', chaque nom de table ci-dessous commence par ce préfixe. Aucun résultat veut dire que les identifiants se trouvent dans un fichier inclus ; lancez alorsgrep -rn "'database'" ~/public_html/sites/default/. -
Vérifiez le site et prenez un vidage. Si Drush est installé, confirmez qu’il rejoint le site :
cd ~/public_html && drush statusLes lignes attendues comprennent
Drupal version : 7.98,Database : ConnectedetDrupal bootstrap : Successful. Prenez ensuite le vidage avecmysqldump, qui fonctionne avec toutes les versions :mysqldump -u exmpl_d7 -p exmpl_d7 | gzip > ~/drupal-before-migration.sql.gz ls -lh ~/drupal-before-migration.sql.gzRésultat attendu : une demande de mot de passe, aucune autre sortie, puis une taille de fichier bien au-dessus de zéro. Avec Drush, l’équivalent est
drush sql-dump --gzip --result-file=/home/exmpl/drupal-before-migration.sqlsous Drush 8, oudrush sql:dumpavec les mêmes options sous Drush 9 et plus récent. Les deux affichentDatabase dump saved tosuivi du chemin. -
Inventoriez le modèle de contenu. Comptez les nœuds par type de contenu :
mysql -u exmpl_d7 -p exmpl_d7 -e "SELECT type, COUNT(*) AS nodes FROM node GROUP BY type;"Résultat attendu :
+---------+-------+ | type | nodes | +---------+-------+ | article | 412 | | event | 38 | | page | 27 | +---------+-------+Chaque rangée est un type de contenu qui a besoin d’une destination. Listez les tables de champs, avec
field_data_field%sous Drupal 7 ounode__field%sous Drupal 8 et plus récent :mysql -u exmpl_d7 -p exmpl_d7 -e "SHOW TABLES LIKE 'field_data_field%';"Résultat attendu : une rangée par champ, par exemple
field_data_field_event_dateetfield_data_field_image. Comptez ensuite les alias et les fichiers :mysql -u exmpl_d7 -p exmpl_d7 -e "SELECT COUNT(*) FROM url_alias; SELECT COUNT(*) FROM file_managed;"Sous Drupal 8.8 et plus récent, utilisez
path_aliasà la place deurl_alias. Notez les quatre nombres. Ils forment votre liste de contrôle pour l’étape 7. -
Bâtissez WordPress sur un sous-domaine de préproduction. Dans cPanel, ouvrez Domains et créez
new.example.comavec la racine de documents~/new.example.com. Créez une base de données vide et son utilisateur pour WordPress dans Manage My Databases de cPanel, puis installez WordPress à cet endroit :cd ~/new.example.com wp core download wp config create --dbname=exmpl_wp --dbuser=exmpl_wp --dbpass='REPLACE_ME' wp core install --url=https://new.example.com --title='Example' \ --admin_user=admin --admin_email=you@example.com --skip-email wp option get siteurlRésultat attendu :
Success: WordPress installed successfully., puishttps://new.example.com. Comme les deux sites partagent un même compte, l’importateur rejoint la base Drupal parlocalhost, sans accès distant à configurer. -
Alignez la structure des permaliens sur vos alias Drupal. Regardez d’abord un échantillon d’alias :
mysql -u exmpl_d7 -p exmpl_d7 -e "SELECT source, alias FROM url_alias LIMIT 5;"Sous Drupal 8.8 et plus récent, lancez plutôt
SELECT path, alias FROM path_alias LIMIT 5;. Si les articles ressemblent àblog/spring-update, donnez la même forme à WordPress :wp rewrite structure '/blog/%postname%/' --hardRésultat attendu :
Success: Rewrite structure set.WordPress ajoute une barre oblique finale et redirige de lui-même la forme Drupal sans barre vers elle, avec une 301. Les pages restent à/<slug>/peu importe ce réglage, donc un alias commeabout-uspasse tel quel. Pour l’ensemble des règles que WordPress suit ici, voyez notre guide sur les permaliens brisés après une migration. -
Importez le contenu. La plupart des sites devraient commencer avec l’extension FG Drupal to WordPress de Frédéric Gilles :
wp plugin install fg-drupal-to-wordpress --activateRésultat attendu :
Plugin installed successfully.suivi deSuccess: Installed 1 of 1 plugins.Ouvrez Outils > Importer > Drupal dans l’admin WordPress, entrez les valeurs de la base de données de l’étape 1, préfixe de table compris, et testez la connexion avant d’importer. La version gratuite prend en charge le contenu de base, comme les articles, les pages, la taxonomie et les images. Une édition Premium payante en couvre davantage, comme les utilisateurs et les champs personnalisés. Vérifiez la page de l’extension pour la répartition actuelle, parce qu’elle change d’une version à l’autre.Si le site a des structures inhabituelles, un script maison est l’autre voie. Il lit les tables Drupal et crée chaque publication avec WP-CLI :
wp post create --post_type=event --post_status=publish \ --post_title='Spring open house' --post_name='spring-open-house' \ --post_date='2024-04-02 10:00:00' --porcelainRésultat attendu : le nouvel identifiant seul, par exemple
518. La valeur de--post_nameest le slug, alors tirez-la de la dernière partie de l’alias Drupal. Pour un champ géré par ACF, enregistrez la valeur et la clé compagne d’ACF. Cette clé commence par un trait de soulignement et contient la clé du champ affichée dans l’écran du groupe de champs ACF :wp post meta update 518 event_date '20240402' wp post meta update 518 _event_date 'field_6650a1b2c3d4e'Résultat attendu :
Success: Updated custom field 'event_date'.deux fois, une par clé. Sans la clé compagne, ACF peut afficher une case vide dans l’éditeur même si la valeur existe. -
Comparez les totaux à votre inventaire.
wp post list --post_type=post --format=count wp post list --post_type=page --format=count wp term list category --format=countRésultat attendu : des nombres qui concordent avec l’étape 3, comme
412et27. Un écart veut habituellement dire que des nœuds Drupal non publiés ont été ignorés, ce qui est peut-être voulu. Confirmez-le avant de continuer. -
Déplacez les fichiers et corrigez leurs chemins. Drupal sert les fichiers téléversés depuis
sites/default/files. Si votre importateur ne les a pas copiés, faites-le vous-même et réécrivez les références :mkdir -p ~/new.example.com/wp-content/uploads/drupal rsync -a ~/public_html/sites/default/files/ ~/new.example.com/wp-content/uploads/drupal/ \ --exclude=css --exclude=js --exclude=styles --exclude=php wp search-replace '/sites/default/files/' '/wp-content/uploads/drupal/' --dry-runLes dossiers exclus contiennent des caches générés que Drupal reconstruit et dont WordPress n’a jamais besoin. Résultat attendu de l’essai à blanc : un tableau de colonnes qui se termine par une ligne comme
Success: 684 replacements to be made.Relancez la commande sans--dry-runune fois le total vraisemblable. Utilisez WP-CLI plutôt qu’unREPLACESQL, parce qu’il garde valides les données PHP sérialisées. Les fichiers copiés de cette façon s’affichent sur la page, mais n’apparaissent pas dans la médiathèque. -
Bâtissez la carte de redirections. Les chemins avec alias concordent déjà, donc la carte couvre surtout les adresses brutes
/node/<id>qu’utilisent encore les vieux liens et les favoris. Sous Drupal 7 :mysql -u exmpl_d7 -p exmpl_d7 -N -e "SELECT CONCAT('RedirectMatch 301 ^/', source, '/?\$ https://example.com/', alias, '/') FROM url_alias WHERE source LIKE 'node/%';" > ~/drupal-redirects.conf head -2 ~/drupal-redirects.confSous Drupal 8.8 et plus récent, les colonnes s’appellent
pathetalias, et les deux commencent déjà par une barre oblique ; retirez donc les barres en trop dans leCONCAT. Résultat attendu :RedirectMatch 301 ^/node/12/?$ https://example.com/about-us/ RedirectMatch 301 ^/node/48/?$ https://example.com/blog/spring-update/Ajoutez une ligne à la main pour chaque adresse de taxonomie comme
/taxonomy/term/5. Collez ensuite le bloc dans~/new.example.com/.htaccess, au-dessus de# BEGIN WordPress. Les règles de WordPress se terminent par une règle attrape-tout, donc les lignes placées dessous ne s’exécutent jamais, et tout ce qui se trouve entre ses marqueurs est réécrit quand on enregistre les permaliens. -
Planifiez les réinitialisations de mots de passe. Les utilisateurs importés gardent leur hachage Drupal, que WordPress ne peut pas vérifier. Certains modules complémentaires affirment accepter les hachages Drupal à la première connexion ; testez un vrai compte avant de vous y fier. Sinon, après la bascule :
wp user reset-password $(wp user list --role=subscriber --field=ID)Résultat attendu : une ligne
Reset password forpar utilisateur, puis une ligneSuccess:avec le total. WP-CLI envoie le courriel de réinitialisation par défaut. Prévenez les utilisateurs la veille, pour que le message ne soit pas pris pour de l’hameçonnage. -
Basculez le domaine. Mettez Drupal en mode maintenance, prenez un dernier vidage et relancez l’importateur pour tout ce qui a été publié depuis la première passe. Échangez ensuite les fichiers et le nom d’hôte :
mkdir ~/drupal-archive mv ~/public_html/{*,.htaccess} ~/drupal-archive/ rsync -a ~/new.example.com/ ~/public_html/ cd ~/public_html wp search-replace 'https://new.example.com' 'https://example.com' --all-tables wp option get siteurlRésultat attendu : un nombre de remplacements, puis
https://example.com. Déplacer le contenu plutôt que le dossier conserve le propriétaire et les permissions que cPanel a posés surpublic_html. Le dossier~/drupal-archiveest hors de la racine web, donc il n’est pas public, et y remettre les fichiers constitue votre retour arrière.
Valider que tout fonctionne
Testez un échantillon d’anciennes adresses, en mêlant alias, identifiants de nœud et un fichier :
for p in /about-us /node/48 /blog/spring-update /sites/default/files/logo.png; do
printf '%s %s\n' "$(curl -s -o /dev/null -w '%{http_code}' "https://example.com$p")" "$p"
done
Résultat attendu : 301 pour les trois premières, puisque WordPress ajoute la barre oblique finale ou suit votre carte. Le chemin du fichier renvoie 404, sauf si vous ajoutez RedirectMatch 301 ^/sites/default/files/(.*)$ https://example.com/wp-content/uploads/drupal/$1 au bloc. Ça vaut la peine si des images de votre site apparaissent dans la recherche d’images.
Confirmez qu’aucun contenu ne pointe encore vers des chemins Drupal :
wp db query "SELECT COUNT(*) FROM wp_posts WHERE post_content LIKE '%sites/default/files%';" --skip-column-names
Résultat attendu : 0. Passez ensuite nos tests de fumée après migration pour les formulaires, le courriel et la mise en cache.
Dépannage
- L’importateur n’arrive pas à se connecter à la base Drupal. Utilisez
localhostcomme hôte et le préfixe de table exact desettings.php. Si la connexion échoue encore, ajoutez l’utilisateur de la base WordPress à la base Drupal dans Manage My Databases de cPanel. - Des publications affichent du code brut comme
[[{"fid":"12"...}]]. C’est un jeton d’intégration du module Media de Drupal 7, que WordPress ne sait pas lire. Les sites Drupal 10 et 11 montrent plutôt des balises<drupal-media>. Trouvez les publications touchées avecwp db query "SELECT ID FROM wp_posts WHERE post_content LIKE '%[[{%' OR post_content LIKE '%<drupal-media%';"et remplacez chaque jeton par une image normale. - Les anciennes URL renvoient une 404. Le bloc de redirections est sous
# BEGIN WordPress, ou un slug diffère de son alias. Comparezgrep -n 'BEGIN WordPress' ~/public_html/.htaccessavec la ligne de votre première redirection, puis vérifiez le slug avecwp post list --name=<slug>. - Les pages s’affichent sans style ou des blocs manquent. Les thèmes et les blocs Drupal ne sont pas transférés. Refaites-les dans un thème WordPress ; aucun importateur ne les déplace.
Quand ce n’est pas la bonne approche
Restez sur Drupal et planifiez une reconstruction sous 10 ou 11 quand la valeur du site tient à sa structure. Ça veut dire du contenu très structuré avec des références entre entités, soit des liens entre contenus qui alimentent des listes et des filtres. Ça veut aussi dire des permissions fines réparties entre plusieurs rôles et des flux éditoriaux. La traduction au niveau des entités, où chaque champ de chaque contenu existe en plusieurs langues, fait aussi partie de la liste. WordPress a besoin d’extensions supplémentaires pour les sites multilingues et gère ce cas de façon moins directe. Les modules sur mesure qui portent la logique d’affaires sont la dernière raison, parce que chacun devient une extension à commander de zéro.
Un petit site de quelques dizaines de pages est souvent plus rapide à refaire à la main qu’à importer. Vous aurez quand même besoin de la carte de redirections de l’étape 9. La page de fin de vie de Drupal 7 du projet Drupal présente les options de soutien si vous avez besoin de plus de temps.
Un WordPress qui rapporte vraiment mérite un opérateur — Stack Harbor opère cette pile sous WordPress Entreprise ; réservez une consultation d’infrastructure.