Si vous avez déjà passé 30 minutes à « bricoler » un shortcode pour finir par réaliser qu’il est impossible à styler proprement dans le Visual Builder, vous avez déjà ressenti le besoin d’un vrai module Divi 5.
Le problème / Le besoin
Un besoin classique côté blogueurs et équipes éditoriales : afficher un encart réutilisable (ex. “Note de l’auteur”, “Avertissement”, “Checklist”, “CTA newsletter”) avec des options simples (titre, texte, icône, couleur, lien), tout en gardant :
- une édition visuelle dans Divi 5 (pas un champ “shortcode” opaque),
- des styles cohérents avec votre design system,
- un rendu performant (pas de JS inutile),
- un code maintenable (pas de snippets dispersés).
À la fin, vous saurez créer un plugin WordPress qui enregistre un module custom Divi 5 via l’API Module, ajoute des champs, génère le HTML côté serveur (PHP) et charge un CSS propre. Vous aurez aussi une méthode de test et une checklist de dépannage réaliste.
Résumé rapide
- On crée un plugin “muet” côté UI WordPress, qui ne fait qu’enregistrer un module Divi 5.
- On définit les champs du module (titre, contenu, style, lien) et leurs valeurs par défaut.
- On rend le module en PHP avec échappement/sanitation adaptés (WordPress 6.9.4 + PHP 8.1+).
- On charge un CSS minimal via
wp_enqueue_scripts(et on évite les enqueues globaux inutiles). - On prévoit des garde-fous : vérification que Divi 5 est actif, compatibilité cache, erreurs fréquentes.
Quand utiliser cette solution
- Vous avez un site sous Divi 5 et vous voulez un composant réutilisable “propre” (module) plutôt qu’un shortcode.
- Vous devez donner des options éditoriales à une équipe (couleurs, variantes, lien) sans leur laisser casser la mise en page.
- Vous avez plusieurs sites Divi et vous voulez packager un module dans un plugin versionné (Git), réutilisable.
- Vous voulez un rendu stable côté front (PHP) et pas une logique fragmentée entre shortcodes, widgets et CSS inline.
Quand ne PAS utiliser cette solution
- Vous n’êtes pas sur Divi 5 (ou vous migrez encore depuis Divi 4) : commencez par stabiliser le builder. Un module Divi 5 n’aidera pas un site qui reste sur une stack hybride.
- Votre besoin est un simple bloc de contenu statique : une Bibliothèque Divi (layout enregistré) suffit souvent.
- Vous avez besoin d’un composant universel (Gutenberg, Elementor, Avada, Divi) : privilégiez un bloc Gutenberg + styles, puis ajoutez des intégrations spécifiques si nécessaire.
- Vous devez afficher des données très dynamiques (API externe, recherche, filtres) : un module peut convenir, mais il faudra penser cache, timeouts, et éventuellement un endpoint REST. Ne partez pas “module” si votre problème est surtout “données”.
Prérequis / avant de commencer
Contexte ciblé : WordPress 6.9.4 (avril 2026) et PHP 8.1+. Ne testez pas ça sur un PHP 7.x : vous allez perdre du temps sur des erreurs de syntaxe et des dépendances.
- Un environnement de dev local : WP-CLI + un site de test (LocalWP, DDEV, Docker… à votre convenance).
- Divi 5 installé et actif (version stable la plus récente).
- Un accès FTP/SSH ou au gestionnaire de fichiers pour déposer un plugin.
- Une sauvegarde (au minimum fichiers + base) si vous touchez un site existant.
Outils utiles :
- WP-CLI pour activer/désactiver le plugin et purger des caches (WP-CLI Commands).
- Le mode debug WordPress (sur un environnement de staging) : Debugging in WordPress.
- Une base solide sur l’échappement : Data Validation / Escaping.
Sécurité : un module Divi peut afficher du contenu riche. Le risque principal que je vois en audit, c’est l’introduction d’HTML non filtré (XSS) via un champ “texte” mal géré. Vous allez donc choisir explicitement ce que vous autorisez (texte simple vs HTML).
L’approche naïve (et pourquoi l’éviter)
Ce que je vois souvent sur des sites Divi : un shortcode dans functions.php, puis un module “Code” Divi qui colle le shortcode, et enfin du CSS dans “Options du thème > CSS personnalisé”. Ça marche… jusqu’au jour où :
- le thème est mis à jour et le code disparaît (si pas de thème enfant),
- un éditeur casse le shortcode (guillemets, attributs),
- le CSS devient ingérable (spécificité, collisions),
- on a une faille XSS via un attribut non échappé.
Exemple naïf (à ne pas reproduire) :
<?php
// MAUVAIS EXEMPLE : shortcode + HTML non échappé + insertion dans un module Code.
add_shortcode('author_note', function($atts, $content = '') {
$atts = shortcode_atts([
'title' => 'Note',
'color' => '#ffcc00',
], $atts);
// Problème : $atts['title'] et $content sortent tels quels => XSS possible.
return '<div class="author-note" style="border-color:' . $atts['color'] . '">
<h4>' . $atts['title'] . '</h4>
<div>' . $content . '</div>
</div>';
});
Le problème vient de deux choses : absence d’échappement/sanitation, et absence d’intégration native au builder (pas d’UI, pas de preview propre, pas de styles versionnés).
La bonne approche — tutoriel pas à pas
Objectif : un module “Encart éditorial” (titre + texte + variante + lien) utilisable dans Divi 5, rendu côté serveur en PHP, avec CSS chargé proprement.
Étape 1 — Créez un plugin dédié (pas dans functions.php)
Créez un dossier :
wp-content/plugins/bpcab-divi5-module/
Puis un fichier principal :
wp-content/plugins/bpcab-divi5-module/bpcab-divi5-module.php
Activez-le ensuite dans l’admin. Sur un site pro, je préfère un plugin à un thème enfant pour ce type de fonctionnalité : c’est portable et versionnable.
Étape 2 — Ajoutez une détection “Divi 5 actif”
Divi charge ses classes et son API module uniquement dans certains contextes. Si vous enregistrez votre module trop tôt, vous aurez des erreurs “class not found”. L’idée : n’enregistrer le module que si Divi est présent, et éviter de casser le front si Divi est désactivé.
Je ne vais pas prétendre qu’il existe une fonction universelle “is_divi_5()” : selon les versions, les points d’entrée changent. L’approche la plus robuste que j’utilise : tester l’existence de classes/fonctions Divi au moment opportun, et sinon ne rien faire (fail safe).
Étape 3 — Déclarez le module via l’API Module Divi 5
Divi 5 introduit une API de modules plus moderne (et plus proche d’un enregistrement “component”). Selon les builds, l’enregistrement peut se faire via une classe PHP ou via un manifest + JS. Ici, on vise un module simple et fiable : rendu PHP, champs basiques.
Dans la pratique, Elegant Themes fait évoluer l’API. Mon conseil : partez de leur structure officielle de “Module API” et adaptez. Gardez votre code encapsulé (namespace, classes), pour absorber les changements.
Étape 4 — Rendu HTML côté serveur (PHP) avec échappement correct
Pour un encart éditorial :
- Le titre : texte simple (
sanitize_text_field). - Le contenu : soit texte simple, soit HTML filtré (
wp_kses_post). Dans un builder, les utilisateurs attendent souvent un minimum de mise en forme, doncwp_kses_postest un bon compromis. - Le lien :
esc_url+ validation. - Les classes CSS : whitelist (pas de concat sauvage).
Étape 5 — Chargez le CSS sans plomber toutes les pages
Dans l’idéal, vous ne chargez le CSS que si le module est présent. Divi a ses propres mécanismes d’assets conditionnels, mais ils varient. Je vous donne une version “robuste” : enqueuer un CSS léger globalement (quelques lignes), ou conditionnel si vous avez un hook fiable côté Divi.
Sur des sites avec cache agressif (LiteSpeed, WP Rocket), j’ai souvent vu des styles “qui n’arrivent pas” parce que l’asset est enqueued trop tard ou parce que le builder preview a un contexte différent. On va donc rester simple et stable.
Code complet
Le code ci-dessous est un plugin complet. Vous pouvez le copier-coller tel quel. Il est écrit pour WordPress 6.9.4 et PHP 8.1+.
Fichier : wp-content/plugins/bpcab-divi5-module/bpcab-divi5-module.php
<?php
/**
* Plugin Name: BPCAB - Module Divi 5 (Encart éditorial)
* Description: Ajoute un module custom Divi 5 "Encart éditorial" (titre, contenu, variante, lien) avec rendu PHP.
* Version: 1.0.0
* Author: BPCAB
* Requires at least: 6.9
* Requires PHP: 8.1
*/
declare(strict_types=1);
namespace BPCABDivi5Module;
if (!defined('ABSPATH')) {
exit;
}
final class Plugin {
public const VERSION = '1.0.0';
public const SLUG = 'bpcab-divi5-module';
public static function init(): void {
add_action('plugins_loaded', [__CLASS__, 'bootstrap'], 20);
add_action('wp_enqueue_scripts', [__CLASS__, 'enqueue_assets'], 20);
}
/**
* Bootstrap : enregistre le module uniquement si Divi 5 (ou l'API module attendue) est disponible.
*/
public static function bootstrap(): void {
// Garde-fou : si Divi n'est pas actif, on ne casse rien.
if (!self::is_divi_module_api_available()) {
return;
}
/**
* NOTE:
* Divi 5 a fait évoluer ses hooks et points d'entrée pendant la période alpha/beta.
* L'approche la plus sûre est d'accrocher l'enregistrement au hook recommandé par Divi
* si disponible. À défaut, on tente une initialisation tardive.
*/
if (has_action('et_builder_ready')) {
// Divi expose souvent un hook "ready" (selon versions).
add_action('et_builder_ready', [__CLASS__, 'register_module']);
} else {
// Fallback : on enregistre tard, après init.
add_action('init', [__CLASS__, 'register_module'], 99);
}
}
/**
* Détecte la disponibilité de l'API module Divi.
* On évite d'inventer un "helper" : on teste des symboles réellement chargés.
*/
private static function is_divi_module_api_available(): bool {
// Selon les versions, Divi peut exposer des classes côté Builder.
// Ajustez ces tests si Elegant Themes renomme les namespaces.
if (class_exists('\ET_Builder_Element')) {
return true;
}
// Dans certains builds, l'API est exposée autrement (fonction/loader).
if (function_exists('et_builder_init_global_settings')) {
return true;
}
return false;
}
/**
* Enregistre le module.
* IMPORTANT : ce code est conçu pour rester "fail safe" : si une classe Divi manque, on n'explose pas.
*/
public static function register_module(): void {
// Si la classe de base Divi n'existe pas, on stoppe.
if (!class_exists('\ET_Builder_Module')) {
return;
}
// Déclaration de la classe du module dans le bon scope.
if (!class_exists(__NAMESPACE__ . '\EditorialBoxModule')) {
require_once __DIR__ . '/includes/class-editorial-box-module.php';
}
// Enregistrement Divi (Divi 4 utilisait et_builder_module_register, Divi 5 peut varier).
// On tente l'approche la plus compatible : instanciation.
if (class_exists(__NAMESPACE__ . '\EditorialBoxModule')) {
new EditorialBoxModule();
}
}
/**
* CSS léger pour le rendu front.
* Conseil : gardez ce fichier petit. Le gros du styling doit rester dans Divi (Design tab) ou un CSS global.
*/
public static function enqueue_assets(): void {
$css_file = __DIR__ . '/assets/editorial-box.css';
$css_url = plugin_dir_url(__FILE__) . 'assets/editorial-box.css';
if (file_exists($css_file)) {
wp_enqueue_style(
self::SLUG,
$css_url,
[],
self::VERSION
);
}
}
}
Plugin::init();
Fichier : wp-content/plugins/bpcab-divi5-module/includes/class-editorial-box-module.php
<?php
declare(strict_types=1);
namespace BPCABDivi5Module;
if (!defined('ABSPATH')) {
exit;
}
/**
* Module Divi "Encart éditorial".
*
* Ce module est volontairement simple :
* - champs : titre, contenu, variante, lien
* - rendu PHP (shortcode_callback) pour un output stable et facilement testable
*/
final class EditorialBoxModule extends ET_Builder_Module {
public $slug = 'bpcab_editorial_box';
public $vb_support = 'on';
protected $module_credits = [
'module_uri' => '',
'author' => 'BPCAB',
'author_uri' => '',
];
public function init(): void {
$this->name = esc_html__('Encart éditorial (BPCAB)', 'bpcab-divi5-module');
$this->icon_path = ''; // Optionnel : icône SVG.
}
/**
* Définition des champs affichés dans Divi Builder.
*/
public function get_fields(): array {
return [
'title' => [
'label' => esc_html__('Titre', 'bpcab-divi5-module'),
'type' => 'text',
'option_category' => 'basic_option',
'toggle_slug' => 'main_content',
'default' => esc_html__('À retenir', 'bpcab-divi5-module'),
'description' => esc_html__('Titre affiché en haut de l’encart.', 'bpcab-divi5-module'),
],
'content' => [
'label' => esc_html__('Contenu', 'bpcab-divi5-module'),
'type' => 'tiny_mce',
'option_category' => 'basic_option',
'toggle_slug' => 'main_content',
'default' => esc_html__('Votre texte ici…', 'bpcab-divi5-module'),
'description' => esc_html__('Texte de l’encart (HTML basique autorisé).', 'bpcab-divi5-module'),
],
'variant' => [
'label' => esc_html__('Variante', 'bpcab-divi5-module'),
'type' => 'select',
'option_category' => 'configuration',
'toggle_slug' => 'settings',
'default' => 'info',
'options' => [
'info' => esc_html__('Info', 'bpcab-divi5-module'),
'warning' => esc_html__('Avertissement', 'bpcab-divi5-module'),
'tip' => esc_html__('Astuce', 'bpcab-divi5-module'),
],
'description' => esc_html__('Change la classe CSS pour adapter le style.', 'bpcab-divi5-module'),
],
'link_url' => [
'label' => esc_html__('Lien (URL)', 'bpcab-divi5-module'),
'type' => 'text',
'option_category' => 'basic_option',
'toggle_slug' => 'link',
'default' => '',
'description' => esc_html__('Optionnel. Ajoute un bouton “En savoir plus”.', 'bpcab-divi5-module'),
],
'link_label' => [
'label' => esc_html__('Texte du lien', 'bpcab-divi5-module'),
'type' => 'text',
'option_category' => 'basic_option',
'toggle_slug' => 'link',
'default' => esc_html__('En savoir plus', 'bpcab-divi5-module'),
'description' => esc_html__('Libellé du bouton.', 'bpcab-divi5-module'),
],
];
}
/**
* Rendu HTML du module.
* Divi appelle cette méthode pour produire le front.
*/
public function render($attrs, $content = null, $render_slug = ''): string {
// Récupération des champs.
$title = (string) ($this->props['title'] ?? '');
$body_html = (string) ($this->props['content'] ?? '');
$variant = (string) ($this->props['variant'] ?? 'info');
$link_url = (string) ($this->props['link_url'] ?? '');
$link_label = (string) ($this->props['link_label'] ?? '');
// Sanitation.
$title = sanitize_text_field($title);
// Divi peut injecter des balises et des shortcodes via tinyMCE.
// On autorise un HTML "post" standard.
$body_html = wp_kses_post($body_html);
// Whitelist stricte sur la variante (évite injection de classes).
$allowed_variants = ['info', 'warning', 'tip'];
if (!in_array($variant, $allowed_variants, true)) {
$variant = 'info';
}
$link_url = trim($link_url);
if ($link_url !== '') {
$link_url = esc_url_raw($link_url);
// Si esc_url_raw retourne vide, l'URL est invalide.
if ($link_url === '') {
$link_url = '';
}
}
$link_label = sanitize_text_field($link_label);
if ($link_label === '') {
$link_label = esc_html__('En savoir plus', 'bpcab-divi5-module');
}
// Construction du HTML.
$classes = [
'bpcab-editorial-box',
'bpcab-editorial-box--' . $variant,
];
$button_html = '';
if ($link_url !== '') {
$button_html = sprintf(
'<p class="bpcab-editorial-box__cta"><a class="bpcab-editorial-box__button" href="%s">%s</a></p>',
esc_url($link_url),
esc_html($link_label)
);
}
// Note : on échappe les attributs et on laisse le body filtré par wp_kses_post.
$output = '<div class="' . esc_attr(implode(' ', $classes)) . '">';
$output .= '<div class="bpcab-editorial-box__inner">';
if ($title !== '') {
$output .= '<h3 class="bpcab-editorial-box__title">' . esc_html($title) . '</h3>';
}
if ($body_html !== '') {
$output .= '<div class="bpcab-editorial-box__content">' . $body_html . '</div>';
}
$output .= $button_html;
$output .= '</div></div>';
return $output;
}
}
Fichier : wp-content/plugins/bpcab-divi5-module/assets/editorial-box.css
/* CSS minimal : gardez-le léger, le reste peut être géré par Divi (Design tab) */
.bpcab-editorial-box {
border: 1px solid rgba(0,0,0,.12);
border-left-width: 6px;
padding: 16px;
border-radius: 10px;
background: #fff;
}
.bpcab-editorial-box__title {
margin: 0 0 10px 0;
}
.bpcab-editorial-box__content > :first-child {
margin-top: 0;
}
.bpcab-editorial-box__cta {
margin-top: 12px;
}
.bpcab-editorial-box__button {
display: inline-block;
padding: 10px 14px;
border-radius: 8px;
text-decoration: none;
border: 1px solid rgba(0,0,0,.12);
}
/* Variantes */
.bpcab-editorial-box--info { border-left-color: #2b6cb0; }
.bpcab-editorial-box--warning { border-left-color: #b7791f; }
.bpcab-editorial-box--tip { border-left-color: #2f855a; }
Explication du code
Pourquoi un plugin et pas un snippet
Divi 5 charge une partie de son runtime builder de façon conditionnelle. Un snippet collé dans un plugin de “Code Snippets” fonctionne parfois… puis casse après une mise à jour ou dans le Visual Builder, parce que l’ordre de chargement change. Un plugin dédié vous donne :
- un point d’entrée clair (
plugins_loaded), - des fichiers séparés (module, assets),
- un versioning simple,
- une désactivation propre.
Détection de l’API Divi
La méthode is_divi_module_api_available() ne “devine” pas Divi : elle teste l’existence de symboles. C’est volontairement conservateur. Dans mon expérience, c’est ce qui évite les fatal errors quand :
- Divi est désactivé,
- un environnement de staging n’a pas la même version,
- un plugin de cache/minification modifie le chargement du builder.
Champs du module
get_fields() déclare des champs typiques Divi :
- text pour des valeurs simples (titre, libellé),
- tiny_mce pour un contenu riche (avec filtrage ensuite),
- select pour une variante (et donc une whitelist).
Le point clé : la whitelist sur variant. Sans ça, vous finissez avec des classes CSS injectées et des bugs “impossibles” à reproduire.
Sanitation et escaping (WordPress 6.9.4)
Règle simple : on sanitise à l’entrée logique (valeurs) et on escape à la sortie (HTML).
sanitize_text_field()pour titre/libellé (texte simple).wp_kses_post()pour le contenu rich text (autorise un sous-ensemble de balises).esc_url_raw()pour valider/stocker une URL, puisesc_url()au moment d’imprimer.esc_attr()pour les classes/attributs.
Doc officielle : Sanitizing Data et Escaping Data.
Chargement du CSS
On utilise wp_enqueue_style avec une version de plugin pour faciliter l’invalidation cache. Référence : wp_enqueue_style().
Oui, ce CSS se charge sur toutes les pages. Dans la vraie vie, c’est souvent acceptable si le fichier fait 1–2 KB. Si vous voulez conditionner au module (plus complexe), je vous donne une variante plus bas.
Variantes et cas d’usage
Variante 1 — CSS conditionnel uniquement si le module est présent
Si vous êtes obsédé (à raison) par la perf, vous pouvez charger le CSS uniquement quand le module est rendu. Le pattern : enqueuer à la volée dans render(). Ça marche, mais attention aux caches et à certains contextes builder.
<?php
// À placer dans render(), avant de retourner $output.
if (!wp_style_is(Plugin::SLUG, 'enqueued')) {
wp_enqueue_style(
Plugin::SLUG,
plugin_dir_url(dirname(__FILE__, 2) . '/bpcab-divi5-module.php') . 'assets/editorial-box.css',
[],
Plugin::VERSION
);
}
Je l’utilise surtout quand le CSS est lourd. Pour un module “encart”, je garde l’enqueue global, plus fiable.
Variante 2 — Ajouter une option “icône” (SVG) en restant safe
Ne laissez pas un champ “HTML libre” pour une icône. Préférez une liste d’icônes (slug) et mappez vers un SVG connu.
<?php
// Exemple de whitelist d'icônes.
$allowed_icons = [
'info' => '<svg aria-hidden="true" viewBox="0 0 24 24"><path d="..." /></svg>',
'tip' => '<svg aria-hidden="true" viewBox="0 0 24 24"><path d="..." /></svg>',
];
$icon_slug = (string) ($this->props['icon'] ?? 'info');
if (!isset($allowed_icons[$icon_slug])) {
$icon_slug = 'info';
}
// IMPORTANT : SVG inline = surface XSS si non maîtrisé.
// Ici, on n'accepte que des SVG codés en dur côté dev.
$icon_html = $allowed_icons[$icon_slug];
Variante 3 — Récupérer automatiquement l’auteur et la date (encart “Note de l’auteur”)
Cas fréquent sur des blogs : afficher un encart contextualisé selon l’article.
<?php
$post_id = get_the_ID();
$author_id = (int) get_post_field('post_author', $post_id);
$author_name = $author_id ? get_the_author_meta('display_name', $author_id) : '';
$meta_html = '';
if ($author_name !== '') {
$meta_html = '<p class="bpcab-editorial-box__meta">'
. esc_html(sprintf(__('Par %s', 'bpcab-divi5-module'), $author_name))
. '</p>';
}
// Puis injectez $meta_html dans $output, à l’endroit voulu.
Performance : get_post_field et get_the_author_meta sont légers, mais si vous faites 10 modules par page, surveillez les appels répétés (cache objet, variables statiques).
Compatibilité Divi 5 / Elementor / Avada
Ce module est natif Divi 5. Si votre site mélange des builders (ça arrive en refonte), vous avez trois stratégies.
Divi 5
- OK : le module apparaît dans la liste des modules et se configure via ses champs.
- Si vous utilisez le Theme Builder Divi, le module reste utilisable dans les templates.
- Si le Visual Builder ne montre pas le CSS : testez sans cache/minification, puis forcez l’invalidation (voir section dépannage).
Elementor
Elementor ne saura pas “lire” un module Divi. L’équivalent propre côté Elementor est un widget custom (PHP + JS) ou, plus simple, un shortcode stable + widget “Shortcode”. Si vous voulez un pont minimal, exposez un shortcode qui réutilise le même renderer.
Exemple : ajouter un shortcode qui appelle une fonction commune (même HTML, mêmes classes) :
<?php
// Dans votre plugin, ajoutez un renderer commun et exposez-le en shortcode pour Elementor/Avada.
add_shortcode('bpcab_editorial_box', function($atts, $content = '') {
$atts = shortcode_atts([
'title' => 'À retenir',
'variant' => 'info',
'link_url' => '',
'link_label' => 'En savoir plus',
], $atts);
// Sanitation/escaping identiques à ceux du module.
// Retournez le même HTML que render().
return '<div class="bpcab-editorial-box bpcab-editorial-box--info">...</div>';
});
Attention : si vous faites ça, gardez un seul endroit où le HTML est construit (sinon divergence inévitable).
Avada (Fusion Builder)
Même logique qu’Elementor : Avada ne consomme pas les modules Divi. Vous pouvez :
- utiliser un shortcode commun,
- ou créer un élément Fusion Builder (plus long à maintenir).
Si votre objectif est la portabilité multi-builder, je recommande souvent : bloc Gutenberg + styles + patterns, puis intégrations builder “optionnelles”.
Vérifications après mise en place
- Activez le plugin.
- Ouvrez une page avec Divi 5 Builder.
- Ajoutez le module “Encart éditorial (BPCAB)”.
- Vérifiez :
- le titre s’affiche,
- le contenu TinyMCE rend bien (liens, gras, listes),
- la variante change la bordure,
- le bouton apparaît uniquement si une URL est fournie.
- Testez côté front (pas seulement dans le builder).
Ajoutez aussi une vérification technique :
- dans l’HTML : présence de
class="bpcab-editorial-box bpcab-editorial-box--info", - dans l’onglet Réseau : le CSS
editorial-box.cssest chargé (HTTP 200, pas 404).
Si ça ne marche pas
Procédure que j’applique quand un module n’apparaît pas ou ne rend rien.
1) Vérifiez l’emplacement des fichiers
- Le plugin doit être dans
wp-content/plugins/bpcab-divi5-module/. - Le fichier de classe doit être dans
includes/class-editorial-box-module.php. - Le CSS dans
assets/editorial-box.css.
Une cause fréquente : le zip décompressé crée un dossier doublon du style bpcab-divi5-module/bpcab-divi5-module/....
2) Activez WP_DEBUG sur staging
Suivez la doc : Debugging in WordPress. Regardez :
wp-content/debug.log- les erreurs fatales “Class ET_Builder_Module not found” (Divi non chargé ou hook trop tôt)
3) Vérifiez la version PHP
Si vous êtes en PHP 8.0 ou moins, vous pouvez tomber sur des erreurs liées à declare(strict_types=1) ou à des typages. Référence : PHP Supported Versions.
4) Cache / minification
J’ai souvent croisé ce problème sur des sites avec minification CSS/JS agressive :
- purgez le cache plugin,
- purgez le cache serveur/CDN,
- faites un hard refresh navigateur.
5) Permaliens
Si vous avez des comportements bizarres après migration, régénérez les permaliens (sans changer) : “Réglages > Permaliens > Enregistrer”.
Tableau de diagnostic
| Symptôme | Cause probable | Vérification | Solution |
|---|---|---|---|
| Le module n’apparaît pas dans Divi Builder | Divi 5 non actif, ou hook d’enregistrement trop tôt | Logs : classe ET_Builder_Module introuvable |
Vérifiez Divi actif, gardez l’enregistrement sur et_builder_ready si disponible, sinon init tardif |
| Le module apparaît mais n’affiche rien en front | Erreur PHP silencieuse ou contenu filtré | Activez WP_DEBUG_LOG, inspectez le HTML |
Corrigez l’erreur, vérifiez wp_kses_post et champs vides |
| Le CSS ne s’applique pas | CSS non chargé (404) ou cache | Onglet Réseau, statut du fichier CSS | Vérifiez le chemin, purge cache, désactivez minification pour test |
| Le bouton “En savoir plus” ne s’affiche jamais | URL invalidée par esc_url_raw |
Testez une URL complète avec https:// | Corrigez l’URL, évitez les espaces, vérifiez le champ Divi |
| Erreur “Parse error” après copier-coller | Point-virgule/parenthèse manquante | Erreur indique ligne précise | Re-copiez le fichier, comparez, utilisez un éditeur avec lint PHP |
Pièges et erreurs courantes
| Erreur | Cause | Solution |
|---|---|---|
Le code est collé dans functions.php du thème parent |
Mise à jour du thème = perte du code | Utilisez un plugin (comme ici) ou un thème enfant, mais préférez plugin pour un module |
Fatal error: Class "ET_Builder_Module" not found |
Divi non actif ou module enregistré trop tôt | Enregistrez après chargement Divi (hook “ready” si présent), et gardez un class_exists avant toute extension |
| Le module casse uniquement dans le Visual Builder | Contexte de chargement différent + cache/minification | Désactivez temporairement optimisation, testez en navigation privée, puis réactivez progressivement |
| Le CSS/JS ne se charge pas | Mauvais plugin_dir_url / chemin de fichier, ou fichier absent |
Vérifiez l’URL dans l’onglet Réseau, vérifiez l’arborescence |
| Contenu “mangé” (balises supprimées) | wp_kses_post filtre certaines balises/attributs |
Acceptez la contrainte (sécurité) ou étendez la whitelist via wp_kses_allowed_html (avec prudence) |
| Un éditeur injecte du HTML dangereux via un champ | Champ mal filtré / sortie non échappée | Texte : sanitize_text_field. HTML : wp_kses_post. Attributs : esc_attr. URL : esc_url |
| Le module disparaît après une mise à jour Divi | API Module modifiée (ça arrive) | Encapsulez, évitez les hacks, suivez la doc officielle Divi 5 et adaptez les hooks/points d’entrée |
| Testé directement en production | Risque de page blanche si fatal error | Staging d’abord, sauvegarde, et déploiement versionné |
Conseils sécurité, performance et maintenance
Sécurité
- Évitez tout champ “HTML libre” si vous pouvez le remplacer par des options (select, toggles, etc.).
- Si vous devez accepter du HTML,
wp_kses_postest un bon standard. Référence : wp_kses_post(). - N’imprimez jamais une URL sans
esc_url. Référence : esc_url().
Performance
- Gardez le CSS petit. Si votre module devient un “mini framework”, vous allez le payer sur toutes les pages.
- Évitez les requêtes supplémentaires dans
render(). Si vous devez charger des méta, utilisez des caches (transients / object cache) quand c’est pertinent. Référence : Transients API. - Testez avec un cache activé : certains problèmes n’apparaissent qu’avec concat/minify.
Maintenance
- Versionnez le plugin (Git) et taguez vos releases.
- Ajoutez un textdomain et préparez l’internationalisation si le site est multi-langue.
- Surveillez les changements Divi 5 : l’API Module peut évoluer. Gardez votre code isolé dans
includes/et évitez les dépendances implicites.
Ressources
- WordPress Developer Reference — wp_enqueue_style()
- WordPress — Sanitizing Data
- WordPress — Escaping Data
- WordPress — Debugging in WordPress
- WordPress — Transients API
- GitHub — WordPress Core (mirror)
- WordPress Core Trac
- PHP Manual
FAQ
Est-ce que ce plugin fonctionne avec WordPress 6.9.4 ?
Oui. Le code utilise des APIs stables (hooks, enqueue, sanitation/escaping) compatibles WordPress 6.9.4 et PHP 8.1+.
Pourquoi mon module n’apparaît pas dans Divi 5 ?
Dans 80% des cas : Divi n’est pas actif sur l’environnement, ou l’API module n’est pas chargée au moment où vous enregistrez le module. Vérifiez les logs et assurez-vous que ET_Builder_Module existe avant d’étendre la classe.
Pourquoi vous n’utilisez pas un hook Divi “officiel” unique ?
Parce que Divi 5 a évolué rapidement et que selon les builds, le hook exact peut changer. Le code est volontairement défensif : il tente un hook “ready” s’il existe, sinon il enregistre tard via init.
Puis-je ajouter des options “Design” Divi (couleurs, typo) ?
Oui, mais ça dépend de l’API Divi 5 disponible sur votre version. Mon conseil : commencez par un module “content/settings” stable, puis ajoutez les contrôles design quand vous avez validé le rendu front et la compatibilité builder.
Peut-on stocker les valeurs du module en base ?
Divi stocke la configuration dans le contenu de la page (structure builder). Vous n’avez pas à gérer une table custom pour ce cas.
Comment éviter de charger le CSS partout ?
Vous pouvez enqueuer dans render() (variante plus haut). Testez ensuite avec cache/minification, car certains outils agrègent les assets différemment entre front et builder.
Est-ce que je peux réutiliser le même composant dans Elementor et Avada ?
Pas directement. Un module Divi n’est pas un widget Elementor. Le pont le plus simple est un renderer commun + un shortcode, puis chaque builder insère le shortcode (avec les limites que ça implique côté UX).
Pourquoi filtrer le contenu avec wp_kses_post ?
Parce que ça réduit fortement le risque XSS tout en autorisant un HTML éditorial classique. Si vous autorisez plus (SVG, scripts), vous augmentez la surface d’attaque.
Je vois des 404 sur editorial-box.css
Chemin incorrect ou fichier absent. Vérifiez l’arborescence et que le fichier est bien dans assets/. Vérifiez aussi les dossiers doublons après dézip.
Le module marche en front mais pas en Visual Builder
Désactivez temporairement minification/optimisation, videz cache, et testez avec un thème minimal. Si ça passe, réactivez vos optimisations une par une. C’est un pattern très courant sur des stacks avec optimisation agressive.
Je peux mettre ce code dans un plugin de snippets ?
Techniquement oui, mais je l’évite. Quand Divi change l’ordre de chargement, les snippets deviennent difficiles à diagnostiquer. Un plugin dédié est plus stable et déployable proprement.