Creare e gestire le opzioni di personalizzazione di un tema WordPress con le sue API
Ogni tema WordPress di qualita' offre un insieme di opzioni che permettono all'utente di modificare colori, tipografia, layout e comportamenti senza toccare una sola riga di codice. Il cuore di questo sistema e' la Theme Customization API, comunemente nota come Customizer, un'infrastruttura nativa che fornisce un'anteprima in tempo reale delle modifiche e le salva in modo sicuro nel database. In questo articolo vedremo come registrare impostazioni, sezioni, pannelli e controlli, come sanitizzare i dati in ingresso, come costruire controlli personalizzati e come implementare l'anteprima live tramite postMessage e il refresh selettivo.
L'architettura del Customizer
L'API di personalizzazione si basa su quattro concetti fondamentali che collaborano tra loro. Le impostazioni (settings) rappresentano i valori memorizzati nel database: sono l'unita' di dato vera e propria. Le sezioni (sections) raggruppano visivamente i controlli correlati. I pannelli (panels) sono contenitori di livello superiore che raggruppano piu' sezioni. I controlli (controls) sono gli elementi dell'interfaccia (campi di testo, selettori di colore, menu a discesa) che l'utente manipola e che sono collegati a una o piu' impostazioni.
La regola concettuale da ricordare e' semplice: un'impostazione contiene il dato, un controllo lo espone nell'interfaccia e una sezione lo contiene. Un'impostazione senza controllo non e' modificabile dall'utente, e un controllo senza impostazione non ha nulla da salvare.
L'hook customize_register
Tutta la registrazione degli oggetti del Customizer avviene all'interno di una funzione agganciata all'action customize_register. WordPress passa a questa funzione un'istanza di WP_Customize_Manager, convenzionalmente chiamata $wp_customize, che espone i metodi per aggiungere pannelli, sezioni, impostazioni e controlli.
<?php
// Aggancia la registrazione all'hook customize_register
function mytheme_customize_register( $wp_customize ) {
// Aggiunge una sezione dedicata ai colori
$wp_customize->add_section( 'mytheme_colors', array(
'title' => __( 'Colori del tema', 'mytheme' ),
'priority' => 30,
'description' => __( 'Personalizza i colori principali del tema.', 'mytheme' ),
) );
}
add_action( 'customize_register', 'mytheme_customize_register' );
Il parametro priority determina l'ordine di visualizzazione: valori piu' bassi appaiono piu' in alto. Le stringhe visibili all'utente vengono sempre passate attraverso la funzione di traduzione __() con il text domain del tema, così da rendere l'interfaccia localizzabile.
Registrare un'impostazione
Il metodo add_setting() definisce dove e come viene memorizzato un valore. Gli argomenti piu' importanti sono default (il valore iniziale), type (che puo' essere theme_mod oppure option), transport (che stabilisce se l'anteprima si aggiorna ricaricando la pagina o via JavaScript) e soprattutto sanitize_callback, la funzione che ripulisce il valore prima del salvataggio.
// Registra un'impostazione per il colore del testo
$wp_customize->add_setting( 'mytheme_text_color', array(
'default' => '#333333',
'type' => 'theme_mod',
'capability' => 'edit_theme_options',
'transport' => 'refresh',
'sanitize_callback' => 'sanitize_hex_color',
) );
Il valore di type merita attenzione. Con theme_mod il dato viene salvato in modo specifico per il tema attivo e si recupera con get_theme_mod(); e' l'opzione consigliata per le impostazioni estetiche. Con option il dato viene salvato nella tabella globale delle opzioni e persiste al cambio di tema; e' utile per configurazioni condivise come chiavi API.
Collegare un controllo all'impostazione
Un'impostazione diventa modificabile solo quando le si associa un controllo tramite add_control(). WordPress fornisce numerosi controlli predefiniti: campi di testo, aree di testo, checkbox, radio, menu a discesa e controlli specializzati come WP_Customize_Color_Control per la scelta dei colori o WP_Customize_Image_Control per le immagini.
// Collega un controllo colore all'impostazione appena creata
$wp_customize->add_control( new WP_Customize_Color_Control( $wp_customize, 'mytheme_text_color', array(
'label' => __( 'Colore del testo', 'mytheme' ),
'section' => 'mytheme_colors',
'settings' => 'mytheme_text_color',
) ) );
Quando si usa una classe di controllo specializzata occorre istanziarla passando l'oggetto $wp_customize, l'identificatore e l'array di argomenti. Per i controlli generici, invece, e' sufficiente passare l'identificatore dell'impostazione e specificare il type nell'array.
Organizzare le opzioni con i pannelli
Quando le sezioni diventano numerose conviene raggrupparle in pannelli. Un pannello si crea con add_panel() e le sezioni vi si associano indicando l'argomento panel nella chiamata a add_section().
// Crea un pannello che raggruppa piu' sezioni correlate
$wp_customize->add_panel( 'mytheme_theme_options', array(
'title' => __( 'Opzioni del tema', 'mytheme' ),
'description' => __( 'Impostazioni avanzate del tema.', 'mytheme' ),
'priority' => 160,
) );
// Assegna la sezione al pannello tramite l'argomento panel
$wp_customize->add_section( 'mytheme_layout', array(
'title' => __( 'Layout', 'mytheme' ),
'panel' => 'mytheme_theme_options',
'priority' => 10,
) );
I pannelli non contengono controlli direttamente: fungono solo da contenitori di sezioni. E' buona pratica riservarli ai temi con molte opzioni, per non appesantire inutilmente l'interfaccia dei temi piu' semplici.
Tipi di controllo comuni
Oltre ai selettori di colore, i controlli piu' usati sono le checkbox per le opzioni booleane e i menu a discesa per le scelte tra alternative predefinite. Ogni controllo richiede una propria impostazione con la relativa callback di sanitizzazione.
// Impostazione e controllo di tipo checkbox
$wp_customize->add_setting( 'mytheme_show_search', array(
'default' => true,
'sanitize_callback' => 'mytheme_sanitize_checkbox',
) );
$wp_customize->add_control( 'mytheme_show_search', array(
'label' => __( 'Mostra la barra di ricerca', 'mytheme' ),
'section' => 'mytheme_layout',
'type' => 'checkbox',
) );
// Impostazione e controllo a discesa (select)
$wp_customize->add_setting( 'mytheme_sidebar_position', array(
'default' => 'right',
'sanitize_callback' => 'mytheme_sanitize_select',
) );
$wp_customize->add_control( 'mytheme_sidebar_position', array(
'label' => __( 'Posizione della sidebar', 'mytheme' ),
'section' => 'mytheme_layout',
'type' => 'select',
'choices' => array(
'left' => __( 'Sinistra', 'mytheme' ),
'right' => __( 'Destra', 'mytheme' ),
'none' => __( 'Nessuna', 'mytheme' ),
),
) );
Nel controllo di tipo select l'array choices definisce le coppie chiave/etichetta: le chiavi sono i valori salvati nel database, mentre le etichette sono i testi mostrati all'utente.
Sanitizzare i dati in ingresso
La sanitizzazione non e' facoltativa: e' la difesa principale contro dati malformati o malevoli. Ogni impostazione dovrebbe dichiarare una sanitize_callback che riceve il valore grezzo e restituisce una versione ripulita. Per i tipi comuni WordPress mette a disposizione funzioni pronte come sanitize_hex_color, absint, sanitize_text_field ed esc_url_raw; per i casi specifici conviene scrivere callback dedicate.
// Sanitizza un valore booleano proveniente da una checkbox
function mytheme_sanitize_checkbox( $checked ) {
// Restituisce true solo se il valore e' impostato ed effettivamente vero
return ( isset( $checked ) && true === (bool) $checked );
}
// Sanitizza un valore proveniente da un menu a discesa
function mytheme_sanitize_select( $input, $setting ) {
// Recupera l'elenco delle scelte valide dal controllo associato
$choices = $setting->manager->get_control( $setting->id )->choices;
// Se il valore non e' tra le scelte ammesse, ripristina il default
return array_key_exists( $input, $choices ) ? $input : $setting->default;
}
La callback per il menu a discesa mostra un pattern robusto: invece di fidarsi del valore ricevuto, verifica che sia effettivamente una delle chiavi ammesse e, in caso contrario, ripristina il valore di default. Questo impedisce che un valore arbitrario venga scritto nel database aggirando l'interfaccia.
Creare un controllo personalizzato
Quando i controlli nativi non bastano, si puo' estendere la classe WP_Customize_Control e sovrascrivere il metodo render_content(), responsabile della generazione del markup. All'interno del metodo sono disponibili proprieta' utili come $this->label, $this->value() per il valore corrente e $this->get_link(), che restituisce gli attributi necessari a collegare l'elemento all'impostazione.
// Controllo personalizzato che estende la classe base del Customizer
class Mytheme_Range_Control extends WP_Customize_Control {
// Tipo univoco che identifica il controllo
public $type = 'mytheme_range';
// Genera il markup HTML del controllo nel pannello
public function render_content() {
// Prepara un identificatore univoco per l'elemento input
$input_id = '_customize-input-' . $this->id;
// Stampa l'etichetta del controllo
printf(
'<label for="%1$s" class="customize-control-title">%2$s</label>',
esc_attr( $input_id ),
esc_html( $this->label )
);
// Stampa il campo range collegato all'impostazione
printf(
'<input type="range" id="%1$s" min="0" max="100" value="%2$s" %3$s />',
esc_attr( $input_id ),
esc_attr( $this->value() ),
$this->get_link()
);
}
}
Una volta definita la classe, la si registra come qualsiasi altro controllo specializzato, istanziandola all'interno del gancio customize_register.
// Impostazione associata al controllo personalizzato
$wp_customize->add_setting( 'mytheme_content_width', array(
'default' => 60,
'sanitize_callback' => 'absint',
'transport' => 'postMessage',
) );
// Istanzia e aggiunge il controllo personalizzato alla sezione
$wp_customize->add_control( new Mytheme_Range_Control( $wp_customize, 'mytheme_content_width', array(
'label' => __( 'Larghezza del contenuto (%)', 'mytheme' ),
'section' => 'mytheme_layout',
) ) );
Il metodo get_link() e' cruciale: emette gli attributi data-customize-setting-link che permettono al Customizer di riconoscere l'elemento e di propagare le modifiche all'impostazione associata. Senza di esso il controllo verrebbe visualizzato ma non salverebbe nulla.
Anteprima live con postMessage
Il transport di un'impostazione puo' assumere due valori. Con refresh l'anteprima ricarica l'intera pagina a ogni modifica: e' l'impostazione predefinita, semplice ma poco fluida. Con postMessage le modifiche vengono applicate via JavaScript senza ricaricare nulla, offrendo un'esperienza molto piu' reattiva. Quest'ultima opzione richiede pero' di scrivere manualmente lo script che aggiorna il DOM.
( function( $ ) {
'use strict';
// Aggiorna il colore del testo senza ricaricare l'anteprima
wp.customize( 'mytheme_text_color', function( value ) {
value.bind( function( newValue ) {
$( 'body' ).css( 'color', newValue );
} );
} );
// Aggiorna la larghezza del contenuto in tempo reale
wp.customize( 'mytheme_content_width', function( value ) {
value.bind( function( newValue ) {
$( '.site-content' ).css( 'max-width', newValue + '%' );
} );
} );
} )( jQuery );
Lo script si registra e si carica solo nel contesto dell'anteprima, agganciandosi all'action customize_preview_init e dichiarando la dipendenza dallo handle customize-preview.
// Carica lo script che gestisce l'anteprima live nel Customizer
function mytheme_customize_preview_js() {
wp_enqueue_script(
'mytheme-customizer-preview',
get_template_directory_uri() . '/js/customizer-preview.js',
array( 'customize-preview', 'jquery' ),
'1.0.0',
true
);
}
add_action( 'customize_preview_init', 'mytheme_customize_preview_js' );
Il codice JavaScript sottoscrive ogni impostazione tramite wp.customize() e, attraverso il metodo bind(), reagisce a ogni variazione del valore aggiornando direttamente gli elementi della pagina. In questo modo l'utente vede l'effetto della modifica istantaneamente.
Refresh selettivo con i partial
Il postMessage e' ideale per proprieta' CSS semplici, ma diventa scomodo quando la modifica riguarda contenuto complesso generato da PHP. Per questi casi esiste il selective refresh: si registra un partial che indica quale porzione del DOM aggiornare e quale callback PHP usare per rigenerarne il contenuto. WordPress ricarica via AJAX solo quel frammento, non l'intera pagina.
// Registra un partial per il refresh selettivo del nome del sito
$wp_customize->selective_refresh->add_partial( 'blogname', array(
'selector' => '.site-title a',
'container_inclusive' => false,
'render_callback' => 'mytheme_customize_partial_blogname',
) );
// Restituisce il contenuto aggiornato del partial
function mytheme_customize_partial_blogname() {
// Stampa il nome del blog gia' escapato da WordPress
bloginfo( 'name' );
}
Il selector individua l'elemento nel DOM, mentre render_callback punta alla funzione che ne rigenera il contenuto. Il selective refresh combina il meglio dei due mondi: la reattivita' dell'anteprima parziale e l'affidabilita' del rendering lato server.
Recuperare e utilizzare i valori
Una volta salvate, le opzioni vanno effettivamente applicate al frontend. I valori di tipo theme_mod si leggono con get_theme_mod(), passando come secondo argomento lo stesso default dichiarato in registrazione. Un pattern molto comune consiste nel generare CSS dinamico da iniettare nell'header del sito tramite l'action wp_head.
// Genera il CSS dinamico a partire dai valori del Customizer
function mytheme_customizer_css() {
// Recupera i valori salvati, con fallback ai default
$text_color = get_theme_mod( 'mytheme_text_color', '#333333' );
$content_width = absint( get_theme_mod( 'mytheme_content_width', 60 ) );
// Costruisce le regole CSS escapando ogni valore dinamico
$css = 'body { color: ' . esc_attr( $text_color ) . '; }';
$css .= '.site-content { max-width: ' . $content_width . '%; }';
// Inietta il CSS inline nell'header del sito
echo '<style id="mytheme-custom-css">' . $css . '</style>';
}
add_action( 'wp_head', 'mytheme_customizer_css' );
E' fondamentale escapare ogni valore dinamico anche in output, non solo in ingresso: esc_attr() per i valori usati come attributi o proprieta' CSS e absint() per i numeri interi. La sanitizzazione in scrittura e l'escaping in lettura sono due difese complementari, entrambe necessarie.
Quando invece un'impostazione e' di tipo option, il valore non si recupera con get_theme_mod() ma con get_option(), coerentemente con il modo in cui e' stato salvato.
// Impostazione salvata come opzione anziche' come theme_mod
$wp_customize->add_setting( 'mytheme_api_key', array(
'type' => 'option',
'default' => '',
'capability' => 'manage_options',
'sanitize_callback' => 'sanitize_text_field',
) );
// Le opzioni si recuperano con get_option, non con get_theme_mod
$api_key = get_option( 'mytheme_api_key', '' );
Buone pratiche
Alcuni accorgimenti rendono le opzioni di un tema affidabili e mantenibili. Dichiarare sempre una sanitize_callback per ogni impostazione, senza eccezioni. Fornire un default sensato e usarlo coerentemente sia in registrazione sia in lettura. Prefissare identificatori e nomi di funzione con il nome del tema per evitare collisioni con altri temi o plugin. Preferire theme_mod per le opzioni estetiche e riservare option ai dati che devono sopravvivere al cambio di tema. Infine, usare postMessage e il selective refresh dove migliorano davvero l'esperienza, senza complicare inutilmente le opzioni piu' semplici.
Conclusione
La Theme Customization API offre un'infrastruttura completa e sicura per esporre le opzioni di un tema, dall'impostazione piu' banale al controllo personalizzato con anteprima in tempo reale. Padroneggiando i quattro elementi fondamentali (impostazioni, sezioni, pannelli e controlli), le callback di sanitizzazione e i meccanismi di anteprima postMessage e selective refresh, e' possibile costruire pannelli di personalizzazione professionali, coerenti con le linee guida di WordPress e pienamente integrati nell'esperienza nativa dell'editor.