Les shortcodes, TinyMCE et l’API View de WordPress

L’API view est un module javascript de WordPress relativement méconnu. Cela fait maintenant quelques temps que je souhaitais vous le faire découvrir, mais j’avais du mal à écrire cet article. Il se trouve que je me suis replongé dans le code de ce module pour la version 1.6 de Pastacode – sortie la semaine dernière, et cela m’a semblé être l’occasion parfaite pour vous le présenter…

Brièvement, que sont ces fameuses «  views » ? Une view est un mécanisme qui remplace un texte par un bloc d’HTML, dans l’éditeur de contenu WordPress, lorsqu’on est en mode visuel. Souvent ce texte peut être un shortcode (comme celui d’une galerie d’image) ou bien une URL (comme pour les embeds).

Exemple de View TinyMCE
Exemple de View TinyMCE

Si certaines « views » sont gérées nativement par WordPress – comme les galeries d’images, les médias audios, videos, playlists et embeds – il est possible d’ajouter nos propres views dans tinyMCE.

Avant d’aller plus loin, il faut comprendre ce que sont les shortcodes, et ce qu’est tinyMCE.

Qu’est ce qu’un shortcode

Les shortcodes sont, comme leur nom l’indique, de petits bouts de code que l’on place dans l’éditeur d’un article, et qui sont substitués à l’affichage par du code html. L’intérêt est de faire les choses proprement : pas de marquage directement dans l’éditeur, et la possibilité d’insérer au sein d’un article du contenu dynamique, sans savoir coder.

Les shortcodes peuvent prendre la forme [balise cle1="val1" cle2="val2"/] ou bien la forme [balise cle1="val1" cle2="val2" cle3="val3"]texte[/balise]. On dit de la première qu’il s’agit d’un shortcode « autofermant » et elle n’est composée que d’attributs. La seconde version permet d’avoir du contenu multiligne.

On ajoute donc ces bouts de texte dans l’éditeur, et à l’affichage de la page une fonction PHP les récupères et interprète leur contenu pour renvoyer du HTML.

Il existe des shortcodes natifs, tels que celui de galerie des médias : gallery, mais WordPress propose une API pour ajouter nos propres shortcodes. Voici les quelques méthodes disponibles et comment cela fonctionne :

Quelques exemples de shortcodes

La fonction PHP qui permet d’associer une balise de shortcode à une fonction de mise en forme est add_shortcode, elle prend 2 arguments : le nom de la balise, et le nom de la callback.

La fonction callback propose trois paramètres :

  1. $atts (un tableau PHP des paramètres passés au shortcode) ;
  2. $content (le contenu entre les deux balises, qui peut être null) ;
  3. $tag qui permet de retrouver ici le nom de balise.

Un shortcode pour mettre en forme un bouton

Voici un exemple pour ajouter un shortcode qui fera apparaitre un bouton dans le contenu.

<?php
// Je créer le shortcode
add_shortcode( 'bouton', 'wabeo_bouton' );
function wabeo_bouton( $atts, $content = '', $tag ) {
// Je filtre les attributs
// Le dernier paramètre de shortcode_atts…
// … $tag permet d'utiliser le filtre shortcode_atts_{$tag}
$atts = shortcode_atts( array(
'ancre' => 'Prendre contact',
'lien' => 'mailto:' . antispambot( get_option( 'admin_email' ) ),
), $atts, $tag );
// Je retourne le html de substitution
return '<a href="' . esc_attr( $atts['lien'] ) . '" class="bouton">'
. esc_html( $atts['ancre'] ) . '</a>';
}

/**
* [bouton/]
* ↳ <a href="mailto:willy@wabeo.fr" class="bouton">Prendre contact</a>
*
* [bouton lien="http://pastacode.wabeo.fr" ancre="Découvrez le plugin"]
* ↳ <a href="http://pastacode.wabeo.fr" class="bouton">Découvrez le plugin</a>
*/

Il y a deux éléments intéressants à noter :

  1. tout d’abord la fonction shortcode_atts ressemble à wp_parse_args, mais elle permet aussi de filtrer les attributs du shortcode, pour cela il ne faut pas omettre de repasser le nom du tag en dernier attribut. Ceci faisant vous pourrez utiliser le filtre sc_{$tag} pour modifier les clés et valeurs des attributs.
  2. ensuite il faut bien faire attention : la fonction callback doit « renvoyer » une chaîne, mais elle ne doit rien « écrire » (donc return et pas de echo).

Un shortcode de conversion de devise

Il est tout à fait possible d’utiliser un shortcode pour générer du contenu dynamique. Par exemple, si je veux interroger une API Rest pour récupérer la valeur d’une devise et convertir une somme d’argent.

add_shortcode( 'conversion', 'wabeo_conversion' );
function wabeo_conversion( $atts, $content = '', $tag ) {
$atts = shortcode_atts( array(
'valeur' => false,
'de' => 'EUR';
'vers' => 'GBP';
), $atts, $tag );
if ( $atts['valeur'] ) {
$sens = $atts['de'] . $atts['vers'];
if ( ! $taux = get_transient( 'taux_' . $sens ) ) {
$data = wp_remote_get( 'http://finance.yahoo.com/webservice/v1/symbols/' . $sens . '=X/quote?format=json' );
if ( ! is_wp_error( $data )
&& '200' == wp_remote_retrieve_response_code( $data ) ) {
$data = json_decode( $data );
if ( isset( $data['resources'][0]['resource']['fields']['name'] )
&& $atts['de'] . '/' . $atts['vers'] == $data['resources'][0]['resource']['fields']['name'] ) {
$taux = $data['resources'][0]['resource']['fields']['price'];
set_transient( 'taux_' . $sens, floatval( $taux ), DAY_IN_SECONDS );
}
}
}
if ( $taux ) {
return ( floatval( $atts['valeur'] ) * $taux ) . ' ' . $atts['vers'];
}
return $atts['valeur'] . ' ' . $atts['de'];
}
return;
}

/**
* [conversion value="1"/]
* ↳ 0.715725
*
* [conversion from="AUD" to="USD" value="10"/]
* ↳ 7.79880
*/

Un shortcode pour mettre en forme un contenu

Voici un dernier exemple pour montrer comment un shortcode peut être utilisé pour mettre en forme le contenu textuel entre ses deux balises.

add_shortcode( 'emphase', 'wabeo_emphase' );
function wabeo_emphase( $atts, $content = '', $tag ) {
$atts = shortcode_atts( array(
'attachment-id' => false,
), $atts, $tag );
if ( $content ) {
if ( $atts['attachment-id']
&& 'attachment' == get_post_type( $atts['attachment-id'] ) ) {
if ( $img = wp_get_attachment_image_src( $atts['attachment-id'], 'large' ) ) {
return '<div class="text-wrapper" style="background-image:url(' . $img[0] . ');"><div>'
. $content
. '</div></div>';
}
}
return '<div class="text-wrapper"><div>'
. $content
. '</div></div>';
}
return;
}

/**
* [emphase]
* text
* [/emphase]
* ↳ <div class="text-wrapper"><div>text</div></div>
*
* [emphase attachment-id="4"]
* text
* [/emphase]
* ↳ <div class="text-wrapper" style="background-image:url(http://exemple.com/wp-content/uploads/image.jpg);">
* ↳ <div>text</div></div>
*/
	.text-wrapper{
padding:2em;
font-size:1.6em;
box-sizing:border-box;
}
.text-wrapper > div{
background-color:rgba(255,255,255,0.8);
}

Vous savez maintenant tout sur les shortcodes.

Qu’est ce que TinyMCE

TinyMCE est un éditeur HTML WYSIWYG1 écrit en javascript. Il permet de saisir et de mettre en forme du texte sans connaitre nécessairement les balises HTML et styles CSS. Un peu comme Word, si vous voulez… Cet éditeur est inclus dans le noyau de WordPress qui l’utilise pour nous permettre d’éditer les contenus des pages et des articles dans l’administration de WordPress.

Lorsque l’on charge la page d’édition d’un article le textarea contenant le texte de l’article est masqué et un éditeur TinyMCE qui lui est associé est créé. Plus tard au moment de valider le formulaire – pour sauvegarder l’article – le contenu de TinyMCE sera compilé puis injecté dans le textarea originel.

Au passage, WordPress nous offre une fonction PHP toute faite pour charger un éditeur TinyMCE en front ou back-office : wp_editor( $content, $editor_id, $settings );

Celle-ci crée un textarea, charge les fichiers javascript et css de tinyMCE et méthode et événement.

Comment personnaliser le comportement de TinyMCE ?

Le module javascript TinyMCE peut être facilement customisé. Si vous allez voir le site officiel, vous constaterez d’ailleurs qu’il n’a pas la même apparence dans WordPress et qu’il ne propose pas les mêmes boutons.

Durant son initialisation, il est possible de lui préciser des réglages et de lui ajouter des plugins. Ces plugins vont permettre d’ajouter des éléments d’interface, et également d’associer des actions particulières à des événements javascripts.

Une extension tinyMCE peut, par exemple, ajouter un bouton dans la barre d’outil qui insérera du contenu lorsque l’on cliquera dessus. Un autre plugin pourra aussi, autre exemple, modifier le texte lorsque l’on jongle entre les onglets « visuel » et « texte ».

Pour ajouter un plugin, il faut concevoir un fichier javascript, et l’ajouter via le filtre mce_external_plugins de WordPress. De même, pour ajouter un bouton à l’éditeur – dont le comportement sera défini par le fichier javascript précédent – il faut passer par le filtre mce_buttons :

add_action( 'admin_init', 'wabeo_tinymce_button' );
function wabeo_tinymce_button() {
if ( ! current_user_can('edit_posts')
&& ! current_user_can('edit_pages') ) {
return false;
}

if ( get_user_option('rich_editing') == 'true') {
add_filter( 'mce_external_plugins', 'wabeo_script_tiny' );
add_filter( 'mce_buttons', 'wabeo_register_button' );
}
}

function wabeo_register_button( $buttons ) {
array_push( $buttons, '|', 'mon_bouton' );
return $buttons;
}

function wabeo_script_tiny( $plugin_array ) {
$plugin_array['monscript'] = plugins_url( '/script.js', __FILE__ );
return $plugin_array;
}

Enfin, pour mettre en forme les éléments que l’on va insérer dans l’interface de tinyMCE, nous allons lui ajouter une feuille de style via la variable globale $editor_styles.

add_action( 'admin_init', 'add_wabeo_styles_to_editor' );
function add_wabeo_styles_to_editor() {
global $editor_styles;
$editor_styles[] = plugins_url( '/styles.css', __FILE__ );
}

Un bouton pour ajouter un shortcode simple

En utilisant ces quelques éléments, nous allons pouvoir par exemple créer un bouton qui, lorsque l’on cliquera dessus, ajoutera un shortcode sans attribut dans l’éditeur, là où est positionné le curseur.

Je déclare mon plugin en PHP (comme indiqué plus haut) puis je conçois le comportement du bouton dans le fichier javascript :

(function() {
tinymce.PluginManager.add('monscript', function( editor, url ) {

// https://www.tinymce.com/docs/demo/custom-toolbar-button/
editor.addButton('mon_bouton', {
icon: false,
text:'bouton',
onclick: function () {
editor.insertContent('[bouton/]');
}
});
});

})();

Vous remarquez que c’est la méthode tinymce.PluginManager.add qui permet d’associer le plugin javascript à tinyMCE. Le premier argument est le nom du plugin, et le second une fonction callback. La fonction de callback renvoie en paramètre l’éditeur courant (car plusieurs éditeurs peuvent être chargés dans un même formulaire).

Au sein de la fonction, c’est le rôle de la méthode editor.addButton de lier l’élément de l’UI à ses propriétés et actions.

Un bouton avec une liste d’actions

Si vous ajoutez un bouton par shortcode, la barre d’outils va vite être saturée… pour éviter cela nous allons rassembler les boutons des shortcodes au sein d’une liste. Au lieu de déclarer un élément de type button, nous allons lui assigner le type menubutton et détailler la liste des sous-boutons sous la forme d’un tableau d’objets javascript.

(function() {
tinymce.PluginManager.add('monscript', function( editor, url ) {

// https://www.tinymce.com/docs/demo/custom-toolbar-menu-button/
editor.addButton('mon_bouton', {
icon: false,
text:'mes shortcodes',
type:'menubutton',
menu: [
{
text: 'bouton',
onclick: function() {
editor.insertContent('[bouton/]');
}
}, {
text: 'conversion de devises',
onclick: function() {
editor.insertContent('[conversion valeur="5"/]');
}
}
]
});
});

})();

Au passage, vous voyez qu’il est possible de remplacer le texte du bouton par une icône. Plus de styles peuvent être définis via la feuille CSS.

Un bouton pour ajouter un shortcode avec des attributs

Dans le précédent exemple, un clic sur le bouton effectuait une action directement dans l’éditeur. Mais si on souhaite ajouter des attributs au shortcode, le mieux est d’ouvrir une boîte modale pour saisir la valeur de ces attributs, puis ensuite construire le shortcode avant de l’insérer.

La technique est plutôt simple : au lieu de lier une action editor.insertContent directement à l’événement onclick, on va lui associer une fonction qui va ouvrir la nouvelle fenêtre via editor.windowManager.open , et c’est lorsque le formulaire de cette boîte modale sera validée qu’on procèdera à l’insertion du shortcode dans l’éditeur.

(function() {

function set_shortcodes_atts( editor, atts ) {

// nom fenetre
var titreFenetre = !_.isUndefined( atts.nom ) ? atts.nom : 'Ajouter un shortcode';
// balise du shortcode
var balise = !_.isUndefined( atts.balise ) ? atts.balise : false;

fn = function() {
editor.windowManager.open( {
title: titreFenetre,
body: atts.body,
onsubmit: function( e ) {
var out = '[' + balise;
for ( var attr in e.data ) {
out += ' ' + attr + '="' + e.data[ attr ] + '"';
}
out += '/]';
editor.insertContent( out );
},
} );
};
return fn;
}

tinymce.PluginManager.add('monscript', function( editor, url ) {

editor.addButton('mon_bouton', {
icon: false,
text:'mes shortcodes',
type:'menubutton',
menu: [
{
text: 'bouton',
onclick: set_shortcodes_atts( editor, {
// Liste des éléments qui composent…
// …le formulaire du shortcode « bouton »
body: [
{
label: 'Texte du bouton',
name: 'ancre',
// http://archive.tinymce.com/wiki.php/api4:class.tinymce.ui.TextBox
type: 'textbox',
value: '',
},
{
label: 'URL du lien',
name: 'lien',
type: 'textbox',
value: '',
}
],
balise: 'bouton',
nom: 'Ajouter un bouton',
} ),
},
{
text: 'conversion de devises',
onclick: set_shortcodes_atts( editor, {
// Liste des éléments qui composent…
// …le formulaire du shortcode « Conversion de devise »
body: [
{
label: 'Valeur',
name: 'valeur',
type: 'textbox',
tooltip: 'Saisissez un nombre',
value: '',
},
{
label: 'De',
name: 'de',
// http://archive.tinymce.com/wiki.php/api4:class.tinymce.ui.ListBox
type: 'listbox',
values : [
{ text: 'Euros', value: 'EUR' },
{ text: 'Livres Sterling', value: 'GBP' },
{ text: 'Dollards Américains', value: 'USD' },
]
},
{
label: 'Vers',
name: 'vers',
type: 'listbox',
values : [
{ text: 'Euros', value: 'EUR' },
{ text: 'Livres Sterling', value: 'GBP' },
{ text: 'Dollards Américains', value: 'USD' },
]
}
],
balise: 'conversion',
nom: 'Conversion de devises',
} ),
}
]
});
});

})();

La méthode qui construit la boîte modale propose de nombreuses options (titre, éléments du formulaire, tailles, styles, actions…). La propriété la plus importante est body puisqu’elle définit quels seront les champs du formulaire.

Les types de champs proposés par TinyMCE sont nombreux2, mais ici je n’ai eu besoin que de textbox et listbox. Les différents contrôles et leurs propriétés sont rapidement détaillés dans l’ancienne documentation de TinyMCE.

On peut aller assez loin avec les champs et méthodes de ce module javascript, comme par exemple organiser un formulaire par onglets, ou bien transmettre des valeurs entre différentes modales (comme le fait Pastacode)… mais il faudrait plus d’un article pour aborder toutes ces possibilités. Nous allons maintenant voir quelque chose d’encore plus intéressant !

L’API View dans tout ça ?

On l’a dit au début, l’API View est un module qui permet de remplacer automatiquement des « motifs » de contenu lorsque l’on se trouve dans le mode « visuel » de tinyMCE. Ça peut donc être un outil intéressant pour donner un aspect agréable et plus « lisible » à nos shortcodes disgracieux !
Mais il n’y a pas que ça… une view propose aussi des éléments d’interface pour éditer les motifs qu’elle substitue.

Le lien vers un fichier image est remplacé par une view de cette image, et un tooltip avec des actions

Si vous cliquez sur une galerie d’images, vous verrez un petit tooltip apparaître vous proposant d’éditer le choix des images, ou bien de supprimer la galerie.

En utilisant les view on va donc pouvoir ajouter des actions qui vont réouvrir notre modale, et permettre de modifier le shortcode dans un environnement graphique !

Au clic du bouton éditer, une modale s'ouvre…
Au clic du bouton éditer, une modale s’ouvre…

Comment enregistrer une nouvelle View ?

Et bien c’est en fait assez simple… Plutôt que de hardcoder les views natives, les développeurs du cœur de WordPress ont créé une méthode générique pour enregistrer les views. On peut donc facilement en ajouter de nouvelles.

La méthode est même semi-automatique pour les shortcodes : la recherche du motif (qui se fait par le biais d’une regex assez impressionnante) est déjà prévue.

Dans notre fichier javascript, à l’intérieur de notre fonction-plugin-tinyMCE, appelons la méthode window.wp.mce.views.register. Elle prend deux paramètres : le premier est la balise du shortcode, et la seconde est notre objet « view ». Ce second hérite directement de l’objet wp.mce.View , et je vous invite à aller lire le code source dans le cœur de WordPress pour mieux comprendre ses propriétés initiales.

La première propriété qui nous intéresse est initialize c’est grâce à elle que nous allons définir ce qui doit s’afficher à la place du shortcode.

	function getAttr( str, name ) {
name = new RegExp( name + '=\"([^\"]+)\"' ).exec( str );
return name ? window.decodeURIComponent( name[1] ) : '';
}

window.wp.mce.views.register( 'bouton', {
initialize: function() {
var titre = '<div class="my-view-wrapper">';
titre += '<p><strong>Bouton</strong></p>';
var ancre = getAttr( this.text, 'ancre' );
if ( ancre ) {
titre += '<p>« ' + _.escape( ancre ) + ' »</p>';
}

titre += '</div>';
this.render( titre );
},
} );

J’utilise une fonction getAttr pour récupérer les attributs du shortcode et pouvoir les réafficher ici. D’ailleurs dans le cas d’un shortcode qui ferait appel à des données dynamiques il serait possible de faire une requête ajax ici – c’est ce que fait la view de la gallery WordPress.

Vous remarquerez que le fait d’appliquer une view à un shortcode lui induit un comportement de block. Il n’est pas possible de l’afficher sur une même ligne que du texte.

Le contenu substitué n’est pas mis en forme, il faut donc lui ajouter des styles CSS pour le rendre plus attractif.

.my-view-wrapper{
text-align:center;
line-height:1.5em;
padding:1em;
background-color:#0073AA;
font-family:monospace;
color:white;
}

.my-view-wrapper p{
margin:0;
padding:0;
}

Voilà, maintenant si vous ajoutez un shortcode via un bouton, ou bien si vous le composez à la main et basculez sur le mode « visuel » de TinyMCE, une view le remplacera automatiquement4. En retournant en mode « texte », la view cèdera alors sa place au shortcode.

Comment éditer la shortcode à partir d’une view ?

Lorsque vous cliquez sur le bloc view, un petit tooltip apparait, vous proposant de supprimer cette instance, ou bien de l’éditer. Si la méthode de suppression est déjà effective, l’édition a besoin d’être définie dans notre code via la propriété edit de la view.

On construit alors une fonction relativement similaire à celle qui crée le shortcode : on appelle editor.windowManager.open et on lui passe les mêmes arguments, en précisant leurs valeurs actuelles récupérées dans le texte. Au moment de la soumission du formulaire, on reconstruit le shortcode et on le renvoie à TinyMCE via la fonction update().

		window.wp.mce.views.register( 'bouton', {
initialize: function() {
var titre = '<div class="my-view-wrapper">';
titre += '<p><strong>Bouton</strong></p>';
var ancre = getAttr( this.text, 'ancre' );
if ( ancre ) {
titre += '<p>« ' + _.escape( ancre ) + ' »</p>';
}

titre += '</div>';
this.render( titre );
},
edit: function( text, update ) {
editor.windowManager.open( {
title: 'Modifier un bouton',
body: [
{
label: 'Texte du bouton',
name: 'ancre',
type: 'textbox',
value: getAttr( text, 'ancre' ),
},
{
label: 'URL du lien',
name: 'lien',
type: 'textbox',
value: getAttr( text, 'lien' ),
}
],
onsubmit: function( e ) {
var out = '[bouton';
for ( var attr in e.data ) {
out += ' ' + attr + '="' + e.data[ attr ] + '"';
}
out += '/]';
update( out );
},
} );
},
} );
});

Vous savez maintenant tout ce qu’il faut pour vous lancer dans l’amélioration de vos interfaces d’édition d’article 🙂

Juste une dernière précision : bien que l’API n’a subit aucun changement depuis plus d’un an, elle est toujours marquée comme « expérimentale » dans le cœur. Si vous l’utilisez il faudra donc surveiller de temps en temps les mises à jour majeures de WordPress pour vérifier d’éventuels changements.

Bien sûr, n’hésitez-pas à laisser en commentaire vos créations basées sur ce tutoriel.

À bientôt !

  1. What You See Is What You Get ↩︎
  2. Et extensibles, il parait… si vous y arrivez faites-moi signe !↩︎
  3. Pour aller dans le détail : le shortcode est en fait maintenu dans un attribut data du nœud HTML de la view. Si vous souhaitez intervenir dessus en mode view, c’est ici qu’il faut chercher… ↩︎
7 commentaires
Marie, il y a 9 ans
Très intéressant cet article, avec des exemples concrets, comme d’habitude 😉
Hervé@RAISE UP, il y a 9 ans
Très intéressant cet article… Je vais le bookmarker, surtout qu’il m’a donné une bonne idée de plugin…
Il ne reste plus qu’à progresser en dev ou se faire aider, car seul, ce n’est pas gagné 😡
Laurent Olivares, il y a 9 ans
Encore un super article de ta part Willy, c’est un domaine que je ne connaissais pas et franchement ça donne presque envie (alors que je suis pas dev) 😉

PS : la note 3 est liée au petit 4 présent dans le texte

Thierry Pigot, il y a 9 ans
Merci Willy pour ce tuto très complet.

Je suis de moins en moins fan de l’utilisation des shortcodes, car pas toujours simple à utiliser par les clients, mais là ça ouvre de nouvelles perspectives 😉

Willy Bahuaud, il y a 9 ans
Oui je comprends ton point de vue. Pour injecter des données dans le contenu il y a deux possibilités : soit utilise des shortcodes (avec une bonne UI ça passe mieux), soit remplacer l’éditeur (en faisant attention à la compatibilité d’autres plugins) et jouer avec le filtre the_content pour construire et renvoyer un contenu mis en forme (via un builder, ACF, ou autre…).

J’opte de plus en plus pour la seconde option (avec ACF pour ma part), mais les deux ne sont pas incompatibles : on peut toujours ajouter des shortcodes dans un champs wp_editor 😉

Hervé@RAISE UP, il y a 9 ans
Si tu cherches des idées pour un prochain article… personnellement un tuto sur cette « seconde option » avec ACF, cela me dit bien… 😉
Brossault Antoine, il y a 8 ans
Le shortcode avec Tinymce que j’utilise le plus c’est pour faire de la mise en forme de texte (comme mettre en forme un chapeau par exemple)

Avec « `selection.setContent« «  vous pouvez juste selectionner votre texte, cliquer sur le bouton et bim !

Exemple :
« `
ed.addButton( ‘chapeau’, {
title : ‘Créer un chapeau’,
image : tempUrl+’/includes/shortcode/chapeau/sc_chapeau.svg’,
onclick : function() {

ed.selection.setContent(‘[chapeau]’+ed.selection.getContent()+'[/chapeau]’);
}
});

« `

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Publié le 06 juin 2016
par Willy Bahuaud
Catégorie Développement WordPress