Jethro Tull - Aqualung photo

Local : importer une base de données depuis le shell

Local possède une version d’Adminer dans les options de chaque site qui est très utile puisqu’elle permet d’accéder rapidement à la base de données, ou d’importer une petite base de données.

Par contre, s’il s’agit d’importer une base qui fait plus de 80 Mo, mieux vaut se tourner vers un outil un peu plus robuste: le shell.

Accéder au shell depuis Local

Et cela tombe bien : Local possède son propre shell, accessible sur simple clic-droit sur le nom du site sur lequel vous souhaitez ouvrir une fenêtre de terminal:

local open site shell new 1280x787

Voici la marche à suivre : Démarrez votre site > sélectionnez le nom du site > clic-droit > Open Site Shell

Importer une base de données dans un site Local

Lorsque j’ai besoin de répliquer un site rapidement, je copie la base données dans le répertoire app du site Local en question.

Pour mon site de test (skyminds-2020 ), le chemin sur ma machine est /Users/matt/Local Sites/skyminds2020/app . C’est dans ce dossier que je place le fichier SQL à importer.

Ensuite, dans le shell Local, il suffit de faire un simple import SQL dans la base qui, par défaut, s’appelle local (et ce, pour tous vos sites Local, bien qu’elles soient toutes indépendantes).

L’utilisateur est root et le mot de passe root également, ce qui est plutôt pratique.

Le shell est par défaut sous app/public donc on remonte d’un cran dans l’arborescence pour pointer vers notre fichier (qui se trouve dans /app ).

Voilà ce que cela nous donne:

mysql -u root -proot local < ../db.sql

Résultat:

mysql: [Warning] Using a password on the command line interface can be insecure.
bash-3.2$Code language: HTTP (http)

L’importation de la base de données SQL ne prend que quelques secondes, alors que cela se plante allégrement lorsque l’on utilise Adminer.

A utiliser sans modération, cela ne vaut vraiment pas le coup de s’embêter avec une interface graphique (ou à placer le fichier dans le répertoire d’adminer comme il l’indique sur la page d’importation).

WordPress: retirer l'option de supprimer les plugins dans l'interface d'administration photo

WordPress: retirer l’option de supprimer les extensions dans l’interface d’administration

C’est assez rare comme demande mais je me suis exécuté : comment faire pour retirer le lien qui permet de supprimer les extensions désactivées dans l’interface d’administration, même si l’on est administrateur ?

Et bien c’est assez simple, il suffit de filtrer le tableau des liens. Voici deux exemples simples pour mettre cela en place.

Retirer le lien de suppression pour toutes les extensions

Voici le code qui vous permet de retirer le lien “Supprimer” qui se trouve en dessous de chaque extension sur la page Extensions:

<?php
/*
Plugin Name: Disable Plugin Deletion
Plugin URI: https://www.skyminds.net/wordpress-retirer-option-supprimer-extensions/
Description: Disable all plugins' deletion links on the plugins page.
Version: 1.0
Author: Matt Biscay
Author URI: https://mattbiscay.com
*/
add_filter( 'plugin_action_links', 'sky_disable_plugin_deletion', 10, 4 );
function sky_disable_plugin_deletion( $actions, $plugin_file, $plugin_data, $context ) {
  // Remove delete link for all installed plugins         
  unset( $actions['delete'] );     
  return $actions;
} Code language: HTML, XML (xml)

Retirer le lien de suppression pour des extensions spécifiques

Voici le code qui vous permet de retirer le lien “Supprimer” qui se trouve en dessous des extensions dont vous spécifiez le chemin (dossier et nom du fichier de l’extension à charger) sur la page Extensions:

<?php
 /*
 Plugin Name: Disable Plugin Deletion
 Plugin URI: https://www.skyminds.net/wordpress-retirer-option-supprimer-extensions/
 Description: Disable all plugins' deletion links on the plugins page.
 Version: 1.0
 Author: Matt Biscay
 Author URI: https://mattbiscay.com
 */
 add_filter( 'plugin_action_links', 'sky_disable_plugin_deletion_selected', 10, 4 );
 function sky_disable_plugin_deletion_selected( $actions, $plugin_file, $plugin_data, $context ) {
   // Remove delete link for specific plugins
   if ( array_key_exists( 'delete', $actions ) && in_array( $plugin_file,
   [
     'akismet/akismet.php',
     'redirection/redirection.php',
   ]
   ) ) {
     unset( $actions['delete'] );
   }
   return $actions;
 }Code language: HTML, XML (xml)

Dans ce dernier snippet, pensez à modifier le nom du répertoire et celui du plugin de manière à ce qu’ils coïncident avec les plugins qui ne doivent pas être supprimés.

A l’origine, c’était pour un de mes clients pour éviter que ses collaborateurs ne suppriment des plugins par inadvertance.

Je m’en sers également sous LocalWP pour éviter de supprimer un plugin en cours de développement – on ne sait jamais !

Réalisation de votre projet web photo

Réalisons votre projet WordPress ou WooCommerce

Nos compétences au service de vos besoins

Stratégie

Nous étudions le parcours de votre site et vos besoins pour vous fournir une solution à long-terme, qui correspond à votre vision.

Développement

Vous souhaitez ajouter de nouvelles fonctionnalités à votre site ou boutique? Nous pouvons coder la solution!

Performance

Votre site doit être rapide et optimisé sur tous supports afin de convertir vos visiteurs. Nous auditons votre site et proposons la solution adaptée.

Hébergement

Vos projets ont besoin d’un hébergement de qualité, c’est la fondation de votre site web et elle doit être solide car c’est sur elle que tout repose.

Maintenance

Votre site a besoin d’une maintenance régulière afin de toujours utiliser la dernière mouture du code et rester sécurisé face aux menaces.

Assistance

Vous avez besoin d’aide ponctuelle? Pas de problème, nous sommes à votre écoute pour vous dépanner rapidement et sereinement.

Lire la suite

Ajouter un lien avec le nombre d'articles et le total du panier WooCommerce photo

Ajouter un lien avec le nombre d’articles et le total du panier WooCommerce

Rares sont désormais les thèmes WordPress qui n’offrent pas le support de WooCommerce tant l’extension qui propulse aujourd’hui des millions de boutiques en ligne est populaire.

Toutefois, le code de WooCommerce évolue en permanence et certaines fonctions changent de nom ou sont appelées différemment.

Un thème compatible avec WooCommerce 2.6 ne l’était plus avec WooCommerce 3.3 par exemple, lorsque toutes les fonctions relatives au panier sont passées à des fonctions objets.

Montrer le nombre d’articles dans le panier dans le thème

Un client Codeable m’a récemment demandé de mettre à jour son thème, qui n’est plus supporté par son auteur, pour afficher le nombre d’articles dans le panier.

Voici le code à insérer, soit dans un des fichiers de votre thème (header.php est le plus indiqué dans notre cas) ou alors dans un widget HTML:

<a class="cart-custom" href="<?php echo wc_get_cart_url(); ?>" title="<?php _e( 'View your shopping cart' ); ?>"><?php echo sprintf ( _n( '%d item', '%d items', WC()->cart->get_cart_contents_count() ), WC()->cart->get_cart_contents_count() ); ?> - <?php echo WC()->cart->get_cart_total(); ?></a>Code language: HTML, XML (xml)

Lire la suite

Redémarrer la machine virtuelle de Local by Flywheel photo

Local : résoudre les problèmes d’importation de la base de données

Aujourd’hui, on importe la base de données d’un site existant dans la nouvelle version de Local Lightning, estampillée 5.4.1.

D’habitude, je lance Adminer > Import, je sélectionne mon fichier de base de données et boom, la base est importée. Mais aujourd’hui, rien ne se passe comme prévu: Adminer mouline, mouline, perd la moitié du design de sa page et n’importe pas la base.

SSH à la rescousse

Je me dis qu’on aura peut-être plus de chance depuis la console SSH. Dans Local, faites un clic droit sur le site > Open Site Shell. Une fenêtre de terminal apparaît alors.

Utilisation de WP-CLI

On commence d’abord par la méthode wp-cli. On copie notre fichier adminer.sql sous /app/public et on lance la commande suivante:

wp db import ./adminer.sqlCode language: JavaScript (javascript)

Résultat:

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)Code language: JavaScript (javascript)

Cela commence bien. La version 5 de Local utilise MySQL 8 donc certaines choses ne sont peut-être pas tout à fait au point. Comme il n’y a pas de socket mysql dans /tmp/mysql.sock, nous allons manuellement spécifier notre socket MySQL dans notre commande. Local nous donne l’adresse du socket dans l’onglet Database:

wp db import ./adminer.sql --socket="/Users/matt/Library/Application Support/Local/run/rvrb6Ce-Z/mysql/mysqld.sock"

Résultat:
ERROR 1067 (42000) at line 23841 in file: './adminer.sql': Invalid default value for 'comment_date'
Code language: JavaScript (javascript)

La bonne nouvelle, c’est que l’on peut se connecter à la base de données MySQL. Mais tiens donc, nous sommes déjà tombés sur cette erreur Invalid default value for comment_date !

Configuration de MySQL

Pour régler le problème sous Local, la marche à suivre diffère un peu de ce que nous avions lancé sur notre serveur puisqu’il n’y a pas une mais deux variables de configuration de mysqld à modifier.

On se connecte au serveur mysql:

mysql -u root -proot

Commençons par voir ce que les variables sql_mode contiennent:

SELECT @@GLOBAL.sql_mode; SELECT @@SESSION.sql_mode; Code language: CSS (css)

Résultat:

+------------------------------------------------------------------------------------------+
| @@GLOBAL.sql_mode                                                                        |
+------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+------------------------------------------------------------------------------------------+
1 row in set (0,00 sec)

+------------------------------------------------------------------------------------------+
| @@SESSION.sql_mode                                                                       |
+------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+------------------------------------------------------------------------------------------+Code language: JavaScript (javascript)

Le problème est, comme la dernière fois, la présence de ces deux instructions: NO_ZERO_IN_DATE,NO_ZERO_DATE.

On enlève donc ces deux instructions de nos deux directives:

SET @@GLOBAL.sql_mode="ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION";
SET @@SESSION.sql_mode="ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION";Code language: CSS (css)

Voilà ce que nous obtenons au final:

+------------------------------------------------------------------------------------------+
| @@GLOBAL.sql_mode                                                                        |
+------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+------------------------------------------------------------------------------------------+
1 row in set (0,00 sec)

+------------------------------------------------------------------------------------------+
| @@SESSION.sql_mode                                                                       |
+------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+------------------------------------------------------------------------------------------+
1 row in set (0,00 sec)Code language: JavaScript (javascript)

On peut désormais importer notre fichier SQL:

mysql -u root -proot local < ./adminer.sql 

Hop l’import de la base de données se passe maintenant sans aucun problème.

Gravity Forms : activer l'anti-spam honeypot sur tous les formulaires photo

Gravity Forms : supprimer les entrées mais garder les fichiers uploadés sur le site

Gravity Forms garde en base de données toutes les entrées des formulaires. Sur un site qui génère énormément de demandes (formulaire de contact, demandes d’informations, formulaire de commande ou pré-commande…).

Cela signifie des milliers d’enregistrements dans la base de données, ce qui n’est pas toujours souhaitable, pour des raisons de stockage et de performance.

Supprimer les entrées des formulaires Gravity Forms

Si vous avez besoin de supprimer les entrées créées par Gravity Forms une fois que le message a été envoyé, vous pouvez utiliser cette fonction:

// Delete all entries from form ID  1.
add_action( 'gform_after_submission_1', 'sky_remove_form_entry' );
function sky_remove_form_entry( $entry ) {
    GFAPI::delete_entry( $entry['id'] );
}Code language: PHP (php)

Dans l’exemple ci-dessus, on ne supprime que les entrées du formulaire qui a l’ID 1. Si vous souhaitez supprimer toutes les entrées de tous les formulaires Gravity Forms d’un site, il suffit d’enlever le _1de la cible de l’action gform_after_submission:

// Delete all entries from all forms.
add_action( 'gform_after_submission', 'sky_remove_form_entry' );
function sky_remove_form_entry( $entry ) {
    GFAPI::delete_entry( $entry['id'] );
}Code language: PHP (php)

Garder les fichiers uploadés lors de la suppression des entrées

Attention: supprimer les entrées Gravity Forms revient également à supprimer les fichiers qui auront été uploadés via les formulaires. C’est tout à fait normal puisqu’ils font partie des champs du formulaire.

Pour garder les fichiers uploadés, même si les entrées associées sont supprimées, il faut utiliser le filtre gform_field_types_delete_files:

add_filter( 'gform_field_types_delete_files', '__return_empty_array' );Code language: JavaScript (javascript)

Supprimer les fichiers associés à un champ upload particulier

Si vous souhaitez supprimer tous les fichiers qui auront été uploadés via un champ upload particulier, il suffit de préciser ce champ dans le tableau $field_types avant de le passer à gform_field_types_delete_files:

add_filter( 'gform_field_types_delete_files', 'sky_delete_custom_upload_field' );
function sky_delete_custom_upload_field( $field_types ) {
    $field_types[] = 'my_custom_upload_field';
    return $field_types;
}Code language: PHP (php)

Dans cet exemple, notre champ upload s’appelle my_custom_upload_field.

Gravity Forms : activer l'anti-spam honeypot sur tous les formulaires photo

Gravity Forms : activer l’anti-spam honeypot sur tous les formulaires

Gravity Forms permet de créer rapidement des formulaires avec des logiques conditionnelles sous WordPress.

Dans les options de Gravity Forms, il existe une option qui ajoute un champ caché au formulaire, “honeypot”, qui permet d’éviter le spam mais qui doit être activé manuellement pour chaque formulaire, ce qui peut être rapidement fastidieux selon le nombre de formulaires que vous avez sur le site.

Voici comment activer et ajouter le champ honeypot à tous vos formulaires, automatiquement:

<?php
/**
 * Enforce anti-spam honeypot on all Gravity forms.
 *
 * @param array $form The current form to be filtered.
 * 
 * @return array
 */
add_filter( 'gform_form_post_get_meta', __NAMESPACE__ . '\\sky_enforce_gravity_forms_anti_spam_honeypot' );
function sky_enforce_gravity_forms_anti_spam_honeypot( $form ): array {
	$form['enableHoneypot'] = true;
	return $form;
}Code language: HTML, XML (xml)

Et voilà, une protection supplémentaire et automatique pour tous vos formulaires !

WordPress : résoudre l'erreur "ftp_nlist() expects parameter 1 to be resource" photo

Lister tous les articles publiés sur un blog WordPress avec wp-cli

wordpress banner 1280x512

Lister les URLs de tous les articles publiés

J’ai récemment eu besoin de lister toutes les URLs des articles du site, pour les promouvoir sur les réseaux sociaux. L’un des services que j’utilise, SocialBee, permet de soumettre une liste de 100 URLs à chaque soumission du formulaire.

Il nous faut donc une liste d’adresse de 100 articles publiés, ce qui est très facile à obtenir grâce à wp-cli. Voici la commande que j’ai écrite:

wp post list --field=url --post_status=publish --allow-root --posts_per_page=100 --paged=1Code language: PHP (php)

Explications:

  • wp est un alias de wp-cli, installé sur le serveur
  • post indique l’on va interroger les articles
  • list: on va lister!
  • --field=url : on veut le champ URL
  • --post_status=publish : les articles publiés uniquement
  • --allow-root : parce que je suis en root
  • --posts_per_page=100: le nombre d’article à récupérer
  • --paged=1 : le numéro de la pagination de la requête

Il vous suffit ensuite d’incrémenter la valeur de --paged pour passer en revue toutes les pages de la requête.

Ou alors retirer totalement les arguments --posts_per_page=100 --paged=1 pour obtenir la liste complète des URLs de tous les articles publiés.

WordPress : résoudre l'erreur "ftp_nlist() expects parameter 1 to be resource" photo

WordPress : résoudre l’erreur “ftp_nlist() expects parameter 1 to be resource, null given”

Sous WordPress 5.3.x et en utilisant wp-cli, on peut obtenir cette erreur lors de la mise à jour de plugins et thèmes:

Warning: ftp_nlist() expects parameter 1 to be resource, null given in /var/www/html/wp-admin/includes/class-wp-filesystem-ftpext.php on line 402

PHP Warning:  ftp_pwd() expects parameter 1 to be resource, null given in /var/www/html/wp-admin/includes/class-wp-filesystem-ftpext.php on line 226Code language: HTTP (http)

Le tout répété cinq à six fois pour la mise à jour d’un plugin. En regardant le ticket trac qui rapporte ce problème, il s’agit d’une erreur qui était auparavant cachée (avec un @ devant la fonction) et qui est maintenant affichée.

Au -delà du fait de cacher ou ne plus cacher l’erreur, il semble qu’il manque une routine qui vérifie que le lien wp_filesystem est bien actif avant de pouvoir l’utiliser.

En attendant que cela soit réglé dans une prochaine version de WordPress, voici ce que l’on peut ajouter au fichier wp-config.php pour se débarrasser de l’erreur proprement:

if ( !defined( 'FS_METHOD' ) ):
    define( 'FS_METHOD', 'direct' );
endif;Code language: PHP (php)

Enregistrez le fichier, problème réglé !

Useful snippets photo

WordPress : trouver tous les articles de moins de 300 mots

Useful snippets photo

On m’a demandé sur Codeable un audit SEO sur un site qui avait plusieurs années d’existence et dont la ligne éditoriale a évolué avec le temps.

Les vieux articles, très courts et peu informatifs, offraient peu de valeur aux visiteurs et devaient donc être listés dans le but de les amender ou de les supprimer.

Le site était sous WordPress donc voici la requête que j’ai utilisée pour dresser la liste de tous les articles qui contiennent moins de 300 mots (on ne compte pas les espaces):

SELECT LENGTH(post_content) - LENGTH(REPLACE(post_content, ' ', ''))+1, post_title, ID
FROM wp_posts WHERE post_type='post' AND post_status='publish' AND ((LENGTH(post_content) - LENGTH(REPLACE(post_content, ' ', ''))+1) < 300);Code language: JavaScript (javascript)

Vous pouvez lancer cette requête SQL sur votre serveur MySQL ou dans un outil comme PHPMyAdmin ou Adminer: cela vous renvoie un tableau de 3 entrées qui contiennent le nombre de mots de l’article, le titre de l’article et son ID.

Au point de vue du SEO, il est recommandé de supprimer les articles zombies qui n’offrent pas de valeur aux visiteurs. Ces pages ne sont généralement pas indexées et n’apparaissent donc pas dans les résultats de recherche.

Mieux vaut consolider le site avec des pages à fort potentiel et avec un contenu conséquent. Ce n’est pas tant le nombre de mots qui compte que la richesse de contenu mais un nombre très faible de mots est un bon indicateur d’un article peu qualifié.

WordPress : résoudre le problème de la table wp_options à qui manquent une colonne Unique et une Primary Key photo

WordPress : résoudre le problème de la table wp_options à qui manquent une colonne Unique et une Primary Key

Chez Codeable, j’ai travaillé sur l’optimisation d’un site e-commerce propulsé par WooCommerce récemment, qui connaissait quelques problèmes de lenteur.

Sous phpMyAdmin, on trouvait également cette erreur:

Current selection does not contain a unique column

Si vous obtenez cette erreur, c’est que la structure de la table wp_options n’est pas à jour donc nous la vérifions avec wp-cli:

wp db query "DESCRIBE $(wp db prefix --allow-root)options" --allow-rootCode language: JavaScript (javascript)

Le résultat obtenu nous montre qu’il n’y a pas de clé primaire (primary key) qui est normalement option_id et qu’il n’y a pas de restriction unique imposée sur la colonne option_name:

+--------------+---------------------+------+-----+---------+-------+
| Field        | Type                | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+-------+
| option_id    | bigint(20) unsigned | NO   |     | NULL    |       |
| option_name  | varchar(191)        | YES  |     | NULL    |       |
| option_value | longtext            | NO   |     | NULL    |       |
| autoload     | varchar(20)         | NO   |     | yes     |       |
+--------------+---------------------+------+-----+---------+-------+Code language: PHP (php)

Et c’est là que le bât blesse – voici à quoi ressemble la structure standard de la table wp-options:

+--------------+---------------------+------+-----+---------+----------------+
| Field        | Type                | Null | Key | Default | Extra          |
+--------------+---------------------+------+-----+---------+----------------+
| option_id    | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| option_name  | varchar(191)        | NO   | UNI | NULL    |                |
| option_value | longtext            | NO   |     | NULL    |                |
| autoload     | varchar(20)         | NO   | MUL | yes     |                |
+--------------+---------------------+------+-----+---------+----------------+Code language: PHP (php)

Ajouter la Primary Key manquante à wp_options

On ajoute à la colonne option_id la clé primaire qui lui manque:

wp db query "ALTER TABLE $(wp db prefix --allow-root)options MODIFY option_id INT AUTO_INCREMENT PRIMARY KEY;" --allow-rootCode language: JavaScript (javascript)

Et on vérifie le résultat:

wp db query "DESCRIBE $(wp db prefix --allow-root)options" --allow-rootCode language: JavaScript (javascript)

Ajouter la contrainte Unique qui manque à wp_options

Pour ajouter la contrainte UNIQUE à la colonne option_name, on lance:

wp db query "ALTER TABLE $(wp db prefix --allow-root)options ADD UNIQUE (option_name);" --allow-rootCode language: JavaScript (javascript)

Là, il est possible que cela bloque, suivant ce qui se trouve dans votre table wp_options.

Résoudre le problème des doublons

Si vous obtenez une erreur comme :

ERROR 1062 (23000) at line 1: Duplicate entry 'jetpack_available_modules' for key 'option_name'Code language: JavaScript (javascript)

alors cela signifie qu’il existe des enregistrements option_name dupliqués, des doublons qui portent le même nom alors que chaque nom option_name devrait être unique.

On peut obtenir la liste des enregistrements option_name doublons avec cette requête:

wp db query "SELECT option_name, COUNT(*) optioncount FROM $(wp db prefix --allow-root)options GROUP BY option_name HAVING optioncount > 1 ORDER BY optioncount DESC;" --allow-rootCode language: JavaScript (javascript)

Par ordre ascendant, voici la liste des doublons:

+---------------------------------------------+-------------+
| option_name                                 | optioncount |
+---------------------------------------------+-------------+
| jetpack_callables_sync_checksum             |       47123 |
| jetpack_sync_full_config                    |          50 |
| jetpack_sync_full_enqueue_status            |          43 |
| jpsq_sync_checkout                          |          10 |
| jetpack_sync_full__params                   |           5 |
| jetpack_sync_settings_sync_via_cron         |           4 |
| jetpack_sync_full__started                  |           4 |
+---------------------------------------------+-------------+

On peut supprimer automatiquement tous les doublons option_name de deux manières différentes, soit en utilisant la plus vieille valeuroption_id(donc la plus petite valeur d’ID), soit en utilisant la valeuroption_id la plus récente (plus grande valeur d’ID).

Garder le doublon option_name le plus ancien

Voici la requête SQL qui montre uniquement le plus ancien enregistrement (MIN) option_id pour chaque doublon de valeur option_name:

SELECT *
FROM wp options
WHERE option_id NOT IN
    (SELECT *
     FROM
       (SELECT MIN(n.option_id)
        FROM wp_options
        GROUP BY n.option_name) x)Code language: CSS (css)

Une fois que vous avez vérifié le résultat, on peut passer à la suppression.

Cette requête SQL garde l’enregistrement (MIN) option_id le plus ancien de tous les doublonsoption_name et supprime tous les enregistrements plus récents que la valeur trouvée:

DELETE
FROM wp options
WHERE option_id NOT IN
    (SELECT *
     FROM
       (SELECT MIN(n.option_id)
        FROM wp_options n
        GROUP BY n.option_name) x)Code language: CSS (css)

Voici l’équivalent wp-cli:

wp db query "DELETE FROM $(wp db prefix --allow-root)options WHERE option_id NOT IN (SELECT * FROM (SELECT MIN(n.option_id) FROM $(wp db prefix --allow-root)options n GROUP BY n.option_name) x)" --allow-rootCode language: JavaScript (javascript)

Garder le doublon option_name le plus récent

Cette requête SQL ne montre que les enregistrements option_id les plus récents (MAX) pour tous les doublons option_name :

SELECT *
FROM wp_options
WHERE option_id NOT IN
    (SELECT *
     FROM
       (SELECT MAX(n.option_id)
        FROM wp_options n
        GROUP BY n.option_name) x)Code language: CSS (css)

Et voici la requête qui permet de garder les enregistrements option_id les plus récents (MAX) pour tous les doublons option_name en supprimant tous les doublons les plus anciens:

DELETE
FROM wp_options
WHERE option_id NOT IN
    (SELECT *
     FROM
       (SELECT MAX(n.option_id)
        FROM wp_options n
        GROUP BY n.option_name) x)Code language: CSS (css)

Voici l’équivalent wp-cli pour garder l’enregistrement option_name le plus récent:

wp db query "DELETE FROM $(wp db prefix --allow-root)options WHERE option_id NOT IN (SELECT * FROM (SELECT MAX(n.option_id) FROM $(wp db prefix --allow-root)options n GROUP BY n.option_name) x)" --allow-rootCode language: JavaScript (javascript)

Vérifier les clés et contraintes de la table wp_options

Ajoutons de nouveau la contrainte UNIQUE sur la colonne option_name :

wp db query "ALTER TABLE $(wp db prefix --allow-root)options ADD UNIQUE (option_name);" --allow-rootCode language: JavaScript (javascript)

Si vous n’obtenez pas d’erreur, vérifiez la table une nouvelle fois pour constater les changements:

wp db query "DESCRIBE $(wp db prefix --allow-root)options;" --allow-rootCode language: JavaScript (javascript)

Kaboom! Votre table wp_options possède maintenant une PRIMARY KEY sur la colonne option_id et la contrainte UNIQUE sur la colonne option_name:

+--------------+---------------------+------+-----+---------+----------------+
| Field        | Type                | Null | Key | Default | Extra          |
+--------------+---------------------+------+-----+---------+----------------+
| option_id    | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| option_name  | varchar(191)        | NO   | UNI |         |                |
| option_value | longtext            | NO   |     | NULL    |                |
| autoload     | varchar(20)         | NO   |     | yes     |                |
+--------------+---------------------+------+-----+---------+----------------+Code language: PHP (php)

Je vous conseille de vérifier la structure de la table de temps à autre, notamment si vous constatez une prise de poids anormale en très peu de temps