Intermediaire 2 min de lecture · 396 mots

Créer des shortcodes WordPress personnalisés : Guide complet

Estimated reading time: 2 minutes

Introduction

Les shortcodes WordPress sont des marqueurs textuels puissants qui permettent d’insérer du contenu dynamique et des fonctionnalités complexes directement dans vos articles, pages ou widgets. Introduits dans WordPress 2.5, ils constituent un pont élégant entre le contenu statique et les fonctionnalités PHP avancées.

Dans ce guide complet, nous explorerons l’architecture des shortcodes, leur création, leur sécurisation et leur optimisation pour des applications professionnelles.

Table des matières

  • Comprendre l’architecture des shortcodes
  • Créer un shortcode simple
  • Shortcodes avec attributs
  • Shortcodes avec contenu encapsulé
  • Shortcodes imbriqués et auto-fermants
  • Sécurité et validation des données
  • Optimisation des performances
  • Cas d’usage avancés
  • Debugging et résolution de problèmes
  • Bonnes pratiques professionnelles
  • 1. Comprendre l’architecture des shortcodes

    Le cycle de vie d’un shortcode

    Lorsque WordPress génère une page, il passe par plusieurs phases de traitement du contenu :

Récupération du contenu depuis la BDD
         ↓
Application des filtres 'thecontent'
         ↓
Parsing et détection des shortcodes
         ↓
Exécution des callbacks associés
         ↓
Remplacement des balises par le contenu généré
         ↓
Affichage final

Structure interne

WordPress utilise une expression régulière pour identifier les shortcodes dans le contenu :

// Pattern simplifié utilisé par WordPress
$pattern = '/[(w+)([^]])](?:(.?)[/1])?/s';

Les shortcodes sont stockés dans un tableau global $shortcodetags qui associe chaque nom de shortcode à sa fonction callback.

2. Créer un shortcode simple

Exemple basique : Affichage de la date du jour


  Shortcode simple pour afficher la date actuelle
 
  @return string Date formatée
 /
function yawcdateshortcode() {
    // Récupérer le format de date WordPress
    $dateformat = getoption('dateformat');

    // Générer la date localisée
    $currentdate = datei18n($dateformat);

    // Retourner le HTML (ne jamais faire echo dans un shortcode)
    return sprintf(
        '',
        date('c'), // Format ISO 8601
        eschtml($currentdate)
    );
}
addshortcode('dateactuelle', 'yawcdateshortcode');

Utilisation :

[dateactuelle]

Points importants :

  • Toujours RETOURNER le contenu, jamais utiliser echo
  • Échapper les données avec eschtml(), escattr(), etc.
  • Utiliser datei18n() pour la localisation
  • Ajouter des classes CSS pour le styling
  • Shortcode avec buffer output

    Si vous devez intégrer du code complexe avec des templates :

    weatherwidgetshortcode() {
        // Démarrer le buffer de sortie
        obstart();
    
        // Inclure un template
        include locatetemplate('template-parts/weather-widget.php');
    
        // Récupérer et nettoyer le buffer
        $output = obgetclean();
    
        return $output;
    }
    addshortcode('meteo', 'yawcweatherwidgetshortcode');
    

    3. Shortcodes avec attributs

    Gestion avancée des attributs

    
      Shortcode pour afficher les derniers articles
     
      @param array $atts Attributs du shortcode
      @return string HTML généré
     /
    function yawcrecentpostsshortcode($atts) {
        // Définir les valeurs par défaut et fusionner avec les attributs fournis
        $atts = shortcodeatts(
            array(
                'nombre'     => 5,
                'categorie'  => '',
                'ordre'      => 'DESC',
                'orderby'    => 'date',
                'exclu'      => '', // IDs à exclure
                'afficher'   => 'titre,extrait,image', // Éléments à afficher
                'classe'     => '',
            ),
            $atts,
            'articlesrecents' // Tag du shortcode pour le filtrage
        );
    
        // Valider et nettoyer les entrées
        $nombre = absint($atts['nombre']);
        $nombre = min($nombre, 50); // Limiter à 50 pour les performances
    
        $ordre = inarray(strtoupper($atts['ordre']), array('ASC', 'DESC'))
            ? strtoupper($atts['ordre'])
            : 'DESC';
    
        // Liste blanche des orderby autorisés
        $orderbyallowed = array('date', 'title', 'rand', 'commentcount', 'modified');
        $orderby = inarray($atts['orderby'], $orderbyallowed)
            ? $atts['orderby']
            : 'date';
    
        // Construire les arguments de requête
        $args = array(
            'postsperpage'      => $nombre,
            'order'               => $ordre,
            'orderby'             => $orderby,
            'ignorestickyposts' => true,
            'nofoundrows'       => true, // Performance : pas besoin de pagination
        );
    
        // Ajouter le filtre de catégorie si spécifié
        if (!empty($atts['categorie'])) {
            $args['categoryname'] = sanitizetextfield($atts['categorie']);
        }
    
        // Gérer les exclusions
        if (!empty($atts['exclu'])) {
            $excluids = arraymap('absint', explode(',', $atts['exclu']));
            $args['postnotin'] = $excluids;
        }
    
        // Exécuter la requête
        $query = new WPQuery($args);
    
        if (!$query->haveposts()) {
            return '

    Aucun article trouvé.

    '; } // Déterminer quels éléments afficher $afficher = arraymap('trim', explode(',', $atts['afficher'])); // Construire le HTML $classecontainer = 'yawc-recent-posts'; if (!empty($atts['classe'])) { $classecontainer .= ' ' . sanitizehtmlclass($atts['classe']); } obstart(); ?>
    haveposts()) : $query->thepost(); ?>
    resetpostdata(); return obgetclean(); } addshortcode('articlesrecents', 'yawcrecentpostsshortcode');

    Exemples d’utilisation :

    [articlesrecents nombre="3"]
    
    [articlesrecents nombre="5" categorie="tutoriels" ordre="ASC"]
    
    [articlesrecents nombre="4" afficher="titre,image,date" classe="grid-layout"]
    
    [articlesrecents orderby="rand" nombre="3" exclu="15,28,42"]
    

    Filtrage des attributs

    Permettre aux développeurs de modifier les attributs :

    atts()
    $atts = applyfilters('yawcrecentpostsatts', $atts);
    

    Utilisation du filtre :

    filter('yawcrecentpostsatts', function($atts) {
        // Forcer l'ordre aléatoire sur la page d'accueil
        if (isfrontpage()) {
            $atts['orderby'] = 'rand';
        }
        return $atts;
    });
    

    4. Shortcodes avec contenu encapsulé

    Box de mise en évidence

    
      Shortcode pour créer des boîtes stylisées
     
      @param array  $atts    Attributs
      @param string $content Contenu entre les balises
      @return string HTML généré
     /
    function yawcboxshortcode($atts, $content = null) {
        $atts = shortcodeatts(array(
            'type'   => 'info',  // info, warning, success, error
            'titre'  => '',
            'icone'  => 'true',
            'fermable' => 'false',
        ), $atts, 'box');
    
        // Validation du type
        $typesvalides = array('info', 'warning', 'success', 'error');
        $type = inarray($atts['type'], $typesvalides) ? $atts['type'] : 'info';
    
        // Icônes par type
        $icones = array(
            'info'    => '',
            'warning' => '',
            'success' => '',
            'error'   => '',
        );
    
        // Traiter le contenu (important pour les shortcodes imbriqués)
        $content = doshortcode($content);
    
        // Construire le HTML
        $html = sprintf(
            '';
    
        return $html;
    }
    addshortcode('box', 'yawcboxshortcode');
    

    Utilisation :

    [box type="warning" titre="Attention"]
    Cette fonctionnalité est en version bêta.
    [/box]
    
    [box type="success" icone="true" fermable="true"]
    Votre inscription a été enregistrée avec succès !
    [/box]
    

    CSS associé

    / yawc-shortcodes.css /
    .yawc-box {
        position: relative;
        padding: 1.5rem;
        margin: 1.5rem 0;
        border-radius: 8px;
        border-left: 4px solid;
    }
    
    .yawc-box-info {
        background-color: #e3f2fd;
        border-left-color: #2196f3;
        color: #0d47a1;
    }
    
    .yawc-box-warning {
        background-color: #fff3e0;
        border-left-color: #ff9800;
        color: #e65100;
    }
    
    .yawc-box-success {
        background-color: #e8f5e9;
        border-left-color: #4caf50;
        color: #1b5e20;
    }
    
    .yawc-box-error {
        background-color: #ffebee;
        border-left-color: #f44336;
        color: #b71c1c;
    }
    
    .yawc-box-header {
        display: flex;
        align-items: center;
        gap: 0.75rem;
        margin-bottom: 0.75rem;
    }
    
    .yawc-box-title {
        margin: 0;
        font-size: 1.1rem;
        font-weight: 600;
    }
    
    .yawc-icon {
        width: 24px;
        height: 24px;
        fill: currentColor;
    }
    
    .yawc-box-close {
        position: absolute;
        top: 0.5rem;
        right: 0.5rem;
        background: none;
        border: none;
        font-size: 1.5rem;
        cursor: pointer;
        opacity: 0.6;
        transition: opacity 0.2s;
    }
    
    .yawc-box-close:hover {
        opacity: 1;
    }
    
    .yawc-box-content p:last-child {
        margin-bottom: 0;
    }
    

    JavaScript pour les boîtes fermables

    // yawc-shortcodes.js
    document.addEventListener('DOMContentLoaded', function() {
        const closeButtons = document.querySelectorAll('.yawc-box-close');
    
        closeButtons.forEach(button => {
            button.addEventListener('click', function() {
                const box = this.closest('.yawc-box');
                box.style.transition = 'opacity 0.3s, transform 0.3s';
                box.style.opacity = '0';
                box.style.transform = 'translateY(-10px)';
    
                setTimeout(() => {
                    box.remove();
                }, 300);
            });
        });
    });
    

    5. Shortcodes imbriqués et auto-fermants

    Système de tabs avec imbrication

    
      Shortcode container pour les onglets
     /
    function yawctabsshortcode($atts, $content = null) {
        static $tabgroupid = 0;
        $tabgroupid++;
    
        $atts = shortcodeatts(array(
            'style' => 'default', // default, pills, underline
        ), $atts, 'tabs');
    
        // Reset le compteur d'onglets pour chaque groupe
        global $yawctabindex;
        $yawctabindex = 0;
    
        // Stocker l'ID du groupe pour les onglets enfants
        global $yawccurrenttabgroup;
        $yawccurrenttabgroup = $tabgroupid;
    
        // Parser le contenu pour extraire les titres des onglets
        $content = doshortcode($content);
    
        return sprintf(
            '
    %s
    ', esc
    attr($atts['style']), $tabgroupid, $content ); } addshortcode('tabs', 'yawctabsshortcode'); / Shortcode pour un onglet individuel / function yawctabshortcode($atts, $content = null) { global $yawctabindex, $yawccurrenttabgroup; $atts = shortcodeatts(array( 'titre' => 'Onglet ' . ($yawctabindex + 1), 'icone' => '', 'actif' => 'false', ), $atts, 'tab'); $isactive = ($yawctabindex === 0 || $atts['actif'] === 'true'); $yawctabindex++; $tabid = sprintf('tab-%d-%d', $yawccurrenttabgroup, $yawctabindex); $panelid = sprintf('panel-%d-%d', $yawccurrenttabgroup, $yawctabindex); // Construire le bouton d'onglet (sera repositionné par JS) $tabbutton = sprintf( '', $isactive ? ' active' : '', $isactive ? 'true' : 'false', escattr($panelid), escattr($tabid), $yawctabindex, !empty($atts['icone']) ? '' . $atts['icone'] . '' : '', eschtml($atts['titre']) ); // Construire le panneau de contenu $tabpanel = sprintf( '
    %s
    ', $is
    active ? ' active' : '', escattr($tabid), escattr($panelid), $yawctabindex, doshortcode($content) ); // Retourner le bouton + panneau (le JS réorganisera) return sprintf( '
    %s
    ', esc
    attr($tabbutton), $tabpanel ); } addshortcode('tab', 'yawctabshortcode');

    JavaScript pour les tabs

    // yawc-tabs.js
    class YAWCTabs {
        constructor(container) {
            this.container = container;
            this.tabGroup = container.dataset.tabGroup;
            this.init();
        }
    
        init() {
            // Créer la liste des onglets
            const tabList = document.createElement('div');
            tabList.className = 'yawc-tab-list';
            tabList.setAttribute('role', 'tablist');
    
            // Extraire tous les boutons et les ajouter à la liste
            const wrappers = this.container.querySelectorAll('.yawc-tab-wrapper');
            const panels = [];
    
            wrappers.forEach(wrapper => {
                const buttonHTML = wrapper.dataset.tabButton;
                const panel = wrapper.querySelector('.yawc-tab-panel');
    
                // Créer le bouton depuis le HTML stocké
                const temp = document.createElement('div');
                temp.innerHTML = buttonHTML;
                const button = temp.firstChild;
    
                tabList.appendChild(button);
                panels.push(panel);
    
                // Ajouter l'écouteur d'événements
                button.addEventListener('click', () => this.switchTab(button, panel));
            });
    
            // Réorganiser le DOM
            this.container.innerHTML = '';
            this.container.appendChild(tabList);
    
            const panelsContainer = document.createElement('div');
            panelsContainer.className = 'yawc-tab-panels';
            panels.forEach(panel => panelsContainer.appendChild(panel));
    
            this.container.appendChild(panelsContainer);
    
            // Ajouter la navigation au clavier
            this.addKeyboardNavigation(tabList);
        }
    
        switchTab(button, panel) {
            // Désactiver tous les onglets
            this.container.querySelectorAll('.yawc-tab-button').forEach(btn => {
                btn.classList.remove('active');
                btn.setAttribute('aria-selected', 'false');
            });
    
            this.container.querySelectorAll('.yawc-tab-panel').forEach(pnl => {
                pnl.classList.remove('active');
            });
    
            // Activer l'onglet sélectionné
            button.classList.add('active');
            button.setAttribute('aria-selected', 'true');
            panel.classList.add('active');
    
            // Focus sur le panneau pour l'accessibilité
            panel.focus();
        }
    
        addKeyboardNavigation(tabList) {
            const buttons = tabList.querySelectorAll('.yawc-tab-button');
    
            buttons.forEach((button, index) => {
                button.addEventListener('keydown', (e) => {
                    let targetIndex = index;
    
                    if (e.key === 'ArrowRight') {
                        targetIndex = (index + 1) % buttons.length;
                    } else if (e.key === 'ArrowLeft') {
                        targetIndex = (index - 1 + buttons.length) % buttons.length;
                    } else if (e.key === 'Home') {
                        targetIndex = 0;
                    } else if (e.key === 'End') {
                        targetIndex = buttons.length - 1;
                    } else {
                        return;
                    }
    
                    e.preventDefault();
                    buttons[targetIndex].click();
                    buttons[targetIndex].focus();
                });
            });
        }
    }
    
    // Initialiser tous les groupes d'onglets
    document.addEventListener('DOMContentLoaded', () => {
        document.querySelectorAll('.yawc-tabs').forEach(container => {
            new YAWCTabs(container);
        });
    });
    

    Utilisation :

    [tabs style="pills"]
        [tab titre="Introduction" actif="true"]
        Contenu de l'introduction avec du Markdown si vous utilisez un parser.
        [/tab]
    
        [tab titre="Configuration"]
        Instructions de configuration détaillées.
        [/tab]
    
        [tab titre="Exemples de code"]
        

    php
    echo « Hello World »;

        [/tab]
    [/tabs]
    

    6. Sécurité et validation des données

    Principes essentiels

  • Toujours échapper les sorties
  • Valider et nettoyer les entrées
  • Utiliser des nonces pour les actions sensibles
  • Limiter les capacités utilisateur
  • Shortcode sécurisé avec formulaire

    
      Shortcode de formulaire de contact sécurisé
     /
    function yawccontactformshortcode($atts) {
        $atts = shortcodeatts(array(
            'email' => getoption('adminemail'),
            'objet' => 'Nouveau message depuis le site',
            'redirect' => '',
        ), $atts, 'contactform');
    
        // Valider l'email
        $emaildestination = isemail($atts['email']) ? $atts['email'] : getoption('adminemail');
    
        // Générer un ID unique pour ce formulaire
        $formid = 'yawc-contact-' . wpgeneratepassword(8, false);
    
        // Traitement du formulaire si soumis
        $messagesent = false;
        $errormessage = '';
    
        if (isset($POST['yawccontactsubmit']) &&
            isset($POST['yawcformid']) &&
            $POST['yawcformid'] === $formid) {
    
            // Vérifier le nonce
            if (!isset($POST['yawccontactnonce']) ||
                !wpverifynonce($POST['yawccontactnonce'], 'yawccontactaction')) {
                $errormessage = 'Erreur de sécurité. Veuillez réessayer.';
            } else {
                // Valider et nettoyer les données
                $nom = isset($POST['nom']) ? sanitizetextfield($POST['nom']) : '';
                $email = isset($POST['email']) ? sanitizeemail($POST['email']) : '';
                $sujet = isset($POST['sujet']) ? sanitizetextfield($POST['sujet']) : '';
                $message = isset($POST['message']) ? sanitizetextareafield($POST['message']) : '';
    
                // Validation
                $errors = array();
    
                if (empty($nom)) {
                    $errors[] = 'Le nom est requis.';
                }
    
                if (empty($email) || !isemail($email)) {
                    $errors[] = 'Une adresse email valide est requise.';
                }
    
                if (empty($message)) {
                    $errors[] = 'Le message est requis.';
                }
    
                // Protection anti-spam basique
                if (isset($POST['website']) && !empty($POST['website'])) {
                    // Champ honeypot rempli = spam
                    $errors[] = 'Détection de spam.';
                }
    
                if (empty($errors)) {
                    // Préparer l'email
                    $to = $emaildestination;
                    $subject = $atts['objet'] . ' - ' . $sujet;
    
                    $body = sprintf(
                        "Nouveau message de contactnn" .
                        "Nom: %sn" .
                        "Email: %sn" .
                        "Sujet: %snn" .
                        "Message:n%snn" .
                        "---n" .
                        "Envoyé depuis: %s",
                        $nom,
                        $email,
                        $sujet,
                        $message,
                        homeurl()
                    );
    
                    $headers = array(
                        'Reply-To: ' . $nom . ' <' . $email . '>',
                        'Content-Type: text/plain; charset=UTF-8'
                    );
    
                    // Envoyer l'email
                    if (wpmail($to, $subject, $body, $headers)) {
                        $messagesent = true;
    
                        // Hook pour les actions post-envoi
                        doaction('yawccontactformsent', array(
                            'nom' => $nom,
                            'email' => $email,
                            'sujet' => $sujet,
                            'message' => $message,
                        ));
    
                        // Redirection si configurée
                        if (!empty($atts['redirect'])) {
                            wpsaferedirect(escurl($atts['redirect']));
                            exit;
                        }
                    } else {
                        $errormessage = 'Erreur lors de l'envoi du message. Veuillez réessayer.';
                    }
                } else {
                    $errormessage = implode('
    ', $errors); } } } // Afficher le formulaire ob
    start(); ?>
    sent) : ?> message)) : ?>
    noncefield('yawccontactaction', 'yawccontactnonce'); ?>
    getclean(); } addshortcode('contactform', 'yawccontactformshortcode');

    Protection contre les injections

    
      Exemple de validation stricte pour un shortcode de recherche
     /
    function yawcsearchshortcode($atts) {
        $atts = shortcodeatts(array(
            'placeholder' => 'Rechercher...',
            'buttontext' => 'Rechercher',
            'posttype'   => 'post',
        ), $atts, 'search');
    
        // Valider le posttype contre une liste blanche
        $allowedposttypes = getposttypes(array('public' => true), 'names');
        $posttypes = arraymap('trim', explode(',', $atts['posttype']));
        $posttypes = arrayfilter($posttypes, function($pt) use ($allowedposttypes) {
            return inarray($pt, $allowedposttypes, true);
        });
    
        if (empty($posttypes)) {
            $posttypes = array('post');
        }
    
        obstart();
        ?>
        
        getclean();
    }
    addshortcode('search', 'yawcsearchshortcode');
    

    7. Optimisation des performances

    Mise en cache des shortcodes

    
      Shortcode avec mise en cache transient
     /
    function yawccachedstatsshortcode($atts) {
        $atts = shortcodeatts(array(
            'cacheduration' => 3600, // 1 heure par défaut
        ), $atts, 'stats');
    
        // Créer une clé de cache unique basée sur les attributs
        $cachekey = 'yawcstats' . md5(serialize($atts));
    
        // Essayer de récupérer depuis le cache
        $cachedoutput = gettransient($cachekey);
    
        if ($cachedoutput !== false) {
            return $cachedoutput . '';
        }
    
        // Générer le contenu (opération coûteuse)
        $output = yawcgeneratestatshtml();
    
        // Mettre en cache
        settransient($cachekey, $output, absint($atts['cacheduration']));
    
        return $output;
    }
    addshortcode('stats', 'yawccachedstatsshortcode');
    
    /
      Nettoyer le cache lors de la publication d'un article
     /
    addaction('savepost', function($postid) {
        if (wpispostrevision($postid) || wpispostautosave($postid)) {
            return;
        }
    
        // Supprimer tous les transients de stats
        global $wpdb;
        $wpdb->query(
            "DELETE FROM $wpdb->options
             WHERE optionname LIKE 'transientyawcstats%'
             OR optionname LIKE 'transienttimeoutyawcstats%'"
        );
    });
    
    function yawcgeneratestatshtml() {
        global $wpdb;
    
        $stats = array(
            'posts' => wpcountposts('post')->publish,
            'pages' => wpcountposts('page')->publish,
            'comments' => wpcountcomments()->approved,
            'users' => countusers()['totalusers'],
        );
    
        obstart();
        ?>
        
    formati18n($stats['posts']); ?> Articles
    formati18n($stats['pages']); ?> Pages
    formati18n($stats['comments']); ?> Commentaires
    formati18n($stats['users']); ?> Utilisateurs
    getclean(); }

    Chargement conditionnel des assets

    
      Enregistrer les assets mais ne les charger que si le shortcode est présent
     /
    class YAWCShortcodeAssets {
        private static $shortcodesfound = array();
    
        public static function init() {
            addaction('wpenqueuescripts', array(CLASS, 'registerassets'));
            addfilter('thecontent', array(CLASS, 'detectshortcodes'), 1);
            addaction('wpfooter', array(CLASS, 'enqueuedetectedassets'));
        }
    
        public static function registerassets() {
            // Enregistrer mais ne pas charger
            wpregisterstyle(
                'yawc-tabs',
                pluginsurl('assets/css/tabs.css', FILE),
                array(),
                '1.0.0'
            );
    
            wpregisterscript(
                'yawc-tabs',
                pluginsurl('assets/js/tabs.js', FILE),
                array(),
                '1.0.0',
                true
            );
    
            wpregisterstyle(
                'yawc-box',
                pluginsurl('assets/css/box.css', FILE),
                array(),
                '1.0.0'
            );
    
            wpregisterscript(
                'yawc-box',
                pluginsurl('assets/js/box.js', FILE),
                array(),
                '1.0.0',
                true
            );
        }
    
        public static function detectshortcodes($content) {
            // Détecter les shortcodes dans le contenu
            if (hasshortcode($content, 'tabs')) {
                self::$shortcodesfound[] = 'tabs';
            }
    
            if (hasshortcode($content, 'box')) {
                self::$shortcodesfound[] = 'box';
            }
    
            return $content;
        }
    
        public static function enqueuedetectedassets() {
            // Charger uniquement les assets nécessaires
            foreach (arrayunique(self::$shortcodesfound) as $shortcode) {
                if (wpstyleis($shortcode, 'registered')) {
                    wpenqueuestyle('yawc-' . $shortcode);
                }
    
                if (wpscriptis($shortcode, 'registered')) {
                    wpenqueuescript('yawc-' . $shortcode);
                }
            }
        }
    }
    
    YAWCShortcodeAssets::init();
    

    8. Cas d’usage avancés

    Shortcode avec requête Ajax

    
      Shortcode de chargement de contenu Ajax
     /
    function yawcajaxloadershortcode($atts) {
        $atts = shortcodeatts(array(
            'action'      => '',
            'buttontext' => 'Charger plus',
            'container'   => 'posts',
        ), $atts, 'ajaxloader');
    
        if (empty($atts['action'])) {
            return '

    Erreur: action requise

    '; } $loader
    id = 'ajax-loader-' . wpgeneratepassword(8, false); // Passer les données au JavaScript wplocalizescript('yawc-ajax-loader', 'yawcAjaxLoader', array( 'ajaxurl' => adminurl('admin-ajax.php'), 'nonce' => wpcreatenonce('yawcajaxloader'), )); obstart(); ?>
    getclean(); } addshortcode('ajaxloader', 'yawcajaxloadershortcode'); / Handler Ajax pour charger plus d'articles / function yawcloadmorepostsajax() { checkajaxreferer('yawcajaxloader', 'nonce'); $page = isset($POST['page']) ? absint($POST['page']) : 1; $args = array( 'posttype' => 'post', 'postsperpage' => 6, 'paged' => $page, 'poststatus' => 'publish', ); $query = new WPQuery($args); if (!$query->haveposts()) { wpsendjsonerror(array('message' => 'Aucun article supplémentaire')); } obstart(); while ($query->haveposts()) { $query->thepost(); ?> resetpostdata(); $html = obgetclean(); wpsendjsonsuccess(array( 'html' => $html, 'hasmore' => $page < $query->maxnumpages, 'nextpage' => $page + 1, )); } addaction('wpajaxloadmoreposts', 'yawcloadmorepostsajax'); addaction('wpajaxnoprivloadmoreposts', 'yawcloadmorepostsajax');

    JavaScript correspondant:

    // yawc-ajax-loader.js
    (function($) {
        'use strict';
    
        class AjaxLoader {
            constructor(element) {
                this.element = $(element);
                this.action = this.element.data('action');
                this.button = this.element.find('.yawc-load-more');
                this.container = this.element.find('.yawc-ajax-content');
                this.spinner = this.element.find('.yawc-ajax-spinner');
                this.page = 1;
    
                this.button.on('click', () => this.loadMore());
            }
    
            loadMore() {
                this.page++;
                this.button.prop('disabled', true);
                this.spinner.show();
    
                $.ajax({
                    url: yawcAjaxLoader.ajaxurl,
                    type: 'POST',
                    data: {
                        action: this.action,
                        nonce: yawcAjaxLoader.nonce,
                        page: this.page
                    },
                    success: (response) => {
                        if (response.success) {
                            this.container.append(response.data.html);
    
                            if (!response.data.hasmore) {
                                this.button.text('Aucun article supplémentaire').prop('disabled', true);
                            }
                        } else {
                            alert(response.data.message);
                            this.button.hide();
                        }
                    },
                    error: () => {
                        alert('Erreur lors du chargement');
                    },
                    complete: () => {
                        this.spinner.hide();
                        this.button.prop('disabled', false);
                    }
                });
            }
        }
    
        $(document).ready(function() {
            $('.yawc-ajax-loader').each(function() {
                new AjaxLoader(this);
            });
        });
    
    })(jQuery);
    

    Shortcode avec géolocalisation

    
      Shortcode pour afficher du contenu basé sur la géolocalisation
     /
    function yawcgeocontentshortcode($atts, $content = null) {
        $atts = shortcodeatts(array(
            'pays'   => '',
            'ville'  => '',
            'fallback' => 'true',
        ), $atts, 'geocontent');
    
        // Détecter le pays de l'utilisateur
        $usercountry = yawcgetusercountry();
        $usercity = yawcgetusercity();
    
        $showcontent = false;
    
        if (!empty($atts['pays'])) {
            $allowedcountries = arraymap('trim', explode(',', $atts['pays']));
            $showcontent = inarray($usercountry, $allowedcountries, true);
        }
    
        if (!empty($atts['ville']) && $showcontent) {
            $allowedcities = arraymap('trim', explode(',', $atts['ville']));
            $showcontent = inarray($usercity, $allowedcities, true);
        }
    
        if ($showcontent || $atts['fallback'] === 'true') {
            return doshortcode($content);
        }
    
        return '';
    }
    addshortcode('geocontent', 'yawcgeocontentshortcode');
    
    /
      Obtenir le pays de l'utilisateur via API ou cache
     /
    function yawcgetusercountry() {
        // Vérifier le cache de session
        if (isset($SESSION['yawcusercountry'])) {
            return $SESSION['yawcusercountry'];
        }
    
        $ip = yawcgetuserip();
    
        // Utiliser une API de géolocalisation
        $response = wpremoteget('http://ip-api.com/json/' . $ip);
    
        if (iswperror($response)) {
            return 'UNKNOWN';
        }
    
        $data = jsondecode(wpremoteretrievebody($response), true);
        $country = isset($data['countryCode']) ? $data['countryCode'] : 'UNKNOWN';
    
        // Mettre en cache
        $SESSION['yawcusercountry'] = $country;
    
        return $country;
    }
    
    function yawcgetuserip() {
        $ip = '';
    
        if (!empty($SERVER['HTTPCLIENTIP'])) {
            $ip = $SERVER['HTTPCLIENTIP'];
        } elseif (!empty($SERVER['HTTPXFORWARDEDFOR'])) {
            $ip = $SERVER['HTTPXFORWARDEDFOR'];
        } else {
            $ip = $SERVER['REMOTEADDR'];
        }
    
        return filtervar($ip, FILTERVALIDATEIP) ? $ip : '0.0.0.0';
    }
    

    9. Debugging et résolution de problèmes

    Mode debug pour shortcodes

    
      Classe de debugging pour shortcodes
     /
    class YAWCShortcodeDebug {
        private static $logs = array();
    
        public static function init() {
            if (!defined('WPDEBUG') || !WPDEBUG) {
                return;
            }
    
            addfilter('doshortcodetag', array(CLASS, 'logshortcode'), 10, 4);
            addaction('wpfooter', array(CLASS, 'displaylogs'));
        }
    
        public static function logshortcode($output, $tag, $attr, $m) {
            $starttime = microtime(true);
    
            self::$logs[] = array(
                'tag'        => $tag,
                'attributes' => $attr,
                'outputlength' => strlen($output),
                'time'       => microtime(true) - $starttime,
                'memory'     => memorygetusage(),
            );
    
            return $output;
        }
    
        public static function displaylogs() {
            if (empty(self::$logs)) {
                return;
            }
    
            echo '
    '; echo '

    Shortcode Debug Info

    '; echo ''; echo ''; foreach (self::$logs as $log) { printf( '

    ',
    eschtml($log['tag']),
    esc
    html(printr($log['attributes'], true)),
    $log['output
    length'],
    $log['time'] 1000,
    $log['memory'] / 1024 / 1024
    );
    }

    echo '

    TagAttributesOutput LengthTime (ms)Memory (MB)
    %s
    %s
    %d %.2f %.2f

    ';
    }
    }

    YAWCShortcodeDebug::init();

    ### Vérification des shortcodes non enregistrés

    
      Détecter les shortcodes utilisés mais non enregistrés
     /
    function yawcdetectunregisteredshortcodes($content) {
        if (!isadmin() && currentusercan('manageoptions')) {
            global $shortcodetags;
    
            // Pattern pour trouver tous les shortcodes
            pregmatchall('/[(w+)/', $content, $matches);
    
            if (!empty($matches[1])) {
                $foundshortcodes = arrayunique($matches[1]);
                $unregistered = array();
    
                foreach ($foundshortcodes as $tag) {
                    if (!arraykeyexists($tag, $shortcodetags)) {
                        $unregistered[] = $tag;
                    }
                }
    
                if (!empty($unregistered)) {
                    addaction('wpfooter', function() use ($unregistered) {
                        echo '
    '; echo 'Shortcodes non enregistrés détectés: '; echo implode(', ', arraymap('eschtml', $unregistered)); echo '
    '; }); } } } return $content; } addfilter('thecontent', 'yawcdetectunregisteredshortcodes', 999);

    Tester les shortcodes en isolation

    
      Fonction utilitaire pour tester les shortcodes
     /
    function yawctestshortcode($tag, $atts = array(), $content = null) {
        if (!functionexists('doshortcode')) {
            requireonce ABSPATH . 'wp-includes/shortcodes.php';
        }
    
        $attsstring = '';
        foreach ($atts as $key => $value) {
            $attsstring .= sprintf(' %s="%s"', $key, escattr($value));
        }
    
        if ($content !== null) {
            $shortcodestring = sprintf('[%s%s]%s[/%s]', $tag, $attsstring, $content, $tag);
        } else {
            $shortcodestring = sprintf('[%s%s]', $tag, $attsstring);
        }
    
        echo '### Test: ' . eschtml($shortcodestring) . '';
        echo '
    '; echo doshortcode($shortcodestring); echo '
    '; } // Utilisation dans un template de test if (current
    usercan('manageoptions') && isset($GET['testshortcodes'])) { yawctestshortcode('dateactuelle'); yawctestshortcode('articlesrecents', array('nombre' => 3)); yawctestshortcode('box', array('type' => 'warning'), 'Contenu de test'); }

    10. Bonnes pratiques professionnelles

    Organisation du code

    
      Structure organisée pour un plugin de shortcodes
     /
    
    // Structure de fichiers recommandée:
    /
    /mon-plugin-shortcodes/
    ├── mon-plugin-shortcodes.php (fichier principal)
    ├── includes/
    │   ├── class-shortcode-base.php
    │   ├── shortcodes/
    │   │   ├── class-tabs-shortcode.php
    │   │   ├── class-box-shortcode.php
    │   │   └── class-contact-form-shortcode.php
    │   └── traits/
    │       ├── trait-cacheable.php
    │       └── trait-ajax-handler.php
    ├── assets/
    │   ├── css/
    │   ├── js/
    │   └── images/
    └── templates/
        ├── tabs.php
        ├── box.php
        └── contact-form.php
    /
    
    // Classe de base pour tous les shortcodes
    abstract class YAWCShortcodeBase {
        protected $tag;
        protected $defaults = array();
    
        abstract public function render($atts, $content = null);
    
        public function construct($tag) {
            $this->tag = $tag;
            addshortcode($tag, array($this, 'process'));
        }
    
        public function process($atts, $content = null) {
            $atts = shortcodeatts($this->defaults, $atts, $this->tag);
            $atts = $this->validateatts($atts);
    
            return $this->render($atts, $content);
        }
    
        protected function validateatts($atts) {
            // Validation de base, à surcharger dans les classes enfants
            return $atts;
        }
    
        protected function gettemplate($name, $vars = array()) {
            extract($vars);
    
            $templatepath = plugindirpath(FILE) . 'templates/' . $name . '.php';
    
            if (!fileexists($templatepath)) {
                return '';
            }
    
            obstart();
            include $templatepath;
            return obgetclean();
        }
    }
    
    // Exemple de shortcode utilisant la classe de base
    class YAWCTabsShortcode extends YAWCShortcodeBase {
        protected $defaults = array(
            'style' => 'default',
        );
    
        public function construct() {
            parent::construct('tabs');
        }
    
        protected function validateatts($atts) {
            $validstyles = array('default', 'pills', 'underline');
    
            if (!inarray($atts['style'], $validstyles)) {
                $atts['style'] = 'default';
            }
    
            return $atts;
        }
    
        public function render($atts, $content = null) {
            wpenqueuestyle('yawc-tabs');
            wpenqueuescript('yawc-tabs');
    
            return $this->gettemplate('tabs', array(
                'style'   => $atts['style'],
                'content' => doshortcode($content),
            ));
        }
    }
    
    // Initialisation
    new YAWCTabsShortcode();
    

    Documentation des shortcodes

    
      Ajouter une page d'aide dans l'admin
     /
    function yawcshortcodeshelppage() {
        addmenupage(
            'Shortcodes Disponibles',
            'Shortcodes',
            'editposts',
            'yawc-shortcodes',
            'yawcrendershortcodeshelp',
            'dashicons-shortcode',
            30
        );
    }
    addaction('adminmenu', 'yawcshortcodeshelppage');
    
    function yawcrendershortcodeshelp() {
        global $shortcodetags;
    
        $yawcshortcodes = array(
            'dateactuelle' => array(
                'description' => 'Affiche la date actuelle',
                'attributes'  => array(),
                'example'     => '[dateactuelle]',
            ),
            'articlesrecents' => array(
                'description' => 'Affiche les articles récents',
                'attributes'  => array(
                    'nombre'    => 'Nombre d'articles à afficher (défaut: 5)',
                    'categorie' => 'Slug de la catégorie à filtrer',
                    'ordre'     => 'ASC ou DESC (défaut: DESC)',
                    'afficher'  => 'Éléments à afficher: titre,extrait,image,date',
                ),
                'example'     => '[articlesrecents nombre="3" categorie="tutoriels"]',
            ),
            'box' => array(
                'description' => 'Crée une boîte stylisée',
                'attributes'  => array(
                    'type'     => 'info, warning, success, error (défaut: info)',
                    'titre'    => 'Titre de la boîte',
                    'icone'    => 'true/false - Afficher l'icône',
                    'fermable' => 'true/false - Ajouter un bouton de fermeture',
                ),
                'example'     => '[box type="warning" titre="Attention"]Contenu[/box]',
            ),
        );
    
        ?>
        
    # Shortcodes Disponibles shortcodes as $tag => $info) : ?>
    ## html($tag); ?>

    html($info['description']); ?>

    ### Attributs disponibles:
      $desc) : ?>
    • html($attr); ?>: html($desc); ?>
    ### Exemple d'utilisation: html($info['example']); ?>

    Bouton TinyMCE pour insérer des shortcodes

    
      Ajouter un bouton dans l'éditeur visuel
     */
    function yawcaddshortcodebutton() {
        if (!currentusercan('editposts') && !currentusercan('editpages')) {
            return;
        }
    
        if (getuseroption('richediting') !== 'true') {
            return;
        }
    
        addfilter('mceexternalplugins', 'yawcaddtinymceplugin');
        addfilter('mcebuttons', 'yawcregistershortcodebutton');
    }
    addaction('adminhead', 'yawcaddshortcodebutton');
    
    function yawcaddtinymceplugin($pluginarray) {
        $pluginarray['yawcshortcodes'] = pluginsurl('assets/js/tinymce-plugin.js', FILE);
        return $pluginarray;
    }
    
    function yawcregistershortcodebutton($buttons) {
        $buttons[] = 'yawcshortcodes';
        return $buttons;
    }
    

    Fichier tinymce-plugin.js:

    (function() {
        tinymce.PluginManager.add('yawcshortcodes', function(editor, url) {
    
            const shortcodes = [
                {
                    text: 'Date actuelle',
                    value: '[dateactuelle]'
                },
                {
                    text: 'Articles récents',
                    value: '[articlesrecents nombre="5"]'
                },
                {
                    text: 'Boîte Info',
                    value: '[box type="info" titre="Information"]Votre contenu ici[/box]'
                },
                {
                    text: 'Tabs',
                    value: '[tabs]n[tab titre="Onglet 1"]Contenu 1[/tab]n[tab titre="Onglet 2"]Contenu 2[/tab]n[/tabs]'
                }
            ];
    
            editor.addButton('yawcshortcodes', {
                text: 'Shortcodes',
                icon: false,
                type: 'menubutton',
                menu: shortcodes.map(sc => ({
                    text: sc.text,
                    onclick: function() {
                        editor.insertContent(sc.value);
                    }
                }))
            });
        });
    })();
    

    Conclusion

    Les shortcodes WordPress sont un outil puissant pour étendre les fonctionnalités de votre site tout en maintenant une interface utilisateur simple. En suivant les bonnes pratiques présentées dans ce guide, vous pouvez créer des shortcodes professionnels, sécurisés et performants.

    Points clés à retenir

  • Toujours retourner, jamais echo - Les shortcodes doivent retourner leur contenu
  • Sécurité avant tout - Échapper les sorties, valider les entrées
  • Performance - Utiliser la mise en cache et le chargement conditionnel
  • Accessibilité - Ajouter les attributs ARIA appropriés
  • Documentation - Documenter vos shortcodes pour les utilisateurs
  • Organisation - Structurer votre code de manière maintenable
  • Validation - Toujours valider les attributs contre des listes blanches
  • Hooks - Fournir des filtres et actions pour l'extensibilité
  • Ressources complémentaires

  • Codex WordPress: Shortcode API
  • Plugin Handbook: Shortcodes
  • Developer Resources: do_shortcode()
  • Security Best Practices
  • Performance Optimization Guide
  • Exercices pratiques

  • Créer un shortcode de galerie personnalisée avec Lightbox
  • Implémenter un système de pricing tables avec shortcodes imbriqués
  • Développer un shortcode de testimonials avec rotation automatique
  • Construire un shortcode de FAQ avec accordéon JavaScript
  • Créer un shortcode de countdown timer avec mise à jour en temps réel
  • En maîtrisant les shortcodes, vous ajoutez un outil précieux à votre arsenal de développeur WordPress, permettant de créer des sites riches en fonctionnalités tout en préservant l'expérience utilisateur pour les éditeurs de contenu.

    Une remarque, un retour ?

    Cet article est vivant — corrections, contre-arguments et retours de production sont les bienvenus. Trois canaux, choisissez celui qui vous convient.