Si vous créez des extensions pour WordPress, il y a de grandes chances que vous ayiez besoin d’une page dédiée pour les réglages.

Créer une page de réglages n’est pas bien compliqué, car WordPress mets à notre disposition une API pour ça. Non, pas besoin d’un framework pour créer des pages de réglages, et non, pas besoin de baliser, soumettre et traiter ses formulaires soit même en utilisant l’API des Options.

Ce qui est cool avec la Settings API, c’est qu’on n’a même pas besoin de s’occuper de sécurité et de la sauvegarde de nos données. WordPress fait tout pour nous !

Il suffit de connaitre 6 fonctions et de bien orchestrer le tout. Rien de plus. Après cet article, vous n’aurez aucune excuse pour ne pas utiliser la Settings API et créer des pages de réglages robustes et bien intégrées à WordPress ! Aussi, cet article présente l’API assez brièvement, pour que vous puissiez vous lancer. J’en parle bien plus en détails dans mon guide le WPCookBook.

Voici donc rapidement les fonctions de la Settings API. C’est parti !

add_menu_page() et add_submenu_page() (bonus)

Ces fonctions sont des bonus et car techniquement elles ne comptent pas dans les six fonctions essentielles de la Settings API que je souhaite vous présenter. Mais bon.

Ces deux fonctions servent à créer un nouvel élément de menu ou de sous-menu dans l’administration et à créer la page correspondante. Donc on en aura besoin !

Il suffit de les hooker sur admin_menu et c’est parti.

// In plugin main file
add_action( 'admin_menu', 'example_menu_items' );
/**
 * Registers our new menu item
 */
function example_menu_items() {
    // Create a top-level menu item.
    $hookname = add_menu_page(
        __( 'Example Settings Page', 'example' ),  // Page title
        __( 'Example Settings Page', 'example' ),  // Menu title
        'manage_options',                     // Capabilities
        'example_settings_page',              // Slug
        'example_settings_page_markup',       // Display callback
        'dashicons-carrot',                   // Icon
        66                                    // Priority/position. Just after 'Plugins'
    );
}

/**
 * Markup callback for the settings page
 */
function example_settings_page_markup(){
    ?>
        <div class="wrap">
            <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        </div>
    <?php
}

La fonction add_menu_page( string $page_title, string $menu_title, string $capability, string $menu_slug, callable $function = '', string $icon_url = '', int $position = null ) prends 7 paramètres : un titre de page, titre de menu, la capacité nécessaire pour accéder au contenu, le slug de la page, une fonction de rappel pour afficher son contenu, une icône (au format SVG encodé en base64 ou alors un slug de Dashicons, comme ici), et une priorité.

Cette fonction seule n’affiche que l’élément de menu. Il reste à fournir la fonction de rappel qui va contenir le balisage de la page et donc nos réglages. Pour le moment, on affiche uniquement le titre de la page (grâce à la fonction get_admin_page_title() ) dans une <div> avec la classe CSS de wrap, qui est utilisée un peu partout dans l’administration de WordPress.

Page de réglages vierge.
Notre page est prête à accueillir nos réglages

add_submenu_page( string $parent_slug, string $page_title, string $menu_title, string $capability, string $menu_slug, callable $function = '', int $position = null ) fonctionne de la même manière, mais permet d’ajouter un sous-menu à un menu existant, qu’il faut donc passer en premier paramètre.

register_setting()

La fonction register_setting( string $option_group, string $option_name, array $args = array() ) se hooke sur admin_init et va simplement déclarer à WordPress l’existence d’un réglage. C’est tout.

La fonction demande un nom de groupe, qui peut être déjà existant (comme general ou discussion pour ajouter des réglages aux pages et groupes existants) ou personnalisé.

Ensuite, il faut lui passer le nom de votre réglage, et vous pouvez optionnellement passer un tableau d’options supplémentaires ou une chaine correspondant au nom d’une fonction de rappel qui sera utilisée pour nettoyer la valeur du réglage avant entrée en base de données.

add_action( 'admin_init', 'example_setting' );
/**
 * Registers a single setting
 */
function example_setting(){
    register_setting( 
        'example_setting',     // Settings group.
        'example_setting',     // Setting name
        'sanitize_text_field'  // Sanitize callback.
    );
}

Ici, on passe un nom simple, et on demande à ce que la fonction sanitize_text_field() soit utilisée pour nettoyer la valeur de notre réglage. Notez qu’on peut aussi passer des fonctions personnalisées. Mais pour cet exemple, on va faire un simple champ texte, donc sanitize_text_field() est tout à fait approprié.

add_settings_section()

La fonction add_settings_section( string $id, string $title, callable $callback, string $page ) va déclarer une section pour y ranger notre réglage. On lui passe un identifiant de section, un titre, une fonction de rappel s’il y a de l’HTML spécial à afficher, et la page à laquelle appartient la section.

Easy.

add_action( 'admin_init', 'example_setting' );
/**
 * Registers a single setting
 */
function example_setting(){
    register_setting( 
        ...
    );

    // Register a section to house our example setting
    add_settings_section( 
        'example_section',                   // Section ID
        __( 'Example section', 'example' ),  // Title
        'example_section_markup',            // Callback or empty string
        'example_settings_page'              // Page to display the section in.
    );
}

/**
 * Displays the content of the section
 * 
 * @param  array  $args  Arguments passed to the add_settings_section() function call
 */
function example_section_markup( $args ){}

Ici, ma fonction de rappel pour afficher l’HTML de la section est vide. Je pourrais donc passer une chaine vide comme troisième paramètre pour add_settings_section() et économiser une déclaration de fonction.

Je voulais juste vous montrer que la fonction de rappel reçoit les arguments passés à l’appel à add_settings_section() correspondant en paramètre. Aussi, si vous avez besoin d’afficher du texte explicatif entre le titre de la section et les réglages, c’est dans cette fonction de rappel que ça se passe.

add_settings_field()

La fonction add_settings_field( string $id, string $title, callable $callback, string $page, string $section = 'default', array $args = array() ) permet de déclarer un champ.

On lui passe un identifiant, un titre, une fonction de rappel pour afficher l’HTML nécessaire, l’identifiant de la page et de la section auxquels il appartient, et un tableau optionnel d’arguments supplémentaires qui seront passés à la fonction de rappel.

add_action( 'admin_init', 'example_setting' );
/**
 * Registers a single setting
 */
function example_setting(){
    register_setting( 
        ...
    );

    add_settings_section( 
        ...
    );

    // Register our example field
    add_settings_field( 
        'example_text_field',                   // Field ID
        __( 'Example text field', 'example' ),  // Title
        'example_text_field_markup',            // Callback to display the field
        'example_settings_page',                // Page
        'example_section',                      // Section
    );
}

/**
 * Displays our setting field
 * 
 * @param  array  $args  Arguments passed to corresponding add_settings_field() call
 */
function example_text_field_markup( $args ){
    $setting = get_option( 'example_setting' );
    $value   = $setting ?: '';
    ?>
        <input class="regular-text" type="text" name="example_setting" value="<?php echo esc_attr( $value ); ?>">
    <?php
}

Dans la fonction de rappel, il faut penser à faire correspondre l’attribut name de votre champ avec le nom de votre réglage déclaré avec register_setting(). Aussi, get_option() permet de récupérer la valeur de votre réglage. Il convient donc aussi de lui passer le bon nom !

Retour sur notre page de réglage

Jusqu’à maintenant, on a effectué tout ce qui est de l’ordre de la déclaration. Notre page, notre section et notre réglage sont déclarés, ainsi que les fonctions de rappel correspondantes pour afficher notre page (presque vide), notre section ( complètement vide) et notre champ (juste un élément <input>).

Il ne reste plus qu’à afficher tout ça, et si tout est déclaré correctement, ce sera fini et tout marchera comme par magie !

Dans la fonction de rappel de notre page de réglage, on va simplement placer un élément <form> qui va pointer vers options.php , et qui va envoyer ses données en POST.

/**
 * Markup callback for the settings page
 */
function example_settings_page_markup(){
    ?>
        <div class="wrap">
            <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
            <form action="options.php" method="POST">
            </form>
        </div>
    <?php
}

settings_fields()

La fonction est très simple. Elle prend un unique paramètre correspondant au nom d’un groupe de réglages, et va afficher les champs nonce, referer, action, et option_page correspondants pour que notre formulaire fonctionne.

/**
 * Markup callback for the settings page
 */
function example_settings_page_markup(){
    ?>
        <div class="wrap">
            <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
            <form action="options.php" method="POST">
                <?php
                    settings_fields( 'example_setting' );
                ?>
            </form>
        </div>
    <?php
}
Les champs cachés de notre formulaire de réglages sont présents dans l'inspecteur.
Nos champs cachés sont bien là.

do_settings_sections()

Notre formulaire est presque prêt. Il faut maintenant boucler sur nos sections et afficher les champs correspondants. C’est ce que fait la fonction do_settings_sections( string $page ). On lui passe un identifiant de page et bim ! Toutes les fonctions de rappel des sections et champs sont appelées, et donc tous les champs de toutes les sections de cette page sont affichés !

/**
 * Markup callback for the settings page
 */
function example_settings_page_markup(){
    ?>
        <div class="wrap">
            <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
            <form action="options.php" method="POST">
                <?php
                    settings_fields( 'example_setting' );
                    do_settings_sections( 'example_settings_page' );
                ?>
            </form>
        </div>
    <?php
}

submit_button()

Il ne manque qu’une chose : un bouton de soumission pour le formulaire. Easy. La fonction submit_button( string $text = null, string $type = 'primary', string $name = 'submit', bool $wrap = true, array|string $other_attributes = null ) sert à ça.

Elle dispose de plusieurs paramètres pour personnaliser le bouton, mais on n’en a pas vraiment besoin dans notre cas.

/**
 * Markup callback for the settings page
 */
function example_settings_page_markup(){
    ?>
        <div class="wrap">
            <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
            <form action="options.php" method="POST">
                <?php
                    settings_fields( 'example_setting' );
                    do_settings_sections( 'example_settings_page' );
                    submit_button();
                ?>
            </form>
        </div>
    <?php
}
La page de réglages est prête et fonctionne.
Notre page est prête et fonctionne !

Tout fonctionne. Vous pouvez soumettre vos réglages, et ils sont sauvegardés automatiquement. Notre fonction de nettoyage déclarée dans register_setting() est appelée automatiquement.

Pas besoin de vérifier le nonce, pas besoin de hooker quoique ce soit sur un hook dans options.php, pas besoin d’écrire de boucle pour lister nos champs, ni de baliser le tableau contenant nos réglages (si si, c’est une <table> !).

Il suffit de dire à WordPress : “Tiens WordPress, voilà mon réglage, avec son nom, son groupe, sa fonction de nettoyage, sa section, sa page, son HTML. Débrouille-toi maintenant.” Et il le fait.

A plusieurs de reprises dans le WPCookBook, je dis que WordPress est magique. Genre au moins dix fois. Mais franchement, c’est pas vrai ?

Ajoutons un autre réglage

Si vous voulez ajouter un autre réglage, on va éviter de multiplier les appels à register_setting(), car chaque réglage prendrait une entrée dans la table wp_options. On va donc stocker nos valeurs dans un tableau. Vous pouvez créer un réglage par section ou un par page, comme vous voulez.

On va simplement ajouter un appel à add_settings_field() et créer la fonction de rappel correspondante.

function example_setting(){
    
    register_setting(...);
    add_settings_section(...);


    // Register our example field
    add_settings_field(...);

    // Register a textarea field
    add_settings_field( 
        'example_textarea',                   // Field ID
        __( 'Example textarea', 'example' ),  // Title
        'example_textarea_markup',            // Callback to display the field
        'example_settings_page',              // Page
        'example_section',                    // Section
    );
}

/**
 * Displays a simple textarea
 * 
 * @param  array  $args  Arguments passed to corresponding add_settings_field() call
 */
function example_textarea_markup( $args ){
    $setting = get_option( 'example_setting' );
    $value = ! empty( $setting['textarea'] ) ? $setting['textarea'] : '';
    ?>
        <textarea name="example_setting[textarea]" rows=6 class="regular-text">
             <?php echo esc_textarea( $value ); ?>
        </textarea>
    <?php
}

Vous remarquerez que l’attribut name de la balise <textarea> fait référence à un tableau. On aura dans ce tableau une clé textarea ayant pour valeur la valeur du champ. On a aussi dû changer la façon dont on récupère sa valeur.

Il faut faire de même pour notre premier champ (le champ texte tout simple), et changer l’attribut name de la balise input ainsi que la façon dont on récupère la valeur du champ.

/**
 * Displays our setting field
 * 
 * @param  array  $args  Arguments passed to corresponding add_settings_field() call
 */
function example_text_field_markup( $args ){
    $setting = get_option( 'example_setting' );
    $value = ! empty( $setting['text'] ) ? $setting['text'] : '';
    ?>
        <input class="regular-text" type="text" name="example_setting[text]" value="<?php echo esc_attr( $value ); ?>">
    <?php
}

Aussi, on va ajuster notre appel à register_setting(), car sanitize_text_field() ne conviendra plus pour nettoyer notre tableau de réglages.

add_action( 'admin_init', 'example_setting' );
/**
 * Registers a single setting
 */
function example_setting(){
    
    // Register a single setting.
    register_setting( 
        'example_setting',     // Settings group.
        'example_setting',     // Setting name
        'example_sanitize'     // Custom sanitize callback
    );
    ...
}
/**
 * Sanitize our settings array
 * 
 * @param  array  $settings  Our settings value
 */
function example_sanitize( $settings ){
    $settings['text'] = ! empty( $settings['text'] ) ? sanitize_text_field( $settings['text'] ) : '';
    $settings['textarea'] = ! empty( $settings['textarea'] ) ? sanitize_textarea_field( $settings['textarea'] ) : '';
    return $settings;
}

Notre fonction de nettoyage reçoit un tableau contenant les valeurs des réglages, et on utilise simplement les fonctions sanitize_text_field() et sanitize_textarea_field() pour les nettoyer avant l’entrée en base de données.

C’est juste que puisqu’on a déclaré qu’un seul réglage et que l’on stocke tout dans un tableau unique, il nous faut traiter ce tableau nous-même.

Page de réglages avec deux réglages.
Nos deux réglages fonctionnent !

Tout est automatique. Pas d’appel à update_option() pour sauvegarder notre réglage. Rien du tout. Magic !

En résumé :

  • register_setting() déclare un réglage
  • add_settings_section() déclare une section pour y ranger nos réglages
  • add_settings_field() déclare un champ
  • settings_field() affiche les champs cachés nécessaires pour traiter notre formulaire
  • do_settings_sections() va boucler sur nos sections pour y afficher les champs associés
  • submit_button() affiche le bouton de soumission du formulaire.

Trois fonctions pour déclarer. Trois fonctions pour afficher. Et c’est tout.

Si vous avez suivi ce tutoriel, vous devriez avoir saisi les bases de la Settings API et devriez être en mesure de créer une page de réglages simples pour votre extension. C’est un peu de travail de déclaration et de boilerplate, mais une fois ce boilerplate écrit, votre page de réglage est très simple à exploiter et à enrichir.

Mais vu que je vous aime bien, je vais vous épargner un peu de boilerplate, en vous en fournissant un ! Vous pouvez télécharger une petite extension avec des exemples de champs divers que vous pourrez copier-coller dans vos projets, selon vos besoins ! Avec les traductions s’il vous plait. Cadeau !

Le code est abondamment commenté, donc rien qu’en lisant vous allez pouvoir comprendre ce qu’il se passe à chaque étape !

https://github.com/vincedubroeucq/settings-api-boilerplate

Si vous avez aimé cet article, je parle bien plus en détails de la Settings API dans le WPCookBook. Le guide est rempli de tutoriels détaillés et de mini-projets pour bien développer pour WordPress, et inclut des chapitres comme “Comment traiter les données de formulaire“, ou “Comment créer des réglages dans l’outil de personnalisation de WordPress“. Bref, si vous avez aimé cet article vous adorerez le WPCookBook !

Enjoy !