Intermediaire 2 min de lecture · 251 mots

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

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 'the_content'
         ↓
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 $shortcode_tags qui associe chaque nom de shortcode à sa fonction callback.

2. Créer un shortcode simple

Exemple basique : Affichage de la date du jour

%s',
        date('c'), // Format ISO 8601
        esc_html($current_date)
    );
}
add_shortcode('date_actuelle', 'yawc_date_shortcode');

Utilisation :

[date_actuelle]

Points importants :

  • Toujours RETOURNER le contenu, jamais utiliser echo
  • Échapper les données avec esc_html(), esc_attr(), etc.
  • Utiliser date_i18n() 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 :

    3. Shortcodes avec attributs

    Gestion avancée des attributs

     5,
                'categorie'  => '',
                'ordre'      => 'DESC',
                'orderby'    => 'date',
                'exclu'      => '', // IDs à exclure
                'afficher'   => 'titre,extrait,image', // Éléments à afficher
                'classe'     => '',
            ),
            $atts,
            'articles_recents' // 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 = in_array(strtoupper($atts['ordre']), array('ASC', 'DESC'))
            ? strtoupper($atts['ordre'])
            : 'DESC';
    
        // Liste blanche des orderby autorisés
        $orderby_allowed = array('date', 'title', 'rand', 'comment_count', 'modified');
        $orderby = in_array($atts['orderby'], $orderby_allowed)
            ? $atts['orderby']
            : 'date';
    
        // Construire les arguments de requête
        $args = array(
            'posts_per_page'      => $nombre,
            'order'               => $ordre,
            'orderby'             => $orderby,
            'ignore_sticky_posts' => true,
            'no_found_rows'       => true, // Performance : pas besoin de pagination
        );
    
        // Ajouter le filtre de catégorie si spécifié
        if (!empty($atts['categorie'])) {
            $args['category_name'] = sanitize_text_field($atts['categorie']);
        }
    
        // Gérer les exclusions
        if (!empty($atts['exclu'])) {
            $exclu_ids = array_map('absint', explode(',', $atts['exclu']));
            $args['post__not_in'] = $exclu_ids;
        }
    
        // Exécuter la requête
        $query = new WP_Query($args);
    
        if (!$query->have_posts()) {
            return '

    Aucun article trouvé.

    '; } // Déterminer quels éléments afficher $afficher = array_map('trim', explode(',', $atts['afficher'])); // Construire le HTML $classe_container = 'yawc-recent-posts'; if (!empty($atts['classe'])) { $classe_container .= ' ' . sanitize_html_class($atts['classe']); } ob_start(); ?>
    have_posts()) : $query->the_post(); ?>

    Exemples d'utilisation :

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

    Filtrage des attributs

    Permettre aux développeurs de modifier les attributs :

    Utilisation du filtre :

    4. Shortcodes avec contenu encapsulé

    Box de mise en évidence

     'info',  // info, warning, success, error
            'titre'  => '',
            'icone'  => 'true',
            'fermable' => 'false',
        ), $atts, 'box');
    
        // Validation du type
        $types_valides = array('info', 'warning', 'success', 'error');
        $type = in_array($atts['type'], $types_valides) ? $atts['type'] : 'info';
    
        // Icônes par type
        $icones = array(
            'info'    => '',
            'warning' => '',
            'success' => '',
            'error'   => '',
        );
    
        // Traiter le contenu (important pour les shortcodes imbriqués)
        $content = do_shortcode($content);
    
        // Construire le HTML
        $html = sprintf(
            '';
    
        return $html;
    }
    add_shortcode('box', 'yawc_box_shortcode');
    

    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

     'default', // default, pills, underline
        ), $atts, 'tabs');
    
        // Reset le compteur d'onglets pour chaque groupe
        global $yawc_tab_index;
        $yawc_tab_index = 0;
    
        // Stocker l'ID du groupe pour les onglets enfants
        global $yawc_current_tab_group;
        $yawc_current_tab_group = $tab_group_id;
    
        // Parser le contenu pour extraire les titres des onglets
        $content = do_shortcode($content);
    
        return sprintf(
            '
    %s
    ', esc_attr($atts['style']), $tab_group_id, $content ); } add_shortcode('tabs', 'yawc_tabs_shortcode'); /** * Shortcode pour un onglet individuel */ function yawc_tab_shortcode($atts, $content = null) { global $yawc_tab_index, $yawc_current_tab_group; $atts = shortcode_atts(array( 'titre' => 'Onglet ' . ($yawc_tab_index + 1), 'icone' => '', 'actif' => 'false', ), $atts, 'tab'); $is_active = ($yawc_tab_index === 0 || $atts['actif'] === 'true'); $yawc_tab_index++; $tab_id = sprintf('tab-%d-%d', $yawc_current_tab_group, $yawc_tab_index); $panel_id = sprintf('panel-%d-%d', $yawc_current_tab_group, $yawc_tab_index); // Construire le bouton d'onglet (sera repositionné par JS) $tab_button = sprintf( '', $is_active ? ' active' : '', $is_active ? 'true' : 'false', esc_attr($panel_id), esc_attr($tab_id), $yawc_tab_index, !empty($atts['icone']) ? '' . $atts['icone'] . '' : '', esc_html($atts['titre']) ); // Construire le panneau de contenu $tab_panel = sprintf( '
    %s
    ', $is_active ? ' active' : '', esc_attr($tab_id), esc_attr($panel_id), $yawc_tab_index, do_shortcode($content) ); // Retourner le bouton + panneau (le JS réorganisera) return sprintf( '
    %s
    ', esc_attr($tab_button), $tab_panel ); } add_shortcode('tab', 'yawc_tab_shortcode');

    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

     get_option('admin_email'),
            'objet' => 'Nouveau message depuis le site',
            'redirect' => '',
        ), $atts, 'contact_form');
    
        // Valider l'email
        $email_destination = is_email($atts['email']) ? $atts['email'] : get_option('admin_email');
    
        // Générer un ID unique pour ce formulaire
        $form_id = 'yawc-contact-' . wp_generate_password(8, false);
    
        // Traitement du formulaire si soumis
        $message_sent = false;
        $error_message = '';
    
        if (isset($*POST['yawc_contact_submit']) &&
            isset($*POST['yawc_form_id']) &&
            $*POST['yawc_form_id'] === $form_id) {
    
            // Vérifier le nonce
            if (!isset($*POST['yawc_contact_nonce']) ||
                !wp_verify_nonce($*POST['yawc_contact_nonce'], 'yawc_contact_action')) {
                $error_message = 'Erreur de sécurité. Veuillez réessayer.';
            } else {
                // Valider et nettoyer les données
                $nom = isset($*POST['nom']) ? sanitize_text_field($*POST['nom']) : '';
                $email = isset($*POST['email']) ? sanitize_email($*POST['email']) : '';
                $sujet = isset($*POST['sujet']) ? sanitize_text_field($*POST['sujet']) : '';
                $message = isset($*POST['message']) ? sanitize_textarea_field($*POST['message']) : '';
    
                // Validation
                $errors = array();
    
                if (empty($nom)) {
                    $errors[] = 'Le nom est requis.';
                }
    
                if (empty($email) || !is_email($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 = $email_destination;
                    $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,
                        home_url()
                    );
    
                    $headers = array(
                        'Reply-To: ' . $nom . ' <' . $email . '>',
                        'Content-Type: text/plain; charset=UTF-8'
                    );
    
                    // Envoyer l'email
                    if (wp_mail($to, $subject, $body, $headers)) {
                        $message_sent = true;
    
                        // Hook pour les actions post-envoi
                        do_action('yawc_contact_form_sent', array(
                            'nom' => $nom,
                            'email' => $email,
                            'sujet' => $sujet,
                            'message' => $message,
                        ));
    
                        // Redirection si configurée
                        if (!empty($atts['redirect'])) {
                            wp_safe_redirect(esc_url($atts['redirect']));
                            exit;
                        }
                    } else {
                        $error_message = 'Erreur lors de l'envoi du message. Veuillez réessayer.';
                    }
                } else {
                    $error_message = implode('
    ', $errors); } } } // Afficher le formulaire ob_start(); ?>

    Protection contre les injections

     'Rechercher...',
            'button_text' => 'Rechercher',
            'post_type'   => 'post',
        ), $atts, 'search');
    
        // Valider le post_type contre une liste blanche
        $allowed_post_types = get_post_types(array('public' => true), 'names');
        $post_types = array_map('trim', explode(',', $atts['post_type']));
        $post_types = array_filter($post_types, function($pt) use ($allowed_post_types) {
            return in_array($pt, $allowed_post_types, true);
        });
    
        if (empty($post_types)) {
            $post_types = array('post');
        }
    
        ob_start();
        ?>
        
        

    7. Optimisation des performances

    Mise en cache des shortcodes

     3600, // 1 heure par défaut
        ), $atts, 'stats');
    
        // Créer une clé de cache unique basée sur les attributs
        $cache_key = 'yawc_stats*' . md5(serialize($atts));
    
        // Essayer de récupérer depuis le cache
        $cached_output = get_transient($cache_key);
    
        if ($cached_output !== false) {
            return $cached_output . '';
        }
    
        // Générer le contenu (opération coûteuse)
        $output = yawc_generate_stats_html();
    
        // Mettre en cache
        set_transient($cache_key, $output, absint($atts['cache_duration']));
    
        return $output;
    }
    add_shortcode('stats', 'yawc_cached_stats_shortcode');
    
    /**
     * Nettoyer le cache lors de la publication d'un article
     */
    add_action('save_post', function($post_id) {
        if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
            return;
        }
    
        // Supprimer tous les transients de stats
        global $wpdb;
        $wpdb->query(
            "DELETE FROM $wpdb->options
             WHERE option_name LIKE '*transient_yawc_stats*%'
             OR option_name LIKE '*transient_timeout_yawc_stats*%'"
        );
    });
    
    function yawc_generate_stats_html() {
        global $wpdb;
    
        $stats = array(
            'posts' => wp_count_posts('post')->publish,
            'pages' => wp_count_posts('page')->publish,
            'comments' => wp_count_comments()->approved,
            'users' => count_users()['total_users'],
        );
    
        ob_start();
        ?>
        
    Articles
    Pages
    Commentaires
    Utilisateurs

    Chargement conditionnel des assets

    8. Cas d'usage avancés

    Shortcode avec requête Ajax

     '',
            'button_text' => 'Charger plus',
            'container'   => 'posts',
        ), $atts, 'ajax_loader');
    
        if (empty($atts['action'])) {
            return '

    Erreur: action requise

    '; } $loader_id = 'ajax-loader-' . wp_generate_password(8, false); // Passer les données au JavaScript wp_localize_script('yawc-ajax-loader', 'yawcAjaxLoader', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('yawc_ajax_loader'), )); ob_start(); ?>
    'post', 'posts_per_page' => 6, 'paged' => $page, 'post_status' => 'publish', ); $query = new WP_Query($args); if (!$query->have_posts()) { wp_send_json_error(array('message' => 'Aucun article supplémentaire')); } ob_start(); while ($query->have_posts()) { $query->the_post(); ?>
    ###
    $html, 'has_more' => $page < $query->max_num_pages, 'next_page' => $page + 1, )); } add_action('wp_ajax_load_more_posts', 'yawc_load_more_posts_ajax'); add_action('wp_ajax_nopriv_load_more_posts', 'yawc_load_more_posts_ajax');

    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.has_more) {
                                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

     '',
            'ville'  => '',
            'fallback' => 'true',
        ), $atts, 'geo_content');
    
        // Détecter le pays de l'utilisateur
        $user_country = yawc_get_user_country();
        $user_city = yawc_get_user_city();
    
        $show_content = false;
    
        if (!empty($atts['pays'])) {
            $allowed_countries = array_map('trim', explode(',', $atts['pays']));
            $show_content = in_array($user_country, $allowed_countries, true);
        }
    
        if (!empty($atts['ville']) && $show_content) {
            $allowed_cities = array_map('trim', explode(',', $atts['ville']));
            $show_content = in_array($user_city, $allowed_cities, true);
        }
    
        if ($show_content || $atts['fallback'] === 'true') {
            return do_shortcode($content);
        }
    
        return '';
    }
    add_shortcode('geo_content', 'yawc_geo_content_shortcode');
    
    /**
     * Obtenir le pays de l'utilisateur via API ou cache
     */
    function yawc_get_user_country() {
        // Vérifier le cache de session
        if (isset($*SESSION['yawc_user_country'])) {
            return $*SESSION['yawc_user_country'];
        }
    
        $ip = yawc_get_user_ip();
    
        // Utiliser une API de géolocalisation
        $response = wp_remote_get('http://ip-api.com/json/' . $ip);
    
        if (is_wp_error($response)) {
            return 'UNKNOWN';
        }
    
        $data = json_decode(wp_remote_retrieve_body($response), true);
        $country = isset($data['countryCode']) ? $data['countryCode'] : 'UNKNOWN';
    
        // Mettre en cache
        $*SESSION['yawc_user_country'] = $country;
    
        return $country;
    }
    
    function yawc_get_user_ip() {
        $ip = '';
    
        if (!empty($*SERVER['HTTP_CLIENT_IP'])) {
            $ip = $*SERVER['HTTP_CLIENT_IP'];
        } elseif (!empty($*SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ip = $*SERVER['HTTP_X_FORWARDED_FOR'];
        } else {
            $ip = $*SERVER['REMOTE_ADDR'];
        }
    
        return filter_var($ip, FILTER_VALIDATE_IP) ? $ip : '0.0.0.0';
    }
    

    9. Debugging et résolution de problèmes

    Mode debug pour shortcodes

     $tag,
                'attributes' => $attr,
                'output_length' => strlen($output),
                'time'       => microtime(true) - $start_time,
                'memory'     => memory_get_usage(),
            );
    
            return $output;
        }
    
        public static function display_logs() {
            if (empty(self::$logs)) {
                return;
            }
    
            echo '
    '; echo '

    Shortcode Debug Info

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

    ',
    esc_html($log['tag']),
    esc_html(print_r($log['attributes'], true)),
    $log['output_length'],
    $log['time'] 1000,
    $log['memory'] / 1024 / 1024
    );
    }

    echo '

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

    ';
    }
    }

    YAWC_Shortcode_Debug::init();

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

    ';
                        echo '**Shortcodes non enregistrés détectés:** ';
                        echo implode(', ', array_map('esc_html', $unregistered));
                        echo '

    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.

    Laisser un commentaire