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
<?php
/**
* Shortcode simple pour afficher la date actuelle
*
* @return string Date formatée
*/
function yawc_date_shortcode() {
// Récupérer le format de date WordPress
$date_format = get_option('date_format');
// Générer la date localisée
$current_date = date_i18n($date_format);
// Retourner le HTML (ne jamais faire echo dans un shortcode)
return sprintf(
'<time class="yawc-current-date" datetime="%s">%s</time>',
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 :
<?php
function yawc_weather_widget_shortcode() {
// Démarrer le buffer de sortie
ob_start();
// Inclure un template
include locate_template('template-parts/weather-widget.php');
// Récupérer et nettoyer le buffer
$output = ob_get_clean();
return $output;
}
add_shortcode('meteo', 'yawc_weather_widget_shortcode');
3. Shortcodes avec attributs
Gestion avancée des attributs
<?php
/**
* Shortcode pour afficher les derniers articles
*
* @param array $atts Attributs du shortcode
* @return string HTML généré
*/
function yawc_recent_posts_shortcode($atts) {
// Définir les valeurs par défaut et fusionner avec les attributs fournis
$atts = shortcode_atts(
array(
'nombre' => 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 '<p class="yawc-no-posts">Aucun article trouvé.</p>';
}
// 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();
?>
<div class="<?php echo esc_attr($classe_container); ?>">
<?php while ($query->have_posts()) : $query->the_post(); ?>
<article class="yawc-post-item" id="post-<?php the_ID(); ?>">
<?php if (in_array('image', $afficher) && has_post_thumbnail()) : ?>
<div class="yawc-post-thumbnail">
<a href="<?php the_permalink(); ?>">
<?php the_post_thumbnail('medium', array('loading' => 'lazy')); ?>
</a>
</div>
<?php endif; ?>
<div class="yawc-post-content">
<?php if (in_array('titre', $afficher)) : ?>
<h3 class="yawc-post-title">
<a href="<?php the_permalink(); ?>">
<?php the_title(); ?>
</a>
</h3>
<?php endif; ?>
<?php if (in_array('date', $afficher)) : ?>
<time class="yawc-post-date" datetime="<?php echo get_the_date('c'); ?>">
<?php echo get_the_date(); ?>
</time>
<?php endif; ?>
<?php if (in_array('auteur', $afficher)) : ?>
<span class="yawc-post-author">
par <?php the_author_posts_link(); ?>
</span>
<?php endif; ?>
<?php if (in_array('extrait', $afficher)) : ?>
<div class="yawc-post-excerpt">
<?php the_excerpt(); ?>
</div>
<?php endif; ?>
<?php if (in_array('lire-plus', $afficher)) : ?>
<a href="<?php the_permalink(); ?>" class="yawc-read-more">
Lire la suite →
</a>
<?php endif; ?>
</div>
</article>
<?php endwhile; ?>
</div>
<?php
wp_reset_postdata();
return ob_get_clean();
}
add_shortcode('articles_recents', 'yawc_recent_posts_shortcode');
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 :
<?php
// Dans la fonction du shortcode, après shortcode_atts()
$atts = apply_filters('yawc_recent_posts_atts', $atts);
Utilisation du filtre :
<?php
add_filter('yawc_recent_posts_atts', function($atts) {
// Forcer l'ordre aléatoire sur la page d'accueil
if (is_front_page()) {
$atts['orderby'] = 'rand';
}
return $atts;
});
4. Shortcodes avec contenu encapsulé
Box de mise en évidence
<?php
/**
* 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 yawc_box_shortcode($atts, $content = null) {
$atts = shortcode_atts(array(
'type' => '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' => '<svg class="yawc-icon" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>',
'warning' => '<svg class="yawc-icon" viewBox="0 0 24 24"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>',
'success' => '<svg class="yawc-icon" viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>',
'error' => '<svg class="yawc-icon" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>',
);
// Traiter le contenu (important pour les shortcodes imbriqués)
$content = do_shortcode($content);
// Construire le HTML
$html = sprintf(
'<div class="yawc-box yawc-box-%s" role="alert">',
esc_attr($type)
);
if ($atts['fermable'] === 'true') {
$html .= '<button class="yawc-box-close" aria-label="Fermer">×</button>';
}
if (!empty($atts['titre']) || $atts['icone'] === 'true') {
$html .= '<div class="yawc-box-header">';
if ($atts['icone'] === 'true') {
$html .= $icones[$type];
}
if (!empty($atts['titre'])) {
$html .= sprintf(
'<h4 class="yawc-box-title">%s</h4>',
esc_html($atts['titre'])
);
}
$html .= '</div>';
}
$html .= sprintf(
'<div class="yawc-box-content">%s</div>',
wpautop($content) // Ajouter les paragraphes automatiquement
);
$html .= '</div>';
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
<?php
/**
* Shortcode container pour les onglets
*/
function yawc_tabs_shortcode($atts, $content = null) {
static $tab_group_id = 0;
$tab_group_id++;
$atts = shortcode_atts(array(
'style' => '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(
'<div class="yawc-tabs yawc-tabs-%s" data-tab-group="%d">%s</div>',
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(
'<button class="yawc-tab-button%s" role="tab" aria-selected="%s" aria-controls="%s" id="%s" data-tab-index="%d">%s%s</button>',
$is_active ? ' active' : '',
$is_active ? 'true' : 'false',
esc_attr($panel_id),
esc_attr($tab_id),
$yawc_tab_index,
!empty($atts['icone']) ? '<span class="tab-icon">' . $atts['icone'] . '</span>' : '',
esc_html($atts['titre'])
);
// Construire le panneau de contenu
$tab_panel = sprintf(
'<div class="yawc-tab-panel%s" role="tabpanel" aria-labelledby="%s" id="%s" data-tab-index="%d">%s</div>',
$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(
'<div class="yawc-tab-wrapper" data-tab-button="%s">%s</div>',
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
<?php
/**
* Shortcode de formulaire de contact sécurisé
*/
function yawc_contact_form_shortcode($atts) {
$atts = shortcode_atts(array(
'email' => 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 contact\n\n" .
"Nom: %s\n" .
"Email: %s\n" .
"Sujet: %s\n\n" .
"Message:\n%s\n\n" .
"---\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('<br>', $errors);
}
}
}
// Afficher le formulaire
ob_start();
?>
<div class="yawc-contact-form-wrapper">
<?php if ($message_sent) : ?>
<div class="yawc-success-message" role="alert">
<p>Merci ! Votre message a été envoyé avec succès.</p>
</div>
<?php else : ?>
<?php if (!empty($error_message)) : ?>
<div class="yawc-error-message" role="alert">
<p><?php echo wp_kses_post($error_message); ?></p>
</div>
<?php endif; ?>
<form method="post" class="yawc-contact-form" id="<?php echo esc_attr($form_id); ?>">
<?php wp_nonce_field('yawc_contact_action', 'yawc_contact_nonce'); ?>
<input type="hidden" name="yawc_form_id" value="<?php echo esc_attr($form_id); ?>">
<!-- Champ honeypot caché pour anti-spam -->
<div style="position: absolute; left: -9999px;">
<label for="website">Site web</label>
<input type="text" name="website" id="website" tabindex="-1" autocomplete="off">
</div>
<div class="yawc-form-field">
<label for="nom-<?php echo esc_attr($form_id); ?>">
Nom <span class="required">*</span>
</label>
<input
type="text"
name="nom"
id="nom-<?php echo esc_attr($form_id); ?>"
required
value="<?php echo isset($_POST['nom']) ? esc_attr($_POST['nom']) : ''; ?>"
>
</div>
<div class="yawc-form-field">
<label for="email-<?php echo esc_attr($form_id); ?>">
Email <span class="required">*</span>
</label>
<input
type="email"
name="email"
id="email-<?php echo esc_attr($form_id); ?>"
required
value="<?php echo isset($_POST['email']) ? esc_attr($_POST['email']) : ''; ?>"
>
</div>
<div class="yawc-form-field">
<label for="sujet-<?php echo esc_attr($form_id); ?>">
Sujet
</label>
<input
type="text"
name="sujet"
id="sujet-<?php echo esc_attr($form_id); ?>"
value="<?php echo isset($_POST['sujet']) ? esc_attr($_POST['sujet']) : ''; ?>"
>
</div>
<div class="yawc-form-field">
<label for="message-<?php echo esc_attr($form_id); ?>">
Message <span class="required">*</span>
</label>
<textarea
name="message"
id="message-<?php echo esc_attr($form_id); ?>"
rows="6"
required
><?php echo isset($_POST['message']) ? esc_textarea($_POST['message']) : ''; ?></textarea>
</div>
<div class="yawc-form-submit">
<button type="submit" name="yawc_contact_submit" class="yawc-submit-button">
Envoyer le message
</button>
</div>
</form>
<?php endif; ?>
</div>
<?php
return ob_get_clean();
}
add_shortcode('contact_form', 'yawc_contact_form_shortcode');
Protection contre les injections
<?php
/**
* Exemple de validation stricte pour un shortcode de recherche
*/
function yawc_search_shortcode($atts) {
$atts = shortcode_atts(array(
'placeholder' => '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();
?>
<form role="search" method="get" class="yawc-search-form" action="<?php echo esc_url(home_url('/')); ?>">
<label class="screen-reader-text" for="yawc-search-input">
<?php echo esc_html($atts['placeholder']); ?>
</label>
<input
type="search"
id="yawc-search-input"
name="s"
placeholder="<?php echo esc_attr($atts['placeholder']); ?>"
value="<?php echo get_search_query(); ?>"
required
>
<?php foreach ($post_types as $pt) : ?>
<input type="hidden" name="post_type[]" value="<?php echo esc_attr($pt); ?>">
<?php endforeach; ?>
<button type="submit">
<?php echo esc_html($atts['button_text']); ?>
</button>
</form>
<?php
return ob_get_clean();
}
add_shortcode('search', 'yawc_search_shortcode');
7. Optimisation des performances
Mise en cache des shortcodes
<?php
/**
* Shortcode avec mise en cache transient
*/
function yawc_cached_stats_shortcode($atts) {
$atts = shortcode_atts(array(
'cache_duration' => 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 . '<!-- Cached -->';
}
// 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();
?>
<div class="yawc-stats-grid">
<div class="yawc-stat-item">
<span class="yawc-stat-number"><?php echo number_format_i18n($stats['posts']); ?></span>
<span class="yawc-stat-label">Articles</span>
</div>
<div class="yawc-stat-item">
<span class="yawc-stat-number"><?php echo number_format_i18n($stats['pages']); ?></span>
<span class="yawc-stat-label">Pages</span>
</div>
<div class="yawc-stat-item">
<span class="yawc-stat-number"><?php echo number_format_i18n($stats['comments']); ?></span>
<span class="yawc-stat-label">Commentaires</span>
</div>
<div class="yawc-stat-item">
<span class="yawc-stat-number"><?php echo number_format_i18n($stats['users']); ?></span>
<span class="yawc-stat-label">Utilisateurs</span>
</div>
</div>
<?php
return ob_get_clean();
}
Chargement conditionnel des assets
<?php
/**
* Enregistrer les assets mais ne les charger que si le shortcode est présent
*/
class YAWC_Shortcode_Assets {
private static $shortcodes_found = array();
public static function init() {
add_action('wp_enqueue_scripts', array(__CLASS__, 'register_assets'));
add_filter('the_content', array(__CLASS__, 'detect_shortcodes'), 1);
add_action('wp_footer', array(__CLASS__, 'enqueue_detected_assets'));
}
public static function register_assets() {
// Enregistrer mais ne pas charger
wp_register_style(
'yawc-tabs',
plugins_url('assets/css/tabs.css', __FILE__),
array(),
'1.0.0'
);
wp_register_script(
'yawc-tabs',
plugins_url('assets/js/tabs.js', __FILE__),
array(),
'1.0.0',
true
);
wp_register_style(
'yawc-box',
plugins_url('assets/css/box.css', __FILE__),
array(),
'1.0.0'
);
wp_register_script(
'yawc-box',
plugins_url('assets/js/box.js', __FILE__),
array(),
'1.0.0',
true
);
}
public static function detect_shortcodes($content) {
// Détecter les shortcodes dans le contenu
if (has_shortcode($content, 'tabs')) {
self::$shortcodes_found[] = 'tabs';
}
if (has_shortcode($content, 'box')) {
self::$shortcodes_found[] = 'box';
}
return $content;
}
public static function enqueue_detected_assets() {
// Charger uniquement les assets nécessaires
foreach (array_unique(self::$shortcodes_found) as $shortcode) {
if (wp_style_is($shortcode, 'registered')) {
wp_enqueue_style('yawc-' . $shortcode);
}
if (wp_script_is($shortcode, 'registered')) {
wp_enqueue_script('yawc-' . $shortcode);
}
}
}
}
YAWC_Shortcode_Assets::init();
8. Cas d’usage avancés
Shortcode avec requête Ajax
<?php
/**
* Shortcode de chargement de contenu Ajax
*/
function yawc_ajax_loader_shortcode($atts) {
$atts = shortcode_atts(array(
'action' => '',
'button_text' => 'Charger plus',
'container' => 'posts',
), $atts, 'ajax_loader');
if (empty($atts['action'])) {
return '<p>Erreur: action requise</p>';
}
$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();
?>
<div class="yawc-ajax-loader" id="<?php echo esc_attr($loader_id); ?>" data-action="<?php echo esc_attr($atts['action']); ?>">
<div class="yawc-ajax-content" data-container="<?php echo esc_attr($atts['container']); ?>">
<!-- Le contenu sera chargé ici -->
</div>
<button class="yawc-load-more" type="button">
<?php echo esc_html($atts['button_text']); ?>
</button>
<div class="yawc-ajax-spinner" style="display:none;">
<span class="spinner"></span>
</div>
</div>
<?php
return ob_get_clean();
}
add_shortcode('ajax_loader', 'yawc_ajax_loader_shortcode');
/**
* Handler Ajax pour charger plus d'articles
*/
function yawc_load_more_posts_ajax() {
check_ajax_referer('yawc_ajax_loader', 'nonce');
$page = isset($_POST['page']) ? absint($_POST['page']) : 1;
$args = array(
'post_type' => '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();
?>
<article class="ajax-post-item">
<h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
<?php the_excerpt(); ?>
</article>
<?php
}
wp_reset_postdata();
$html = ob_get_clean();
wp_send_json_success(array(
'html' => $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
<?php
/**
* Shortcode pour afficher du contenu basé sur la géolocalisation
*/
function yawc_geo_content_shortcode($atts, $content = null) {
$atts = shortcode_atts(array(
'pays' => '',
'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
<?php
/**
* Classe de debugging pour shortcodes
*/
class YAWC_Shortcode_Debug {
private static $logs = array();
public static function init() {
if (!defined('WP_DEBUG') || !WP_DEBUG) {
return;
}
add_filter('do_shortcode_tag', array(__CLASS__, 'log_shortcode'), 10, 4);
add_action('wp_footer', array(__CLASS__, 'display_logs'));
}
public static function log_shortcode($output, $tag, $attr, $m) {
$start_time = microtime(true);
self::$logs[] = array(
'tag' => $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 '<div id="yawc-shortcode-debug" style="position: fixed; bottom: 0; left: 0; right: 0; background: #f0f0f0; padding: 20px; max-height: 300px; overflow: auto; z-index: 99999; border-top: 2px solid #333;">';
echo '<h3 style="margin: 0 0 10px;">Shortcode Debug Info</h3>';
echo '<table style="width: 100%; border-collapse: collapse;">';
echo '<tr><th>Tag</th><th>Attributes</th><th>Output Length</th><th>Time (ms)</th><th>Memory (MB)</th></tr>';
foreach (self::$logs as $log) {
printf(
'<tr><td>%s</td><td><pre>%s</pre></td><td>%d</td><td>%.2f</td><td>%.2f</td></tr>',
esc_html($log['tag']),
esc_html(print_r($log['attributes'], true)),
$log['output_length'],
$log['time'] * 1000,
$log['memory'] / 1024 / 1024
);
}
echo '</table></div>';
}
}
YAWC_Shortcode_Debug::init();
Vérification des shortcodes non enregistrés
<?php
/**
* Détecter les shortcodes utilisés mais non enregistrés
*/
function yawc_detect_unregistered_shortcodes($content) {
if (!is_admin() && current_user_can('manage_options')) {
global $shortcode_tags;
// Pattern pour trouver tous les shortcodes
preg_match_all('/\[(\w+)/', $content, $matches);
if (!empty($matches[1])) {
$found_shortcodes = array_unique($matches[1]);
$unregistered = array();
foreach ($found_shortcodes as $tag) {
if (!array_key_exists($tag, $shortcode_tags)) {
$unregistered[] = $tag;
}
}
if (!empty($unregistered)) {
add_action('wp_footer', function() use ($unregistered) {
echo '<div class="yawc-warning" style="background: #fff3cd; border: 1px solid #ffc107; padding: 15px; margin: 20px;">';
echo '<strong>Shortcodes non enregistrés détectés:</strong> ';
echo implode(', ', array_map('esc_html', $unregistered));
echo '</div>';
});
}
}
}
return $content;
}
add_filter('the_content', 'yawc_detect_unregistered_shortcodes', 999);
Tester les shortcodes en isolation
<?php
/**
* Fonction utilitaire pour tester les shortcodes
*/
function yawc_test_shortcode($tag, $atts = array(), $content = null) {
if (!function_exists('do_shortcode')) {
require_once ABSPATH . 'wp-includes/shortcodes.php';
}
$atts_string = '';
foreach ($atts as $key => $value) {
$atts_string .= sprintf(' %s="%s"', $key, esc_attr($value));
}
if ($content !== null) {
$shortcode_string = sprintf('[%s%s]%s[/%s]', $tag, $atts_string, $content, $tag);
} else {
$shortcode_string = sprintf('[%s%s]', $tag, $atts_string);
}
echo '<h3>Test: ' . esc_html($shortcode_string) . '</h3>';
echo '<div style="border: 1px solid #ccc; padding: 15px;">';
echo do_shortcode($shortcode_string);
echo '</div>';
}
// Utilisation dans un template de test
if (current_user_can('manage_options') && isset($_GET['test_shortcodes'])) {
yawc_test_shortcode('date_actuelle');
yawc_test_shortcode('articles_recents', array('nombre' => 3));
yawc_test_shortcode('box', array('type' => 'warning'), 'Contenu de test');
}
10. Bonnes pratiques professionnelles
Organisation du code
<?php
/**
* 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 YAWC_Shortcode_Base {
protected $tag;
protected $defaults = array();
abstract public function render($atts, $content = null);
public function __construct($tag) {
$this->tag = $tag;
add_shortcode($tag, array($this, 'process'));
}
public function process($atts, $content = null) {
$atts = shortcode_atts($this->defaults, $atts, $this->tag);
$atts = $this->validate_atts($atts);
return $this->render($atts, $content);
}
protected function validate_atts($atts) {
// Validation de base, à surcharger dans les classes enfants
return $atts;
}
protected function get_template($name, $vars = array()) {
extract($vars);
$template_path = plugin_dir_path(__FILE__) . 'templates/' . $name . '.php';
if (!file_exists($template_path)) {
return '';
}
ob_start();
include $template_path;
return ob_get_clean();
}
}
// Exemple de shortcode utilisant la classe de base
class YAWC_Tabs_Shortcode extends YAWC_Shortcode_Base {
protected $defaults = array(
'style' => 'default',
);
public function __construct() {
parent::__construct('tabs');
}
protected function validate_atts($atts) {
$valid_styles = array('default', 'pills', 'underline');
if (!in_array($atts['style'], $valid_styles)) {
$atts['style'] = 'default';
}
return $atts;
}
public function render($atts, $content = null) {
wp_enqueue_style('yawc-tabs');
wp_enqueue_script('yawc-tabs');
return $this->get_template('tabs', array(
'style' => $atts['style'],
'content' => do_shortcode($content),
));
}
}
// Initialisation
new YAWC_Tabs_Shortcode();
Documentation des shortcodes
<?php
/**
* Ajouter une page d'aide dans l'admin
*/
function yawc_shortcodes_help_page() {
add_menu_page(
'Shortcodes Disponibles',
'Shortcodes',
'edit_posts',
'yawc-shortcodes',
'yawc_render_shortcodes_help',
'dashicons-shortcode',
30
);
}
add_action('admin_menu', 'yawc_shortcodes_help_page');
function yawc_render_shortcodes_help() {
global $shortcode_tags;
$yawc_shortcodes = array(
'date_actuelle' => array(
'description' => 'Affiche la date actuelle',
'attributes' => array(),
'example' => '[date_actuelle]',
),
'articles_recents' => 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' => '[articles_recents 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]',
),
);
?>
<div class="wrap">
<h1>Shortcodes Disponibles</h1>
<?php foreach ($yawc_shortcodes as $tag => $info) : ?>
<div class="shortcode-doc" style="background: #fff; padding: 20px; margin: 20px 0; border-left: 4px solid #2271b1;">
<h2><?php echo esc_html($tag); ?></h2>
<p><?php echo esc_html($info['description']); ?></p>
<?php if (!empty($info['attributes'])) : ?>
<h3>Attributs disponibles:</h3>
<ul>
<?php foreach ($info['attributes'] as $attr => $desc) : ?>
<li><strong><?php echo esc_html($attr); ?>:</strong> <?php echo esc_html($desc); ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<h3>Exemple d'utilisation:</h3>
<code style="display: block; background: #f0f0f0; padding: 10px; margin: 10px 0;">
<?php echo esc_html($info['example']); ?>
</code>
<button class="button button-secondary copy-shortcode" data-shortcode="<?php echo esc_attr($info['example']); ?>">
Copier l'exemple
</button>
</div>
<?php endforeach; ?>
</div>
<script>
document.querySelectorAll('.copy-shortcode').forEach(button => {
button.addEventListener('click', function() {
const shortcode = this.dataset.shortcode;
navigator.clipboard.writeText(shortcode).then(() => {
this.textContent = 'Copié !';
setTimeout(() => {
this.textContent = 'Copier l\'exemple';
}, 2000);
});
});
});
</script>
<?php
}
Bouton TinyMCE pour insérer des shortcodes
<?php
/**
* Ajouter un bouton dans l'éditeur visuel
*/
function yawc_add_shortcode_button() {
if (!current_user_can('edit_posts') && !current_user_can('edit_pages')) {
return;
}
if (get_user_option('rich_editing') !== 'true') {
return;
}
add_filter('mce_external_plugins', 'yawc_add_tinymce_plugin');
add_filter('mce_buttons', 'yawc_register_shortcode_button');
}
add_action('admin_head', 'yawc_add_shortcode_button');
function yawc_add_tinymce_plugin($plugin_array) {
$plugin_array['yawc_shortcodes'] = plugins_url('assets/js/tinymce-plugin.js', __FILE__);
return $plugin_array;
}
function yawc_register_shortcode_button($buttons) {
$buttons[] = 'yawc_shortcodes';
return $buttons;
}
Fichier tinymce-plugin.js:
(function() {
tinymce.PluginManager.add('yawc_shortcodes', function(editor, url) {
const shortcodes = [
{
text: 'Date actuelle',
value: '[date_actuelle]'
},
{
text: 'Articles récents',
value: '[articles_recents 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('yawc_shortcodes', {
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.