Vous vous êtes sûrement déjà dit qu’il serait génial de développer un plugin qui fonctionne aussi bien avec WooCommerce qu’avec Easy Digital Downloads… mais la réalité, c’est que ces deux plateformes ont des approches complètement différentes ! Plutôt que de créer deux plugins distincts (et donc doubler la maintenance), on va voir comment construire une solution élégante qui s’adapte automatiquement à l’environnement détecté. Au programme : architecture modulaire, gestion unifiée des hooks, et interface d’administration qui s’adapte comme un caméléon.
Architecture modulaire pour un plugin e-commerce universel
Créer un plugin qui fonctionne avec WooCommerce ET Easy Digital Downloads, c’est possible ! Mais attention, il faut bien structurer le code dès le départ. Une architecture modulaire nous permettra de détecter automatiquement quelle plateforme est active et d’adapter notre plugin en conséquence.
Détection automatique de la plateforme active
La première étape consiste à détecter quelle plateforme e-commerce est installée sur le site. On peut facilement vérifier la présence des plugins grâce aux classes WordPress :
class EcommercePlatformDetector {
public static function detect_active_platforms() {
$platforms = [];
// Détection de WooCommerce
if (class_exists('WooCommerce')) {
$platforms[] = 'woocommerce';
}
// Détection d'Easy Digital Downloads
if (class_exists('Easy_Digital_Downloads')) {
$platforms[] = 'edd';
}
return $platforms;
}
public static function get_primary_platform() {
$platforms = self::detect_active_platforms();
// Priorité à WooCommerce si les deux sont actifs
if (in_array('woocommerce', $platforms)) {
return 'woocommerce';
}
return !empty($platforms) ? $platforms[0] : null;
}
}
Cette classe nous permet de savoir exactement avec quoi on travaille. Simple mais efficace !
Structuration des dossiers et fichiers du plugin
Bon, maintenant qu’on sait détecter les plateformes, il faut organiser notre code proprement. Voici la structure que je recommande :
mon-plugin-ecommerce/
├── mon-plugin-ecommerce.php (fichier principal)
├── composer.json
├── includes/
│ ├── class-main-plugin.php
│ ├── abstracts/
│ │ └── abstract-ecommerce-handler.php
│ ├── handlers/
│ │ ├── class-woocommerce-handler.php
│ │ └── class-edd-handler.php
│ └── class-platform-detector.php
├── assets/
│ ├── css/
│ └── js/
└── templates/
├── woocommerce/
└── edd/
Cette organisation nous permet de séparer clairement les responsabilités. Les handlers dans leur dossier, les assets séparés, et même les templates spécifiques à chaque plateforme.
Classe abstraite et pattern Strategy
C’est ici que ça devient intéressant ! On va utiliser une classe abstraite pour définir les méthodes communes, puis implémenter le pattern Strategy avec des classes spécialisées :
abstract class Abstract_Ecommerce_Handler {
abstract public function get_products($args = []);
abstract public function get_cart_contents();
abstract public function add_to_cart($product_id, $quantity = 1);
abstract public function get_checkout_url();
// Méthode commune
public function format_price($amount) {
return number_format($amount, 2, ',', ' ') . ' €';
}
}
class WooCommerce_Handler extends Abstract_Ecommerce_Handler {
public function get_products($args = []) {
$query_args = array_merge([
'post_type' => 'product',
'post_status' => 'publish',
'posts_per_page' => 10
], $args);
return get_posts($query_args);
}
public function get_cart_contents() {
return WC()->cart->get_cart();
}
public function add_to_cart($product_id, $quantity = 1) {
return WC()->cart->add_to_cart($product_id, $quantity);
}
public function get_checkout_url() {
return wc_get_checkout_url();
}
}
Et voici le fichier principal du plugin qui orchestre tout ça :
<?php
/**
* Plugin Name: Mon Plugin E-commerce Universel
* Description: Compatible WooCommerce et Easy Digital Downloads
* Version: 1.0.0
*/
// Sécurité
if (!defined('ABSPATH')) {
exit;
}
// Autoloading avec Composer
require_once __DIR__ . '/vendor/autoload.php';
class Mon_Plugin_Ecommerce {
private $handler;
public function __construct() {
add_action('plugins_loaded', [$this, 'init']);
}
public function init() {
$platform = EcommercePlatformDetector::get_primary_platform();
switch ($platform) {
case 'woocommerce':
$this->handler = new WooCommerce_Handler();
break;
case 'edd':
$this->handler = new EDD_Handler();
break;
default:
add_action('admin_notices', [$this, 'no_platform_notice']);
return;
}
$this->setup_hooks();
}
private function setup_hooks() {
// Vos hooks WordPress ici
}
public function no_platform_notice() {
echo '<div class="notice notice-error"><p>Aucune plateforme e-commerce compatible détectée.</p></div>';
}
}
new Mon_Plugin_Ecommerce();
Pour l’autoloading PSR-4, le composer.json ressemble à ça :
{
"autoload": {
"psr-4": {
"MonPlugin\\Ecommerce\\": "includes/"
}
}
}
Cette architecture nous donne une base solide pour développer un plugin vraiment universel. On peut facilement ajouter d’autres plateformes plus tard !
Hooks et filtres spécifiques aux plateformes e-commerce
Maintenant qu’on a une architecture modulaire, il faut s’attaquer au cœur du problème : les hooks et filtres spécifiques à chaque plateforme. Et croyez-moi, c’est là que ça devient intéressant (et parfois frustrant !).
Comparaison des hooks principaux WooCommerce vs EDD
La première chose qui frappe quand on travaille avec ces deux plateformes, c’est la différence de nomenclature. Voici un tableau comparatif des hooks essentiels :
| Action | WooCommerce | Easy Digital Downloads |
|---|---|---|
| Ajout au panier | woocommerce_add_to_cart | edd_add_to_cart |
| Validation commande | woocommerce_checkout_process | edd_pre_process_purchase |
| Affichage prix | woocommerce_get_price_html | edd_purchase_link_args |
| Gestion stock | woocommerce_product_set_stock | edd_decrease_earnings |
| Après paiement | woocommerce_payment_complete | edd_complete_purchase |
Comme vous pouvez le voir, chaque plateforme a sa propre logique de nommage. WooCommerce utilise son préfixe partout, tandis qu’EDD garde une approche plus directe.
Classe HooksManager unifiée
Pour gérer cette complexité, créons une classe qui centralise l’enregistrement des hooks selon la plateforme détectée :
<?php
class HooksManager {
private $platform;
private $hooks_map;
public function __construct($platform) {
$this->platform = $platform;
$this->init_hooks_map();
}
private function init_hooks_map() {
$this->hooks_map = array(
'add_to_cart' => array(
'woocommerce' => 'woocommerce_add_to_cart',
'edd' => 'edd_add_to_cart'
),
'checkout_process' => array(
'woocommerce' => 'woocommerce_checkout_process',
'edd' => 'edd_pre_process_purchase'
),
'payment_complete' => array(
'woocommerce' => 'woocommerce_payment_complete',
'edd' => 'edd_complete_purchase'
)
);
}
public function add_hook($hook_type, $callback, $priority = 10, $args = 1) {
if (!isset($this->hooks_map[$hook_type][$this->platform])) {
return false;
}
$actual_hook = $this->hooks_map[$hook_type][$this->platform];
add_action($actual_hook, $callback, $priority, $args);
return true;
}
public function add_filter($hook_type, $callback, $priority = 10, $args = 1) {
if (!isset($this->hooks_map[$hook_type][$this->platform])) {
return false;
}
$actual_hook = $this->hooks_map[$hook_type][$this->platform];
add_filter($actual_hook, $callback, $priority, $args);
return true;
}
}
Couche d’abstraction unifiée
L’idée, c’est de créer une interface commune qui masque les différences entre plateformes. Voici comment on peut l’utiliser :
<?php
class ECommerceAbstraction {
private $hooks_manager;
public function __construct($platform) {
$this->hooks_manager = new HooksManager($platform);
$this->register_unified_hooks();
}
private function register_unified_hooks() {
// Hook unifié pour l'ajout au panier
$this->hooks_manager->add_hook('add_to_cart', array($this, 'handle_cart_addition'));
// Hook unifié pour le processus de commande
$this->hooks_manager->add_hook('checkout_process', array($this, 'handle_checkout'));
}
public function handle_cart_addition($cart_item_key = null, $product_id = null) {
// Logique unifiée qui fonctionne pour les deux plateformes
error_log('Produit ajouté au panier : ' . $product_id);
// Ici on peut ajouter notre logique métier
do_action('my_plugin_product_added', $product_id);
}
public function handle_checkout() {
// Validation commune
$this->validate_custom_fields();
// Logique spécifique si nécessaire
if ($this->hooks_manager->get_platform() === 'woocommerce') {
// Traitement spécifique WooCommerce
}
}
}
Fonctions callback unifiées
Voici des exemples concrets de fonctions qui marchent pour les deux plateformes :
<?php
// Fonction pour logger les ventes
function log_sale_data($order_id) {
$order_data = array();
if (class_exists('WooCommerce')) {
$order = wc_get_order($order_id);
$order_data = array(
'total' => $order->get_total(),
'customer_email' => $order->get_billing_email(),
'items' => $order->get_item_count()
);
} elseif (function_exists('edd_get_payment')) {
$payment = edd_get_payment($order_id);
$order_data = array(
'total' => $payment->total,
'customer_email' => $payment->email,
'items' => count($payment->downloads)
);
}
// Envoi vers votre système de tracking
wp_remote_post('https://votre-api.com/track', array(
'body' => json_encode($order_data)
));
}
// Fonction de validation personnalisée
function validate_purchase_requirements() {
$current_user = wp_get_current_user();
if (!$current_user->exists()) {
if (class_exists('WooCommerce')) {
wc_add_notice('Connexion requise pour finaliser l\'achat', 'error');
} else {
edd_set_error('login_required', 'Connexion requise');
}
return false;
}
return true;
}
Bon, je dois avouer que cette approche demande un peu plus de code au début, mais elle vous évitera des heures de debugging plus tard. Croyez-moi, j’ai déjà passé des nuits entières à cause de hooks mal gérés !
Interface d’administration adaptative et exemple concret
Maintenant qu’on a posé les bases architecturales, passons à la partie visible de l’iceberg : l’interface d’administration. C’est là que votre plugin va vraiment briller (ou pas) ! L’objectif ici, c’est de créer une expérience utilisateur cohérente, peu importe que l’utilisateur soit sur WooCommerce ou EDD.
Création du menu admin unifié
Bon, commençons par créer un menu d’administration qui s’adapte automatiquement selon la plateforme active. La clé, c’est d’utiliser intelligemment wp_add_menu_page() et wp_add_submenu_page() :
class UnifiedAdmin {
private $platform_detector;
private $capability = 'manage_options';
public function __construct(PlatformDetector $detector) {
$this->platform_detector = $detector;
add_action('admin_menu', [$this, 'add_admin_menu']);
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
}
public function add_admin_menu() {
$platform = $this->platform_detector->get_active_platform();
$menu_title = $platform === 'woocommerce' ? 'WC Reviews Manager' : 'EDD Reviews Manager';
wp_add_menu_page(
'Reviews Manager',
$menu_title,
$this->capability,
'reviews-manager',
[$this, 'main_page'],
'dashicons-star-filled',
26
);
// Sous-menus adaptatifs
wp_add_submenu_page(
'reviews-manager',
'Modération',
'Modérer les avis',
$this->capability,
'reviews-moderation',
[$this, 'moderation_page']
);
wp_add_submenu_page(
'reviews-manager',
'Configuration',
'Paramètres',
$this->capability,
'reviews-settings',
[$this, 'settings_page']
);
}
public function enqueue_admin_scripts($hook) {
if (strpos($hook, 'reviews-manager') === false) {
return;
}
wp_enqueue_script(
'reviews-admin-js',
plugin_dir_url(__FILE__) . 'assets/admin.js',
['jquery'],
'1.0.0',
true
);
wp_localize_script('reviews-admin-js', 'reviewsAjax', [
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('reviews_admin_nonce'),
'platform' => $this->platform_detector->get_active_platform()
]);
}
}
L’idée ici, c’est de créer une interface qui reste familière selon la plateforme. Si c’est WooCommerce, on adapte le vocabulaire et les icônes pour coller à l’écosystème WC.
Système de reviews produits cross-platform
Maintenant, le plat de résistance : un système de reviews qui fonctionne sur les deux plateformes. D’abord, créons la structure de base de données :
class ReviewsDatabase {
private $table_name;
public function __construct() {
global $wpdb;
$this->table_name = $wpdb->prefix . 'unified_product_reviews';
}
public function create_table() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE {$this->table_name} (
id mediumint(9) NOT NULL AUTO_INCREMENT,
product_id mediumint(9) NOT NULL,
platform varchar(20) NOT NULL,
user_id mediumint(9) NOT NULL,
rating tinyint(1) NOT NULL,
title varchar(255) NOT NULL,
content text NOT NULL,
status varchar(20) DEFAULT 'pending',
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY product_platform (product_id, platform),
KEY status (status)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
public function insert_review($data) {
global $wpdb;
return $wpdb->insert(
$this->table_name,
[
'product_id' => sanitize_text_field($data['product_id']),
'platform' => sanitize_text_field($data['platform']),
'user_id' => get_current_user_id(),
'rating' => intval($data['rating']),
'title' => sanitize_text_field($data['title']),
'content' => sanitize_textarea_field($data['content']),
'status' => 'pending'
],
['%d', '%s', '%d', '%d', '%s', '%s', '%s']
);
}
}
Ensuite, le formulaire de soumission avec AJAX :
jQuery(document).ready(function($) {
$('#submit-review-form').on('submit', function(e) {
e.preventDefault();
var formData = {
action: 'submit_product_review',
product_id: $('#product_id').val(),
rating: $('input[name="rating"]:checked').val(),
title: sanitizeInput($('#review_title').val()),
content: sanitizeInput($('#review_content').val()),
nonce: reviewsAjax.nonce
};
$.ajax({
url: reviewsAjax.ajaxurl,
type: 'POST',
data: formData,
beforeSend: function() {
$('#submit-btn').prop('disabled', true).text('Envoi en cours...');
},
success: function(response) {
if (response.success) {
showMessage('Avis soumis avec succès !', 'success');
$('#submit-review-form')[0].reset();
} else {
showMessage(response.data, 'error');
}
},
error: function() {
showMessage('Erreur de connexion', 'error');
},
complete: function() {
$('#submit-btn').prop('disabled', false).text('Soumettre');
}
});
});
function sanitizeInput(input) {
return input.replace(/<script[^>]*>.*?<\/script>/gi, '');
}
});
Côté PHP, le handler AJAX avec toute la sécurité nécessaire :
public function handle_ajax_review_submission() {
if (!wp_verify_nonce($_POST['nonce'], 'reviews_admin_nonce')) {
wp_die('Sécurité compromise');
}
if (!is_user_logged_in()) {
wp_send_json_error('Vous devez être connecté');
return;
}
$data = [
'product_id' => intval($_POST['product_id']),
'platform' => $this->platform_detector->get_active_platform(),
'rating' => min(5, max(1, intval($_POST['rating']))),
'title' => sanitize_text_field($_POST['title']),
'content' => sanitize_textarea_field($_POST['content'])
];
if (empty($data['title']) || empty($data['content'])) {
wp_send_json_error('Tous les champs sont requis');
return;
}
$result = $this->db->insert_review($data);
if ($result) {
wp_send_json_success('Avis ajouté avec succès');
} else {
wp_send_json_error('Erreur lors de l\'enregistrement');
}
}
Tests de compatibilité et bonnes pratiques
Bon, maintenant parlons de ce qui fâche : les tests ! C’est pas le plus fun, mais c’est crucial pour éviter que votre plugin transforme le site de vos utilisateurs en champ de bataille.
D’abord, vérifiez toujours les versions minimum :
class CompatibilityChecker {
private $min_wp_version = '5.0';
private $min_wc_version = '4.0';
private $min_edd_version = '2.9';
public function check_requirements() {
$errors = [];
if (version_compare(get_bloginfo('version'), $this->min_wp_version, '<')) {
$errors[] = sprintf('WordPress %s ou supérieur requis.', $this->min_wp_version);
}
if (class_exists('WooCommerce')) {
if (version_compare(WC()->version, $this->min_wc_version, '<')) {
$errors[] = sprintf('WooCommerce %s ou supérieur requis.', $this->min_wc_version);
}
}
if (class_exists('Easy_Digital_Downloads')) {
if (version_compare(EDD_VERSION, $this->min_edd_version, '<')) {
$errors[] = sprintf('Easy Digital Downloads %s ou supérieur requis.', $this->min_edd_version);
}
}
return $errors;
}
public function deactivate_if_incompatible() {
$errors = $this->check_requirements();
if (!empty($errors)) {
deactivate_plugins(plugin_basename(__FILE__));
wp_die(
'<h1>Plugin incompatible</h1><p>' . implode('<br>', $errors) . '</p>',
'Erreur d\'activation',
['back_link' => true]
);
}
}
}
Et pour finir, voici votre checklist pour soumettre le plugin sur le repository WordPress :
- ✅ Code respectant les standards WordPress (PHPCS avec WordPress-Coding-Standards)
- ✅ Sanitisation systématique avec
sanitize_text_field(),wp_verify_nonce() - ✅ Traductions préparées avec
__()et_e() - ✅ Tests sur plusieurs versions de WordPress, WooCommerce et EDD
- ✅ Documentation README.txt complète avec FAQ
- ✅ Gestion propre de l’activation/désactivation (hooks register_activation_hook)
- ✅ Vérification des capabilities utilisateur
- ✅ Pas de code malveillant (logique !)
- ✅ Licence GPL compatible
Attention cependant : le processus de review peut prendre plusieurs semaines. Soyez patients et réactifs aux feedbacks de l’équipe WordPress !
