Si vous avez déjà ajouté un “petit snippet” pour la fiche produit WooCommerce et que, d’un coup, le prix disparaît ou le bouton “Ajouter au panier” se retrouve au mauvais endroit, vous avez rencontré le vrai sujet : personnaliser sans plugin ne veut pas dire bidouiller sans méthode.

Ce qu’on va réaliser

Vous allez personnaliser la page produit WooCommerce (fiche produit) sans installer de plugin de snippets et sans toucher au cœur de WordPress/WooCommerce, sur un site WordPress 6.9.4 (avril 2026) avec PHP 8.1+.

Résultat concret côté boutique :

  • Les éléments clés (prix, note, extrait, bouton panier, meta) sont réordonnés pour mieux convertir.
  • Un bandeau de réassurance s’affiche au bon endroit (ex : “Retours 30 jours / Expédition 24h”).
  • Un message conditionnel (ex : livraison offerte dès 60€) s’affiche selon le prix du produit.
  • Les onglets (description, infos supplémentaires, avis) sont renommés, réordonnés, et vous pouvez en supprimer/ajouter.
  • Un CTA (lien guide des tailles, contact, WhatsApp…) apparaît juste après “Ajouter au panier”.
  • Un CSS minimal est chargé uniquement sur les pages produit.

À la fin, vous saurez :

  • où coller le code proprement (mu-plugin ou plugin custom, et quand utiliser un thème enfant),
  • quels hooks WooCommerce utiliser,
  • comment éviter les conflits avec Divi 5, Elementor et Avada.

Résumé rapide

  • On évite functions.php si possible : on crée un mu-plugin (chargé automatiquement).
  • On personnalise la fiche produit via les hooks WooCommerce (actions/filtres) plutôt que des overrides de template.
  • On réordonne les blocs avec remove_action() / add_action() et les priorités.
  • On modifie les onglets via le filtre woocommerce_product_tabs.
  • On ajoute du CSS via wp_enqueue_scripts et on le limite à is_product().
  • On teste avec une checklist + on prévoit les conflits de cache et de builders.

Quand utiliser cette solution

Cette approche est idéale si :

  • vous voulez une fiche produit plus efficace (conversion) sans installer 3 extensions,
  • vous devez garder un site stable (moins de plugins = moins de surface de bugs),
  • vous avez quelques personnalisations ciblées (ordre des blocs, textes, onglets, micro-CTA),
  • vous utilisez un thème compatible WooCommerce (la majorité) et vous acceptez de tester sur un environnement de staging.

Dans mon expérience, c’est la meilleure voie pour 80% des boutiques “classiques” : vêtements, accessoires, produits digitaux, artisanat, petites marques.

Quand ne PAS utiliser cette solution

Évitez ce tutoriel comme solution principale si :

  • vous voulez refaire toute la mise en page avec des sections complexes (grilles, colonnes, blocs conditionnels par variation) : un builder WooCommerce ou des templates dédiés seront plus adaptés,
  • vous avez besoin d’un configurateur produit, d’options avancées (add-ons), ou d’une logique métier lourde (B2B, prix par rôle, bundles complexes),
  • vous n’avez aucun accès aux fichiers (hébergement verrouillé) : dans ce cas, un plugin reste parfois la seule option.

Autre cas fréquent : si votre thème remplace totalement la fiche produit WooCommerce (certains thèmes “boutique” le font), les hooks standards peuvent être partiellement ignorés. Là, il faut diagnostiquer avant.

Avant de commencer (prérequis)

Versions et environnement

  • WordPress : 6.9.4 (ou plus récent)
  • PHP : 8.1+ (8.2/8.3 recommandé si votre hébergeur le permet)
  • WooCommerce : version récente (vérifiez dans Extensions > Extensions installées)
  • Un thème enfant si vous comptez faire des overrides de templates (optionnel dans ce tutoriel, mais utile en général)

Sauvegarde et test

Faites une sauvegarde (fichiers + base de données) et testez sur un staging si possible. Beaucoup d’erreurs viennent d’un simple point-virgule manquant… et sur une boutique en prod, ça peut faire une page blanche.

Où coller le code (choix recommandé)

Vous avez trois emplacements possibles. Je vous donne l’ordre de préférence :

  • MU-plugin (recommandé) : un fichier PHP dans /wp-content/mu-plugins/. Chargé automatiquement, même si vous changez de thème.
  • Plugin custom : un petit plugin maison dans /wp-content/plugins/. Activable/désactivable.
  • functions.php du thème enfant : acceptable, mais si vous changez de thème, vous perdez la personnalisation. Et j’ai souvent vu des sites casser parce que le thème parent était mis à jour et que le thème enfant contenait du code non testé.

Accéder aux réglages WooCommerce utiles

  • Réglages généraux : WooCommerce > Réglages
  • Taxes/livraison : WooCommerce > Réglages > Livraison
  • Statut système (diagnostic) : WooCommerce > État

Notions : hook, action, filtre (en clair)

Un hook est un “point d’accroche” dans le code. WooCommerce en expose beaucoup sur la fiche produit.

  • Une action exécute du code à un endroit précis (ex : afficher un bloc). En PHP : add_action().
  • Un filtre modifie une valeur (ex : modifier un tableau d’onglets). En PHP : add_filter().

WordPress a ses hooks, WooCommerce ajoute les siens. La différence pratique : les hooks WooCommerce sont positionnés selon la structure des templates WooCommerce (ex : “avant le résumé produit”).

Les hooks WooCommerce utilisés

Voici ceux qu’on va utiliser, avec leur rôle :

  • woocommerce_single_product_summary (action) : zone centrale de la fiche produit (titre, prix, note, extrait, bouton panier…). C’est le hook le plus rentable pour réordonner.
  • woocommerce_before_single_product_summary (action) : avant le résumé (souvent la galerie images).
  • woocommerce_after_single_product_summary (action) : après le résumé (onglets, upsells, produits liés).
  • woocommerce_product_tabs (filtre) : permet de modifier les onglets (titre, ordre, suppression, ajout).
  • wp_enqueue_scripts (action WordPress) : charger CSS/JS côté front.

Référence utile : la liste des hooks WooCommerce varie selon les templates, mais la structure du single product est stable depuis longtemps. Quand un site “ignore” ces hooks, c’est presque toujours un thème/builder qui remplace le template.

Étape 1 : créer un mini-plugin (recommandé) plutôt que coller du code au hasard

Le but : mettre votre personnalisation dans un fichier isolé, versionnable, et facile à désactiver si vous cassez quelque chose.

Option A (recommandée) : MU-plugin

Créez le dossier /wp-content/mu-plugins/ s’il n’existe pas, puis créez un fichier :

/wp-content/mu-plugins/bpcab-wc-single-product-custom.php

<?php
/**
 * Personnalisations WooCommerce - Fiche produit (sans plugin)
 *
 * Emplacement : wp-content/mu-plugins/bpcab-wc-single-product-custom.php
 * Compatible : WordPress 6.9.4+, PHP 8.1+, WooCommerce récent
 */

defined('ABSPATH') || exit;

/**
 * Petite sécurité : ne chargez ce fichier que si WooCommerce est actif.
 * (woocommerce/woocommerce.php est le plugin principal)
 */
function bpcab_wc_is_active(): bool {
	return class_exists('WooCommerce');
}

/**
 * Point d'entrée : on attend que WordPress soit prêt.
 */
add_action('plugins_loaded', function () {
	if (!bpcab_wc_is_active()) {
		return;
	}

	// Les étapes suivantes vont s'accrocher ici (hooks WooCommerce et WordPress).
}, 20);

Pourquoi plugins_loaded ? Parce que WooCommerce doit être chargé. J’ai souvent vu des erreurs “Call to undefined function” quand le code part trop tôt.

Option B : plugin custom

Si vous préférez activer/désactiver depuis l’admin, créez :

/wp-content/plugins/bpcab-wc-single-product-custom/bpcab-wc-single-product-custom.php

Le code est identique, mais ajoutez un en-tête de plugin WordPress. Référence : Plugin Header Requirements.

Étape 2 : réordonner les éléments de la fiche produit (sans override de template)

WooCommerce affiche le contenu du “résumé produit” via une série d’actions déjà enregistrées avec des priorités. Vous allez retirer certains éléments et les remettre au bon endroit.

Objectif concret

  • Afficher le prix plus haut (juste après le titre)
  • Afficher la note après le prix
  • Déplacer la meta (SKU/catégories) sous le bouton panier

Ajoutez ce code dans le callback de plugins_loaded (dans votre mu-plugin), ou directement après si vous ne structurez pas :

<?php
defined('ABSPATH') || exit;

add_action('plugins_loaded', function () {
	if (!class_exists('WooCommerce')) {
		return;
	}

	/**
	 * On réordonne les éléments du résumé produit.
	 * Le hook woocommerce_single_product_summary est déclenché dans le template single-product.php.
	 *
	 * Note : on garde les fonctions WooCommerce natives (wc_template_*),
	 * on ne réécrit pas la logique.
	 */
	add_action('wp', function () {

		// Retirer (pour les réinsérer dans un autre ordre)
		remove_action('woocommerce_single_product_summary', 'woocommerce_template_single_rating', 10);
		remove_action('woocommerce_single_product_summary', 'woocommerce_template_single_price', 10);
		remove_action('woocommerce_single_product_summary', 'woocommerce_template_single_excerpt', 20);
		remove_action('woocommerce_single_product_summary', 'woocommerce_template_single_meta', 40);

		// Réinsérer : titre est à 5 par défaut, on place le prix à 6
		add_action('woocommerce_single_product_summary', 'woocommerce_template_single_price', 6);

		// Note après le prix
		add_action('woocommerce_single_product_summary', 'woocommerce_template_single_rating', 7);

		// Extrait un peu plus bas (après le bouton panier qui est souvent à 30)
		add_action('woocommerce_single_product_summary', 'woocommerce_template_single_excerpt', 31);

		// Meta sous l'extrait
		add_action('woocommerce_single_product_summary', 'woocommerce_template_single_meta', 35);

	}, 20);
}, 20);

Pourquoi passer par wp ? Parce que les remove/add sont plus fiables quand WordPress a déterminé le type de page. Sur certains sites, le thème/builder joue avec les hooks plus tard, et la priorité vous sauve.

Résultat attendu

Sur la fiche produit, vous verrez le prix monter juste sous le titre, puis la note. Le reste suit votre ordre.

Étape 3 : ajouter un bandeau de réassurance et une info livraison conditionnelle

Un bandeau simple, au bon endroit, fait souvent mieux qu’un plugin “badges” lourd. On va l’afficher juste après le prix.

Ajouter un bandeau fixe

<?php
add_action('plugins_loaded', function () {
	if (!class_exists('WooCommerce')) {
		return;
	}

	/**
	 * Affiche un bandeau de réassurance après le prix.
	 * Hook : woocommerce_single_product_summary
	 */
	add_action('woocommerce_single_product_summary', function () {
		if (!is_product()) {
			return;
		}

		echo '<div class="bpcab-reassurance" role="note">';
		echo '<strong>Expédition 24/48h</strong><br>';
		echo '<span>Retours 30 jours • Paiement sécurisé</span>';
		echo '</div>';
	}, 8);
}, 20);

Ajouter une info conditionnelle selon le prix

Exemple : “Livraison offerte dès 60€” si le produit est en dessous. On récupère le prix WooCommerce, en tenant compte des produits variables (prix min).

<?php
add_action('plugins_loaded', function () {
	if (!class_exists('WooCommerce')) {
		return;
	}

	add_action('woocommerce_single_product_summary', function () {
		if (!is_product()) {
			return;
		}

		global $product;
		if (!$product || !is_a($product, 'WC_Product')) {
			return;
		}

		// Seuil : à adapter
		$free_shipping_threshold = 60.0;

		// Prix affichable : pour les variables, on prend le prix min
		$price = (float) $product->get_price();
		if ($product->is_type('variable')) {
			$price = (float) $product->get_variation_price('min', true);
		}

		if ($price > 0 && $price < $free_shipping_threshold) {
			$missing = $free_shipping_threshold - $price;

			echo '<div class="bpcab-shipping-hint">';
			echo 'Livraison offerte dès ' . esc_html(wc_price($free_shipping_threshold)) . '. ';
			echo 'Plus que <strong>' . esc_html(wc_price($missing)) . '</strong>.';
			echo '</div>';
		}
	}, 9);
}, 20);

Point sécurité : on échappe les sorties HTML avec esc_html(). wc_price() renvoie du HTML (symbole, span), donc on l’échappe en HTML texte ici. Si vous voulez conserver le format HTML de wc_price(), utilisez wp_kses_post() à la place.

Étape 4 : personnaliser les onglets (description / avis) proprement

Les onglets sont gérés via un tableau filtré par woocommerce_product_tabs. C’est un filtre : vous recevez une valeur, vous la modifiez, vous la retournez.

Renommer et réordonner les onglets

<?php
add_action('plugins_loaded', function () {
	if (!class_exists('WooCommerce')) {
		return;
	}

	/**
	 * Personnalise les onglets produit.
	 * Filtre : woocommerce_product_tabs
	 */
	add_filter('woocommerce_product_tabs', function (array $tabs): array {

		// Renommer l'onglet description
		if (isset($tabs['description']['title'])) {
			$tabs['description']['title'] = 'Détails';
			$tabs['description']['priority'] = 10;
		}

		// Renommer l'onglet infos supplémentaires
		if (isset($tabs['additional_information']['title'])) {
			$tabs['additional_information']['title'] = 'Caractéristiques';
			$tabs['additional_information']['priority'] = 20;
		}

		// Mettre les avis en dernier
		if (isset($tabs['reviews']['priority'])) {
			$tabs['reviews']['priority'] = 30;
		}

		return $tabs;
	}, 20);
}, 20);

Supprimer un onglet (ex : “Infos supplémentaires”)

Si votre boutique n’utilise pas les attributs, cet onglet est souvent vide. Le supprimer évite un onglet inutile.

<?php
add_action('plugins_loaded', function () {
	if (!class_exists('WooCommerce')) {
		return;
	}

	add_filter('woocommerce_product_tabs', function (array $tabs): array {
		// Supprime l'onglet "additional_information"
		unset($tabs['additional_information']);
		return $tabs;
	}, 30);
}, 20);

Ajouter un onglet “Livraison & retours”

On ajoute un onglet qui affiche un contenu simple. Ici, pas de champ admin : on reste “sans plugin”. Si vous voulez un contenu éditable, je vous donne une alternative plus bas.

<?php
add_action('plugins_loaded', function () {
	if (!class_exists('WooCommerce')) {
		return;
	}

	add_filter('woocommerce_product_tabs', function (array $tabs): array {

		$tabs['bpcab_shipping_returns'] = [
			'title'    => 'Livraison & retours',
			'priority' => 25,
			'callback' => function () {
				echo '<h2>Livraison & retours</h2>';
				echo '<p>Expédition sous 24/48h ouvrées. Retours sous 30 jours (produit non utilisé).</p>';
				echo '<p>Pour les produits volumineux, un transporteur peut vous contacter.</p>';
			},
		];

		return $tabs;
	}, 25);
}, 20);

Note : le callback imprime du HTML. Si vous injectez du contenu venant de l’admin, échappez-le (risque XSS).

Étape 5 : ajouter un CTA après “Ajouter au panier” + un champ technique

Le hook woocommerce_after_add_to_cart_button est un classique : il s’exécute juste après le bouton panier. Pratique pour un lien guide des tailles, un bouton contact, ou un rappel “Besoin d’aide ?”.

Ajouter un CTA simple

<?php
add_action('plugins_loaded', function () {
	if (!class_exists('WooCommerce')) {
		return;
	}

	add_action('woocommerce_after_add_to_cart_button', function () {
		if (!is_product()) {
			return;
		}

		// Exemple : lien vers une page "Guide des tailles"
		$url = home_url('/guide-des-tailles/');

		echo '<div class="bpcab-after-cart-cta">';
		echo '<a class="bpcab-cta-link" href="' . esc_url($url) . '">Voir le guide des tailles</a>';
		echo '</div>';
	}, 20);
}, 20);

Ajouter un champ caché (ex : tracking interne) en restant propre

Exemple courant : vous voulez ajouter un champ hidden dans le formulaire “add to cart” pour tracer une source (sans plugin). Attention : si vous traitez une donnée côté serveur, utilisez un nonce. Ici, on montre juste l’insertion du champ, sans logique de traitement.

<?php
add_action('plugins_loaded', function () {
	if (!class_exists('WooCommerce')) {
		return;
	}

	add_action('woocommerce_before_add_to_cart_button', function () {
		if (!is_product()) {
			return;
		}

		// Champ hidden simple (pas de donnée sensible)
		echo '<input type="hidden" name="bpcab_source" value="single_product" />';
	}, 20);
}, 20);

Si vous commencez à enregistrer ce champ dans la commande, vous entrez dans une zone “données utilisateur” : validation, nettoyage, et sécurité deviennent obligatoires.

Étape 6 : ajouter un CSS propre (et chargé uniquement sur la page produit)

Le CSS “vite fait” collé dans le customizer marche… jusqu’au jour où il devient ingérable. Ici, on charge un petit fichier CSS uniquement sur les pages produit.

Créer le fichier CSS

Créez :

/wp-content/mu-plugins/assets/bpcab-wc-single.css

.bpcab-reassurance{
  margin: 12px 0;
  padding: 10px 12px;
  border: 1px solid rgba(0,0,0,.08);
  background: rgba(0,0,0,.03);
  border-radius: 8px;
}

.bpcab-shipping-hint{
  margin: 10px 0 14px;
  padding: 10px 12px;
  border-left: 4px solid #1e73be;
  background: rgba(30,115,190,.06);
}

.bpcab-after-cart-cta{
  margin-top: 10px;
}

.bpcab-cta-link{
  display: inline-block;
  text-decoration: underline;
}

Enqueue côté PHP

<?php
add_action('plugins_loaded', function () {
	if (!class_exists('WooCommerce')) {
		return;
	}

	/**
	 * Charge le CSS uniquement sur les pages produit.
	 * Hook WordPress : wp_enqueue_scripts
	 */
	add_action('wp_enqueue_scripts', function () {

		if (!is_product()) {
			return;
		}

		// URL du fichier (MU-plugin)
		$css_url  = content_url('mu-plugins/assets/bpcab-wc-single.css');
		$css_path = WP_CONTENT_DIR . '/mu-plugins/assets/bpcab-wc-single.css';

		// Version : utile pour casser le cache quand vous modifiez le fichier
		$ver = file_exists($css_path) ? (string) filemtime($css_path) : null;

		wp_enqueue_style(
			'bpcab-wc-single',
			$css_url,
			[],
			$ver
		);

	}, 20);
}, 20);

Erreur fréquente : utiliser un mauvais chemin (URL vs PATH) et finir avec un CSS qui ne charge jamais. Ici, on calcule les deux.

Compatibilité Divi 5 / Elementor / Avada

La règle : si le builder utilise les templates WooCommerce standards, vos hooks fonctionnent. S’il remplace entièrement la fiche produit, il faut adapter.

Divi 5

  • Divi peut utiliser des “Product Templates” (Theme Builder). Si votre template Divi inclut le module “Woo Product Summary”, il s’appuie souvent sur les hooks, mais pas toujours.
  • Test rapide : désactivez temporairement le template produit Divi (sur staging) et voyez si vos changements apparaissent sur la fiche WooCommerce “native”.
  • Astuce : si votre CTA n’apparaît pas, placez-le sur un hook plus “bas niveau” comme woocommerce_after_add_to_cart_form (selon la structure du module).

Elementor

  • Avec Elementor Pro, les templates “Single Product” peuvent remplacer le rendu WooCommerce.
  • Si vous utilisez les widgets WooCommerce natifs Elementor (Add to Cart, Product Price…), certains hooks ne sont plus appelés.
  • Approche pragmatique : gardez les hooks pour la logique (condition, texte) et utilisez un widget HTML/Shortcode dans Elementor si nécessaire. Exemple : créer un shortcode qui affiche votre bandeau, puis l’insérer où vous voulez.

Avada (Fusion Builder)

  • Avada propose des layouts WooCommerce et peut injecter ses propres wrappers. La majorité des hooks restent disponibles, mais les priorités peuvent “sembler” ignorées si Avada recompose la page.
  • Si l’ordre ne change pas : cherchez des template overrides Avada dans le thème (ou child theme) qui remplacent les templates WooCommerce.

Une solution universelle pour les builders : le shortcode

Quand un builder bloque vos hooks, un shortcode vous redonne la main. Exemple : shortcode pour le bandeau réassurance, que vous placez dans un module texte.

<?php
add_action('plugins_loaded', function () {
	if (!class_exists('WooCommerce')) {
		return;
	}

	add_shortcode('bpcab_reassurance', function (): string {
		if (!is_product()) {
			return '';
		}

		ob_start();
		echo '<div class="bpcab-reassurance" role="note">';
		echo '<strong>Expédition 24/48h</strong><br>';
		echo '<span>Retours 30 jours • Paiement sécurisé</span>';
		echo '</div>';
		return (string) ob_get_clean();
	});
}, 20);

Divi 5 : module Texte + [bpcab_reassurance]. Elementor : widget Shortcode. Avada : élément “Shortcode”.

Le code complet

Copiez-collez ceci dans /wp-content/mu-plugins/bpcab-wc-single-product-custom.php. Ce fichier inclut toutes les étapes (réordonnancement, bandeaux, onglets, CTA, CSS).

<?php
/**
 * Personnalisations WooCommerce - Fiche produit (sans plugin)
 *
 * Emplacement : wp-content/mu-plugins/bpcab-wc-single-product-custom.php
 * Fichier CSS : wp-content/mu-plugins/assets/bpcab-wc-single.css
 *
 * Compatible : WordPress 6.9.4+, PHP 8.1+, WooCommerce récent
 */

defined('ABSPATH') || exit;

function bpcab_wc_is_active(): bool {
	return class_exists('WooCommerce');
}

add_action('plugins_loaded', function () {

	if (!bpcab_wc_is_active()) {
		return;
	}

	/**
	 * 1) Réordonner les éléments du résumé produit
	 */
	add_action('wp', function () {

		// On ne touche qu'aux pages produit
		if (!function_exists('is_product') || !is_product()) {
			return;
		}

		// Retirer des éléments pour les réinsérer dans un autre ordre
		remove_action('woocommerce_single_product_summary', 'woocommerce_template_single_rating', 10);
		remove_action('woocommerce_single_product_summary', 'woocommerce_template_single_price', 10);
		remove_action('woocommerce_single_product_summary', 'woocommerce_template_single_excerpt', 20);
		remove_action('woocommerce_single_product_summary', 'woocommerce_template_single_meta', 40);

		// Réinsérer avec des priorités adaptées
		add_action('woocommerce_single_product_summary', 'woocommerce_template_single_price', 6);
		add_action('woocommerce_single_product_summary', 'woocommerce_template_single_rating', 7);
		add_action('woocommerce_single_product_summary', 'woocommerce_template_single_excerpt', 31);
		add_action('woocommerce_single_product_summary', 'woocommerce_template_single_meta', 35);

	}, 20);

	/**
	 * 2) Bandeau de réassurance
	 */
	add_action('woocommerce_single_product_summary', function () {
		if (!is_product()) {
			return;
		}

		echo '<div class="bpcab-reassurance" role="note">';
		echo '<strong>Expédition 24/48h</strong><br>';
		echo '<span>Retours 30 jours • Paiement sécurisé</span>';
		echo '</div>';
	}, 8);

	/**
	 * 3) Info livraison conditionnelle selon le prix
	 */
	add_action('woocommerce_single_product_summary', function () {
		if (!is_product()) {
			return;
		}

		global $product;
		if (!$product || !is_a($product, 'WC_Product')) {
			return;
		}

		$free_shipping_threshold = 60.0;

		$price = (float) $product->get_price();
		if ($product->is_type('variable')) {
			$price = (float) $product->get_variation_price('min', true);
		}

		if ($price > 0 && $price < $free_shipping_threshold) {
			$missing = $free_shipping_threshold - $price;

			echo '<div class="bpcab-shipping-hint">';
			echo 'Livraison offerte dès ' . esc_html(wc_price($free_shipping_threshold)) . '. ';
			echo 'Plus que <strong>' . esc_html(wc_price($missing)) . '</strong>.';
			echo '</div>';
		}
	}, 9);

	/**
	 * 4) Onglets : renommer/réordonner + ajouter un onglet
	 */
	add_filter('woocommerce_product_tabs', function (array $tabs): array {

		if (isset($tabs['description']['title'])) {
			$tabs['description']['title'] = 'Détails';
			$tabs['description']['priority'] = 10;
		}

		if (isset($tabs['additional_information']['title'])) {
			$tabs['additional_information']['title'] = 'Caractéristiques';
			$tabs['additional_information']['priority'] = 20;
		}

		if (isset($tabs['reviews']['priority'])) {
			$tabs['reviews']['priority'] = 30;
		}

		$tabs['bpcab_shipping_returns'] = [
			'title'    => 'Livraison & retours',
			'priority' => 25,
			'callback' => function () {
				echo '<h2>Livraison & retours</h2>';
				echo '<p>Expédition sous 24/48h ouvrées. Retours sous 30 jours (produit non utilisé).</p>';
				echo '<p>Pour les produits volumineux, un transporteur peut vous contacter.</p>';
			},
		];

		return $tabs;
	}, 20);

	/**
	 * 5) CTA après le bouton "Ajouter au panier"
	 */
	add_action('woocommerce_after_add_to_cart_button', function () {
		if (!is_product()) {
			return;
		}

		$url = home_url('/guide-des-tailles/');

		echo '<div class="bpcab-after-cart-cta">';
		echo '<a class="bpcab-cta-link" href="' . esc_url($url) . '">Voir le guide des tailles</a>';
		echo '</div>';
	}, 20);

	/**
	 * 6) Champ hidden (exemple technique)
	 */
	add_action('woocommerce_before_add_to_cart_button', function () {
		if (!is_product()) {
			return;
		}

		echo '<input type="hidden" name="bpcab_source" value="single_product" />';
	}, 20);

	/**
	 * 7) CSS : chargé uniquement sur la page produit
	 */
	add_action('wp_enqueue_scripts', function () {

		if (!function_exists('is_product') || !is_product()) {
			return;
		}

		$css_url  = content_url('mu-plugins/assets/bpcab-wc-single.css');
		$css_path = WP_CONTENT_DIR . '/mu-plugins/assets/bpcab-wc-single.css';

		$ver = file_exists($css_path) ? (string) filemtime($css_path) : null;

		wp_enqueue_style('bpcab-wc-single', $css_url, [], $ver);
	}, 20);

	/**
	 * 8) Shortcode de secours (utile avec certains builders)
	 */
	add_shortcode('bpcab_reassurance', function (): string {
		if (!function_exists('is_product') || !is_product()) {
			return '';
		}

		ob_start();
		echo '<div class="bpcab-reassurance" role="note">';
		echo '<strong>Expédition 24/48h</strong><br>';
		echo '<span>Retours 30 jours • Paiement sécurisé</span>';
		echo '</div>';
		return (string) ob_get_clean();
	});

}, 20);

Explication du code

Pourquoi un MU-plugin

Un MU-plugin est chargé automatiquement par WordPress, sans écran d’activation. Pour une boutique, c’est un avantage : même si quelqu’un “désactive un plugin par erreur”, votre personnalisation critique reste en place.

Pourquoi plugins_loaded

WooCommerce doit être chargé. Si vous utilisez des fonctions/classes WooCommerce trop tôt, vous aurez des fatal errors. plugins_loaded est un point d’équilibre simple et fiable.

Réordonner avec remove/add + priorités

woocommerce_single_product_summary est une action qui “déroule” plusieurs fonctions dans un ordre défini par leur priorité (un nombre). Plus le nombre est petit, plus ça s’affiche tôt.

Le mécanisme :

  • remove_action(hook, fonction, priorité) enlève une fonction déjà attachée.
  • add_action(hook, fonction, priorité) la remet ailleurs.

Erreur classique : retirer une action avec la mauvaise priorité. Si WooCommerce a attaché la fonction à 10 et que vous faites remove_action(..., 20), ça ne supprime rien. Résultat : vous avez des doublons.

Onglets via woocommerce_product_tabs

C’est un filtre : vous recevez un tableau $tabs. Chaque onglet a :

  • title : le libellé
  • priority : l’ordre
  • callback : la fonction qui affiche le contenu

Vous retournez le tableau modifié. Si vous oubliez le return, vous cassez les onglets.

CSS via enqueue (et pas en dur)

Charger le CSS proprement évite les surprises de cache et les collisions. En plus, avec filemtime(), vous forcez le rechargement quand vous modifiez le fichier.

Vérification finale

Faites ces tests, dans cet ordre (ça évite de chercher 2h un problème de cache) :

  1. Ouvrez une fiche produit en navigation privée (cache navigateur réduit).
  2. Vérifiez l’ordre : titre → prix → note → bandeau → bouton panier → extrait → meta.
  3. Vérifiez que l’onglet “Livraison & retours” apparaît.
  4. Testez un produit variable : le message “livraison offerte dès…” doit se baser sur le prix min.
  5. Inspectez la page : le CSS bpcab-wc-single.css est bien chargé.
  6. Testez l’ajout au panier : aucune erreur, pas de champ visible (hidden).

Si ça ne marche pas

Voici un tableau de diagnostic (les cas que je vois le plus souvent en dépannage WooCommerce).

Symptôme Cause probable Vérification Solution
Rien ne change sur la fiche produit Le code est au mauvais endroit ou MU-plugin non chargé Vérifiez que le fichier est bien dans /wp-content/mu-plugins/ (pas dans plugins) Déplacez le fichier, puis rechargez. Regardez aussi wp-content/debug.log
Le prix/avis s’affiche en double remove_action avec mauvaise priorité Recherchez la priorité d’origine (souvent 10) Corrigez la priorité dans remove_action
Le CSS ne charge pas Mauvaise URL ou cache agressif Ouvrez l’onglet Network (DevTools) et cherchez bpcab-wc-single.css Vérifiez content_url(), le chemin du fichier, puis videz cache plugin/CDN
Les hooks ne semblent pas appelés Template produit remplacé par Divi/Elementor/Avada Désactivez le template builder sur staging Utilisez le shortcode ou réactivez les templates WooCommerce natifs
Erreur 500 / page blanche Erreur PHP (parenthèse/point-virgule) ou PHP trop ancien Consultez les logs serveur + debug.log Corrigez la syntaxe, vérifiez PHP 8.1+ et désactivez temporairement le MU-plugin

Debug WordPress (sur staging)

Si vous devez activer le debug, suivez la doc officielle et ne laissez pas l’affichage d’erreurs en production : Debugging in WordPress.

Pièges et erreurs courantes

Erreur Cause Solution
Coller le code dans le mauvais fichier Confusion entre thème parent, thème enfant, plugin, MU-plugin Utilisez un MU-plugin pour les snippets “métier” WooCommerce
Oublier un point-virgule Copier-coller incomplet Relisez la ligne précédente, vérifiez les logs PHP
Confondre action et filtre Vous faites un echo dans un filtre ou vous oubliez return Action = affiche/exécute. Filtre = modifie et retourne
Utiliser un hook inadapté Vous accrochez un bloc “après le panier” sur un hook “avant résumé” Revenez à woocommerce_single_product_summary et ajustez la priorité
Priorité de hook mal choisie Le thème ajoute aussi des actions au même hook Changez la priorité (ex : 8, 9, 31) jusqu’à obtenir l’ordre voulu
Cache non vidé Plugin cache, cache serveur, CDN Videz cache plugin + serveur + CDN. Testez en navigation privée
Tester en production sans sauvegarde Pression, “juste un petit snippet” Staging + sauvegarde. Toujours
Snippet cassé par un plugin de snippets Deux versions du même code actives Désactivez l’une des sources (MU-plugin OU plugin snippets, pas les deux)
Code d’un ancien tutoriel incompatible Fonctions obsolètes, logique ancienne, PHP trop vieux Ciblez WP 6.9.4 + PHP 8.1+. Évitez les snippets non datés

Variante / alternative

Alternative 1 : override de template (thème enfant)

Quand vous devez changer le HTML structurel (wrappers, colonnes, placement exact), les hooks ne suffisent plus. Là, vous passez par un override de template WooCommerce dans le thème enfant.

Chemin typique :

  • source : wp-content/plugins/woocommerce/templates/content-single-product.php
  • cible : wp-content/themes/votre-theme-enfant/woocommerce/content-single-product.php

Risque : à chaque mise à jour WooCommerce, vous devez vérifier les changements de template. WooCommerce affiche généralement un avertissement dans WooCommerce > État si vos overrides sont obsolètes.

Référence : WooCommerce Template Structure.

Alternative 2 : utiliser un plugin (quand c’est plus raisonnable)

Si votre besoin devient “éditeur visuel de fiche produit”, un plugin/builder WooCommerce peut être plus rentable en temps (et parfois plus sûr si bien maintenu). Dans ce cas, choisissez un plugin très utilisé, maintenu, et testez l’impact performance.

Conseils sécurité, performance et maintenance

  • Ne modifiez jamais le core (WordPress/WooCommerce). Vos changements seront écrasés à la mise à jour.
  • Échappez vos sorties : esc_url(), esc_html(), et si vous affichez du HTML “autorisé”, wp_kses_post(). Référence : Data Validation / Escaping.
  • Limitez vos enqueues : CSS/JS uniquement sur is_product(). Ça se ressent sur les Core Web Vitals.
  • Documentez vos priorités : quand vous reviendrez dans 6 mois, vous serez content de comprendre pourquoi le prix est à 6 et pas à 11.
  • Surveillez les overrides si vous en créez : WooCommerce évolue. Un override non maintenu est une source classique de bugs checkout/produit.
  • Compat PHP : si votre hébergeur repasse en PHP 8.0 par erreur, certains patterns peuvent casser. Vérifiez la version PHP dans votre panel. Référence PHP : PHP Supported Versions.

Ressources

FAQ

Est-ce vraiment “sans plugin” si j’utilise un MU-plugin ?

Oui dans l’esprit “sans extension tierce”. Un MU-plugin est du code que vous possédez, sans dépendance externe. Techniquement, c’est un plugin, mais pas un plugin installé depuis un marketplace.

Pourquoi éviter de mettre ce code dans functions.php ?

Parce que vous liez votre logique WooCommerce à votre thème. Si vous changez de thème (ou si un builder remplace le template), vous perdez vos personnalisations. En dépannage, je récupère souvent des boutiques où le thème a été changé et tout le comportement produit a disparu.

Mes modifications ne s’affichent pas avec Elementor/Divi, que faire ?

Utilisez le shortcode fourni (ex : [bpcab_reassurance]) et placez-le dans le template builder. Sinon, vérifiez que votre template builder appelle bien les sections WooCommerce natives.

Comment trouver la “bonne priorité” d’un hook ?

Vous partez des valeurs WooCommerce par défaut (souvent 5, 10, 20, 30, 40) et vous insérez entre. Si votre thème ajoute des blocs, vous ajustez. Quand l’ordre semble impossible, c’est souvent un template remplacé.

Puis-je supprimer totalement les onglets et afficher tout en une seule colonne ?

Oui, mais là vous entrez souvent dans une refonte de structure HTML. Les hooks peuvent suffire (vous affichez le contenu ailleurs et vous désactivez les tabs), mais un override de template devient parfois plus propre.

Le message “livraison offerte dès…” doit tenir compte des promos, c’est le cas ?

Le code utilise get_price() (prix actif) et, pour un variable, le prix min via get_variation_price('min', true). En général, ça inclut les prix promo. Si vous avez des règles de prix complexes (B2B), il faut adapter selon votre logique.

Est-ce que ce code ralentit ma boutique ?

Non de manière notable : ce sont quelques hooks et un CSS léger, et le CSS est chargé uniquement sur is_product(). Le vrai risque performance vient plutôt des plugins lourds et des builders mal optimisés.

Je vois “wc_price()” affiché avec des caractères bizarres, pourquoi ?

Parce que wc_price() renvoie du HTML. Si vous l’échappez avec esc_html(), vous transformez le HTML en texte. Remplacez esc_html(wc_price(...)) par wp_kses_post(wc_price(...)) si vous voulez conserver le markup.

Comment annuler rapidement si j’ai cassé la fiche produit ?

Renommez temporairement le fichier MU-plugin (ex : ajoutez .off) via FTP/SSH. WordPress ne le chargera plus. Ensuite corrigez calmement sur staging.

Dois-je régénérer les permaliens après ces changements ?

Non, pas pour des hooks de rendu. Mais si vous constatez des 404 étranges après une migration, allez dans Réglages > Permaliens et cliquez “Enregistrer” (sans changer). C’est un réflexe utile en dépannage.

Où trouver la liste des templates WooCommerce pour faire un override propre ?

Le dépôt officiel WooCommerce sur GitHub expose les templates : Templates WooCommerce (GitHub). Utilisez-le comme référence de vérité, pas un vieux tutoriel non daté.