WordPress : automatiser l’ajout d’un bouton TinyMCE lors de l’ajout d’un shortcode

Aujourd’hui je reviens afin de vous parler d’une « problématique » que j’ai eu dans le développement de shortcodes sur WordPress.

Je ne sais pas si vous avez eu à développer pour un site média mais les shortcodes sur WordPress sont un outil indispensables pour styliser et ajouter des fonctionnalités pour un article. Sauf que là est le problème : plus vous avez de shortcodes plus il est difficile de les garder en tête. C’est pour cela qu’une bonne pratique est d’ajouter des boutons sur l’éditeur TinyMCE de WordPress !

Mais il n’est pas simple (enfin si si on met des post-it sur son bureau) lorsque que l’on est sur de multiple tâches différentes de penser à rajouter le bouton dans l’éditeur et puis même cela rajoute toujours quelques lignes à vos fichiers pour finir à plusieurs centaines de lignes pour presque faire la même chose.

Ajouter un shortcode ainsi qu’un bouton sur TinyMCE sans gestionnaire de shortcodes

Pour cette première partie je vais vous montrer comment de base ajouter un shortcode sur WordPress ainsi qu’un bouton pour insérer ce shortcode dans l’éditeur afin que vous visualisez le nombre de ligne de code que cela engendre.

Pour commencer nous allons ajouter à votre plugin ou à votre thème un shortcode que l’on va appeler « Hello World ». Ce shortcode va juste afficher « Hello World » lorsque on l’appellera.

Pour cela utilisez la fonction add_shortcode de WordPress.

<?php
function call_hello_world(){
    echo "Hello World";
}
add_shortcode('hello-world', "call_hello_world");

Et maintenant allons ajouter le bouton sur TinyMCE. Tout d’abord ajouter le bouton dans la liste des boutons de l’éditeur à travers le filtre « mce_buttons ». Ces lignes permettent juste d’informer TinyMCE des ID de boutons qui vont être affichés ou supprimés. Mais en rien on informe TinyMCE de l’ajout du bouton dans le code Javascript.

<?php
function register_mce_button( $buttons ){
    array_push($buttons, 'hello-world');
    return $buttons;
}

if ( current_user_can( 'edit_posts' ) && current_user_can( 'edit_pages' ) ) {
    add_filter('mce_buttons', 'register_mce_button' );
}

Allons maintenant ajouter le code JS afin d’ajouter le bouton. Pour cela nous devons enregistrer un nouveau plugin à TinyMCE via le filtre « mce_external_plugins ». Ce filtre va permettre d’ajouter un fichier js où l’on va configurer le plugin dans le tableau comprenant tous les plugins externes de TinyMCE.

<?php

/**
 * Register the TinyMCE Plugin
 * @param $plugin_array
 * @return mixed
 */
function ad_mce_plugin( $plugin_array ){
    $plugin_array['shortcodes_plugin'] = get_template_directory_uri().'/js/tinymce/shortcode-buttons.js';
    return $plugin_array;
}

if ( current_user_can( 'edit_posts' ) && current_user_can( 'edit_pages' ) ) {
    add_filter( 'mce_external_plugins',  'ad_mce_plugin' );
}

Le code JS du plugin :

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

        editor.addButton('hello-world', {
            text: 'Hello World',
            icon: false,
            onclick: function () {
                editor.insertContent("[hello-world]");
            }
        });
    });

})();

Nous enregistrons le plugin avec le même nom que celui mis en PHP ainsi que le bouton avec aussi le même nom que celui enregistré précédemment.

Et voilà vous avez en quelques lignes un bouton sur l’éditeur TinyMCE qui va ajouter directement le shortcode dans l’éditeur !

Les points négatifs

Imaginons que vous travaillez dans un environnement avec plus de 30 shortcodes par exemple. Et bien vous devriez faire une boucle pour la fonction add shortcode puis autre part une boucle pour l’ajout des boutons sur tinymce plus l’ajout dans le javascript… Il y a des chances que vous oubliez un élément. C’est pour cela que maintenant je vais vous expliquer ce que moi j’ai mis en place afin d’avoir un code plus propre, peut être plus long au début mais vous n’aurez plus besoin après de toucher à TinyMCE et à ses filtres.

Le principe

Le principe est assez simple : donner à une Classe la tâche d’ajouter les shortcodes qui ne seront plus une fonction mais une Classe Shortcode spécifique que nous allons créer. Chaque Shortcode aura sa classe dans lequel nous configurerons ses paramètres de shortcode ainsi que ses paramètres de TinyMCE. Nous allons donc créer des classes qui vont tous avoir une tâche différentes.

Création du système

Nous allons donc créer la Class PHP abstraite pour les Shortcodes. Nous allons l’appeler CustomShortcode.

<?php

abstract class CustomShortcode
{
    public static $itsTag;

    /**
     * Add the shortcode to wordpress
     * CustomShortcode constructor.
     */
    public function __construct(){
        add_shortcode($this->getTag(), array($this, 'actionShortcode'));
    }

    /**
     * Get shortcode Tag
     * @return mixed
     */
    public function getTag(){
        return $this::$itsTag;
    }

    public abstract function getDefaultParams();
    public abstract function getMceParams();

    protected abstract function render($atts, $content);

    /**
     * Mix atts of shortcode and default parameters and finally render the shortcode
     * @param $atts
     * @param null $content
     * @return mixed
     */
    public function actionShortcode($atts, $content = null){
        $atts = shortcode_atts($this->getDefaultParams(), $atts );
        return $this->render($atts, $content);
    }

    /**
     * Generate shortcode string
     * @param string $tag
     * @param array $params
     * @param string $content
     * @return string
     */
    public static function __createShortcodeString($tag, $params, $content){
        $start = '['.$tag;

        foreach($params as $key => $value){
            $start .= ' '.$key.'="'.$value.'"';
        }

        $start .= ']';
        $end = '[/'.$tag.']';
        return $start.$content.$end;
    }

    /**
     * Generate the shortcode string
     * @return string
     */
    public function createDefaultShortcodeString(){
        return $this::__createShortcodeString($this::$itsTag, $this->getDefaultParams(), '');
    }

}
  • $itsTag correspond au tag du shortcode, ici cela serait « hello-world »,
  • Le constructeur va lui ajouter le shortcode dans WordPress,
  • Chaque shortcode devra implémenter les fonctions « getDefaultParams » et « getMceParams » qui permettent de configurer les paramètres par défaut du shortcode ainsi que les paramètres pour l’éditeur de TinyMCE ainsi que la fonction « render » qui permet d’afficher le contenu du shortcode,
  • la fonction « actionShortcode » est la fonction appelée par chaque « add_shortcode » et permet d’appeler la fonction render du shortcode. Cela permet d’automatiser la fusion des attributs du shortcode afin de ne pas devoir refaire appel à la fonction shortcode_atts à chaque fois dans le render 🙂
  • « __createShortcodeString » permet de générer le shortcode en version text, pour notre dernier shortcode cela générerait « [hello-world] »

Voilà nous avons donc notre class Shortcode dont chaque nouveau shortcode à ajouter à WordPress devra étendre. Cela permet bien de séparer chaque shortcode afin de ne pas avoir toutes les fonctions de shortcodes dans functions.php ou un autre fichier.

Il nous reste deux choses à faire :

  • Créer une classe qui va sauvegarder les shortcodes ajoutés
  • Créer la classe qui intégrera les boutons sur TinyMCE

Créons donc une classe qui va instancier/sauvegarder les shortcodes ainsi que d’appeler la classe qui ajoutera les boutons des shortcodes dans TinyMCE :

<?php

class ClassA
{
    public static $shortcodes = array();
    public function __construct(){
       self::$shortcodes = array(
            HellowWorldShortcode::$itsTag => new HellowWorldShortcode(),
        );

        add_action('admin_init', function(){
            $tinyMce = new TinyMceIntegrator(self::$shortcodes);
            $tinyMce->addShortcodeToTinyMce();
        });
    }
}

Et créons la classe qui va intégrer les boutons sur TinyMCE dont je vais expliquer en détail :

<?php

class TinyMceIntegrator
{
    /**
     * @var CustomShortcodes[]
     */
    private $shortcodes;

    public function __construct(array $shortcodes)
    {
        $this->shortcodes = $shortcodes;
    }

    /**
     * Add filter and action for TinyMCE
     * @param array $shortcodes
     */
    public function addShortcodeToTinyMce(){
        if ( current_user_can( 'edit_posts' ) && current_user_can( 'edit_pages' ) ) {
            add_filter('mce_buttons', array($this, 'registerMceButtons'));
            add_filter( 'mce_external_plugins', array($this, 'addMCEPlugin') );
        }

        foreach ( array('post.php','post-new.php') as $hook ) {
            add_action( "admin_head-$hook", array($this, 'initVarJavascript') );
        }
    }

    /**
     * Add all Shortcodes buttons to TinyMCE
     * @param $buttons
     * @return mixed
     */
    public function registerMceButtons( $buttons ){
        global $sanitizer;
        foreach($this->shortcodes as $aShortcode){
            array_push($buttons, $aShortcode->getTag());

            //Add parent buttons
            $mceParam = $aShortcode->getMceParams();
            if($mceParam['parent'] !== false){
                $sanitizeParent = $sanitizer->simpleSanitize($mceParam['parent']);
                if(!in_array($sanitizeParent, $buttons)){
                    array_push($buttons, $sanitizeParent);
                }
            }
        }

        return $buttons;
    }

    /**
     * Register the TinyMCE Plugin
     * @param $plugin_array
     * @return mixed
     */
    public function addMCEPlugin( $plugin_array ){
        $plugin_array['shortcodes_plugin'] = get_template_directory_uri().'/shortcode-buttons.js';
        return $plugin_array;
    }

    /**
     * Initialize javascript var for TinyMCE plugin
     */
    public function initVarJavascript(){
        global $sanitizer;
?>
        <script type='text/javascript' id="shortcode_tmce_buttons">
            var buttonsNotParent = {};
            var buttonsWithParent = {};
            var textParent = {};
            //initialize if parent
<?php

            //loop shortcodes and get tinyMCE param
            // first loop is to init array of parent
            foreach($this->shortcodes as $aShortcode){
                $mceParam = $aShortcode->getMceParams();
                if($mceParam['parent'] !== false){
                    $sanitizeParent =  $sanitizer->simpleSanitize($mceParam['parent']);
?>
                    buttonsWithParent.<?= $sanitizeParent ?> = [];
                    textParent.<?= $sanitizeParent ?> = "<?= $mceParam['parent'] ?>";
<?php
                }
            }
?>
            //push if parent and finish
<?php
            foreach($this->shortcodes as $aShortcode){
                $mceParam = $aShortcode->getMceParams();
                $defaultShortcodeString = $aShortcode->createDefaultShortcodeString();
                $tagShortcode = $aShortcode::$itsTag;
                if($mceParam['parent'] !== false){
?>
                   buttonsWithParent.<?php echo $sanitizer->simpleSanitize($mceParam['parent']) ?>.push({
                        text: "<?= $mceParam['text'] ?>",
                        onclick: function(){
                            editor = tinyMCE.get(wpActiveEditor);
                            var content = '<?php echo $defaultShortcodeString ?>';
                            editor.insertContent(content);
                        }
                    });
<?php
                }
                else{
?>
                    buttonsNotParent.<?= $tagShortcode ?> = {
                        text: "<?= $mceParam['text'] ?>",
<?php
                        if(isset($mceParam['icon']) && $mceParam['icon'] != false){
?>
                        icon: "<?= $mceParam['icon'] ?>",
<?php
                        }
                        else{
?>
                        icon: false,
<?php
                        }
?>
                        onclick: function(){
                            editor = tinyMCE.get(wpActiveEditor);
                            var content = '<?php echo $defaultShortcodeString ?>';
                            editor.insertContent(content);
                        }
                    };
<?php
                }
            }
?>
        </script>
<?php
    }
}

Si vous vous rappelez cette Classe va reprendre le code plus haut mais que je vais intégrer directement à tous les shortcodes.

  • « addShortcodeToTinyMce » permet d’appeler les filtres de TinyMCE « mce_buttons » et « mce_external_plugins » ainsi que deux actions « admin_head-post.php » et « admin_head-post-new.php »
  • « registerMceButtons » est la fonction appelée pour le hook mce_buttons, on boucle juste sur les shortcodes. Un bouton sera ajouté pour chaque shortcode. L’id du bouton sera donc le tag du shortcode ici
  • « addMCEPlugin » est appelée pour ajouter le plugin js comme plus haut dans la première partie

Je vais vous expliquer à quoi sert la fonction « initVarJavascript ». Elle me permet d’ajouter des variables globales JS afin de les récupérer dans le plugin TinyMCE que j’ai enregistré. Dans cette fonction je vais configurer les boutons à ajouter en fonction des paramètres TinyMCE de l’objet Shortcode instancié. Comme ça je n’aurai plus qu’à faire une boucle sur ces variables et ajouter les boutons. Simple non ? 🙂

Allez je vous mets enfin le fichier JS qui va permettre d’ajouter les boutons côté Javascript 😉 :

(function() {
    /*
     var buttonsNotParent = [];
     var buttonsWithParent = {};
     var textParent = {};
     */
    /* Register the buttons */
    tinymce.create('tinymce.plugins.ShortcodeButtons', {
        init : function(editor, url) {
            for(var slugParent in buttonsWithParent){
                editor.addButton( slugParent,{
                    type: "menubutton",
                    text: textParent[slugParent],
                    icon: false,
                    menu: buttonsWithParent[slugParent]
                });
            }

            for(var slugButton in buttonsNotParent){
                editor.addButton( slugButton, buttonsNotParent[slugButton]);
            }

        },
        createControl : function(n, cm) {
            return null;
        },
    });
    /* Start the buttons */
    tinymce.PluginManager.add( 'shortcodes_plugin', tinymce.plugins.ShortcodeButtons );
})();

Quoi ? Seulement 30 lignes ? Et oui et oui seulement 30 et vous n’aurez plus besoin d’y toucher ensuite ! Sympas non ?

Bon après ça il nous reste plus qu’à créer notre petit shortcode HelloWorld dans ce système 🙂
Donc on crée notre classe :

<?php

class HelloWorldShortcode extends CustomShortcode
{
    public static $itsTag = 'hello-world';

    public function getDefaultParams()
    {
        return array(
        );
    }

    public function getMceParams()
    {
        return array(
            'parent' => false,
            'icon' => false,
            'text' => 'Hello World',
        );
    }

    protected function render($atts, $content)
    {
        echo "Hello World";
    }
}

Et on l’instancie dans notre ClassA comme plus haut et le tour est joué vous avez ajouté un shortcode HelloWorld sur WordPress en même temps qu’un bouton TinyMCE afin d’intégrer ce shortcode dans un futur (mega génial) article !

 

Voilà le tutoriel touche à sa fin, j’espère qu’il vous a été utile et vous aidera à rendre le codage de thème ou de plugin WordPress plus propre et facile :).

Si vous avez des questions, des remarques sur le code ou sur WordPress n’hésitez pas à laisser un commentaire et je vous répondrai. J’ai imaginé ce mini système car je crée un thème WordPress Premium et du coup je crée mon petit framework pour être plus rapide dans mes développements plus tard.

À plus tard pour un nouveau tutoriel sur du développement WordPress 🙂