Conflits d'extensions WordPress et admin WooCommerce lent : isoler les extensions pendant l'édition en masse
Un conflit d'extension WooCommerce s'annonce rarement de lui-même. Sur une boutique avec soixante extensions actives, le symptôme est un admin WooCommerce lent et une opération en masse qui se comporte différemment que sur une installation propre : elle traîne, s'arrête parfois à mi-chemin, écrit occasionnellement une partie des produits et signale une erreur sur le reste. L'éditeur en masse est rarement en cause. Chaque extension du site accroche ses propres callbacks à save_post et woocommerce_update_product, et quand BEAR enregistre trois mille produits, tous ces callbacks s'exécutent trois mille fois chacun. Un constructeur de page qui régénère son cache, une extension SEO qui recalcule des scores, une extension de recherche qui réindexe, une extension de synchronisation qui appelle une API externe : aucune n'a été écrite en pensant à une écriture en masse.
Trop d'extensions sur un site WordPress n'est pas un problème que l'on résout en les désactivant, parce qu'on en a besoin le reste de la journée. C'est un problème que l'on résout requête par requête.
WordPress a un seul endroit où cela peut être contrôlé, et il est plus tôt que ce à quoi la plupart des gens s'attendent.
Pourquoi un conflit d'extension WordPress ne peut pas se corriger depuis functions.php
Le conseil habituel face à un conflit d'extension WordPress est de désactiver les extensions une par une jusqu'à ce que le symptôme disparaisse. Cela trouve le coupable et vous laisse un choix que vous ne voulez pas faire, parce que l'extension que vous devez garder désactivée est aussi une extension dont la boutique a besoin. WordPress charge les extensions avant de charger le thème. Au moment où functions.php s'exécute, chaque fichier d'extension a déjà été inclus et chaque hook est déjà enregistré. Retirer des callbacks à ce stade signifie les traquer un par un avec remove_action, et vous en manquerez toujours certains : les closures ne peuvent pas être retirées par leur nom, les priorités diffèrent, et une extension qui enregistre ses hooks tardivement les rajoutera après que vous avez terminé. Le résultat est une extension à moitié désactivée, ce qui est pire qu'une extension pleinement chargée.
Il existe un hook qui s'exécute avant le chargement des extensions. WordPress lit la liste des extensions actives dans l'option active_plugins et inclut chaque fichier de cette liste. L'option passe d'abord par le filtre option_active_plugins. Retournez-y un tableau plus court et les autres fichiers d'extension ne sont jamais lus du tout. Pas désactivés, pas réduits au silence : jamais chargés.
Le code qui doit s'exécuter avant le chargement des extensions ne peut pas lui-même résider dans une extension. Il vit dans wp-content/mu-plugins/. Les extensions must-use de WordPress sont chargées avant tout ce qui figure dans la liste normale des extensions, elles ne peuvent pas être désactivées depuis l'admin, et chaque fichier PHP placé directement dans ce dossier s'exécute automatiquement. C'est assez tôt pour que le filtre ci-dessus compte encore.
Les extensions must-use de WordPress se chargent en premier
Enregistrez ceci sous wp-content/mu-plugins/bear-isolation.php. Si le dossier mu-plugins de WordPress n'existe pas encore sur votre site, créez-le : c'est un dossier ordinaire à l'intérieur de wp-content, et chaque fichier PHP qu'il contient se charge automatiquement. Il n'y a rien à activer ensuite.
<?php
/**
* Plugin Name: BEAR Plugin Isolation
* Description: Loads a reduced set of plugins during BEAR bulk editor and MCP requests.
* Version: 1.0
*
* Place this file into wp-content/mu-plugins/
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Mode:
* 'disable' - load everything EXCEPT the plugins listed below. Safer, start here.
* 'keep_only' - load ONLY the plugins listed below. Fastest, needs testing.
*/
define( 'BEAR_ISOLATION_MODE', 'disable' );
/**
* The list the mode above applies to. Paths are exactly as they appear
* in the active_plugins option: folder/file.php
*/
function bear_isolation_list() {
$list = array(
// 'elementor/elementor.php',
// 'js_composer/js_composer.php',
// 'contact-form-7/wp-contact-form-7.php',
// 'wp-smushit/wp-smush.php',
);
return apply_filters( 'bear_isolation_list', $list );
}
/**
* Plugins that stay loaded whatever the mode and the list say.
* Without WooCommerce and BEAR itself the request cannot work at all.
*/
function bear_isolation_always_keep() {
$keep = array(
'woocommerce/woocommerce.php',
'woo-bulk-editor/index.php', // same folder for the free and the paid build
);
return apply_filters( 'bear_isolation_always_keep', $keep );
}
/**
* Is this a request the isolation should apply to?
*
* REST routing has not run yet at this point, so the raw URI is the only
* signal available for the MCP endpoint.
*/
function bear_isolation_is_target_request() {
$uri = isset( $_SERVER['REQUEST_URI'] ) ? (string) $_SERVER['REQUEST_URI'] : '';
// The MCP endpoint: pretty permalinks and the plain query form.
if ( strpos( $uri, '/woobe/v1/mcp' ) !== false
|| strpos( $uri, 'rest_route=/woobe/v1/mcp' ) !== false ) {
return true;
}
// The bulk editor admin page.
if ( isset( $_GET['page'] ) && strpos( (string) $_GET['page'], 'woobe' ) === 0 ) {
return true;
}
// The editor's own ajax calls: woobe_get_products, woobe_bulk_edit and the rest.
if ( isset( $_REQUEST['action'] ) && strpos( (string) $_REQUEST['action'], 'woobe' ) === 0 ) {
return true;
}
return false;
}
/**
* Regular single-site activation list: values are plugin paths.
*/
function bear_isolation_filter_plugins( $plugins ) {
if ( ! is_array( $plugins ) || ! bear_isolation_is_target_request() ) {
return $plugins;
}
$keep = bear_isolation_always_keep();
$list = bear_isolation_list();
if ( BEAR_ISOLATION_MODE === 'keep_only' ) {
$allowed = array_merge( $keep, $list );
return array_values( array_intersect( $plugins, $allowed ) );
}
// 'disable': drop the listed ones, but never the ones that must stay.
$drop = array_diff( $list, $keep );
return array_values( array_diff( $plugins, $drop ) );
}
add_filter( 'option_active_plugins', 'bear_isolation_filter_plugins', 1 );
/**
* Multisite: network-activated plugins live in another option, and there the
* plugin path is the KEY, not the value. Hence the _key variants.
*/
function bear_isolation_filter_network_plugins( $plugins ) {
if ( ! is_array( $plugins ) || ! bear_isolation_is_target_request() ) {
return $plugins;
}
$keep = bear_isolation_always_keep();
$list = bear_isolation_list();
if ( BEAR_ISOLATION_MODE === 'keep_only' ) {
$allowed = array_flip( array_merge( $keep, $list ) );
return array_intersect_key( $plugins, $allowed );
}
$drop = array_flip( array_diff( $list, $keep ) );
return array_diff_key( $plugins, $drop );
}
add_filter( 'site_option_active_sitewide_plugins', 'bear_isolation_filter_network_plugins', 1 );
Les deux modes
disable est celui par lequel commencer. Tout se charge comme d'habitude sauf les extensions que vous nommez. Ajoutez les plus lourdes qui n'ont rien à faire pendant une écriture de produits : constructeurs de page, sliders, galeries, extensions de formulaires, analytics, widgets de chat, optimiseurs d'images, extensions de sauvegarde. Chacune que vous ajoutez, c'est un jeu de callbacks en moins par produit.
keep_only est l'inverse et le plus rapide des deux : rien ne se charge sauf WooCommerce, BEAR et ce que vous listez. Sur un site avec soixante extensions, cela transforme une écriture en masse en quelque chose proche d'une installation propre. Cela retire aussi des extensions dont vous pourriez réellement avoir besoin pendant l'écriture, donc traitez la liste comme quelque chose que l'on construit en testant, pas en devinant.
Les deux modes protègent WooCommerce et BEAR via bear_isolation_always_keep(), donc une faute de frappe dans la liste ne peut pas casser la requête. Le dossier de l'extension est woo-bulk-editor pour la version gratuite comme pour la version payante, donc le chemin ci-dessus est le même quelle que soit celle que vous utilisez. Si vous n'êtes pas sûr du chemin d'une autre extension, il est visible dans Extensions dans le wp-admin, ou dans l'option active_plugins.
Ce que l'isolation couvre
Trois types de requête, et vous pouvez les restreindre en modifiant bear_isolation_is_target_request() :
- Le point de terminaison MCP,
/wp-json/woobe/v1/mcpet sa forme?rest_route=. C'est là qu'un assistant IA écrit des produits, et là qu'un conflit d'extension est le plus difficile à remarquer, parce que personne ne regarde l'écran. - La page de l'éditeur en masse elle-même,
edit.php?post_type=product&page=woobe. C'est celle dont le chargement devient visiblement plus rapide : l'admin n'importe plus chaque metabox et chaque script d'admin de chaque extension. - Les appels ajax de l'éditeur, chaque action commençant par
woobe. C'est là que l'écriture proprement dite a lieu, donc c'est là que les callbacks se seraient déclenchés.
Si vous voulez l'isolation uniquement pour MCP et pas pour la page de l'éditeur, supprimez la deuxième et la troisième vérification. Le reste du fichier continue de fonctionner.
Ce que vous perdez, et c'est le point sur lequel réfléchir
Une extension qui n'est pas chargée ne réagit pas à l'enregistrement d'un produit. La plupart du temps, c'est exactement le but recherché. Parfois, c'est un problème, et il vaut mieux savoir lequel avant de lancer une opération sur tout le catalogue.
Recherche et synchronisation. Relevanssi, ElasticPress, Algolia, générateurs de flux, connecteurs ERP : ils mettent à jour leur index ou envoient des données lors de save_post. Isolez-les et trois mille produits changent dans la base de données pendant que l'index décrit encore les anciens. Reconstruisez l'index ensuite, ou laissez ces extensions chargées.
Multilingue. WPML et Polylang créent et lient des traductions lors de l'enregistrement d'un produit. Sans eux, l'écriture n'atterrit que dans la langue source et les liens entre traductions peuvent dériver.
Champs définis par d'autres extensions. ACF, Subscriptions, Bookings, metaboxes personnalisées : si l'extension n'est pas chargée, ses champs ne sont pas enregistrés, et BEAR ne les affiche pas comme colonnes. L'opération ne corrompra rien, mais un champ que vous comptiez modifier ne sera pas là.
Caches. Les extensions de cache invalident les pages à l'enregistrement. Isolées, elles ne le feront pas, et la boutique continue de servir les anciens prix jusqu'à ce que le cache expire ou que vous le purgiez manuellement.
Extensions de sécurité. Une extension pare-feu dans la liste disable est un pare-feu qui ne tourne pas pour cette requête. Le point de terminaison MCP a son propre jeton et, avec la connexion à deux facteurs activée, sa propre seconde vérification, mais c'est une décision à prendre délibérément et non par accident.
Deux choses que cette méthode ne peut pas désactiver du tout : les autres mu-plugins, et le thème actif. Tous deux se chargent en dehors de active_plugins. Si le functions.php de votre thème porte le code le plus lourd du site, l'isolation n'aidera pas sur cette partie.
La tester avant de lui faire confiance
Travaillez d'abord sur une copie de préproduction, avec un petit filtre plutôt que tout le catalogue.
- Installez le fichier avec une liste vide et vérifiez que rien ne change. Le site doit se comporter exactement comme avant.
- Ajoutez une extension à la liste, ouvrez la page de l'éditeur en masse et vérifiez que les colonnes que vous utilisez habituellement sont toujours là.
- Lancez une opération en masse sur dix produits et comparez le résultat à ce que vous attendez : prix écrits, stock écrit, la boutique affichant les nouvelles valeurs après une purge de cache.
- Ajoutez l'extension suivante et recommencez. Quand quelque chose disparaît ou cesse de se mettre à jour, la dernière extension ajoutée en est la raison.
Pour retirer entièrement l'isolation, supprimez le fichier. Il n'y a rien à désactiver et rien qui reste dans la base de données.
Quand cela en vaut la peine : un admin WooCommerce lent, et quand ce n'est pas le cas
Sur une boutique avec une douzaine d'extensions, non. WordPress gère cela sans problème et le gain ne vaut pas le fichier. Cela devient utile là où la liste d'extensions s'allonge, là où une opération en masse sur des milliers de produits expire ou se termine partiellement, ou là où un assistant IA écrit via le serveur MCP et que personne ne regarde l'écran pour remarquer qu'une extension de synchronisation a déclenché huit mille appels d'API en arrière-plan.
C'est aussi la réponse honnête à « comment accélérer l'admin WooCommerce » quand la vraie raison est le nombre d'extensions plutôt que le serveur : au lieu de chercher l'unique extension coupable, vous décidez lesquelles ont réellement leur place chargées pendant que des produits sont écrits.