Mu-plugin WordPress
Mu-plugin vs plugin normale
Sezione intitolata “Mu-plugin vs plugin normale”| Plugin normale | Mu-plugin | |
|---|---|---|
| Attivazione | manuale | automatica |
| Posizione | wp-content/plugins/ |
wp-content/mu-plugins/ |
| Disattivabile da admin | sì | no |
| Hook di attivazione | sì | no |
| Ordine di caricamento | dopo | prima dei plugin normali |
| Aggiornamento | marketplace/zip | deploy via FTP |
Per un contratto dati che il frontend presuppone, il mu-plugin è la scelta giusta: se il plugin che registra i CPT non è attivo, la build fallisce. Con un plugin normale sarebbe un errore umano silenzioso.
Come è fatto un mu-plugin
Sezione intitolata “Come è fatto un mu-plugin”<?php/** * Plugin Name: Headless CMS Setup * Description: CPT, meta e impostazioni per il frontend Astro. * Version: 1.0.0 */
if (!defined('ABSPATH')) { exit;}
final class Headless_CMS { public static function init(): void { add_action('init', [self::class, 'register_post_types']); add_action('init', [self::class, 'register_meta']); add_action('save_post', [self::class, 'save_meta'], 10, 2); }
// ...}
Headless_CMS::init();Regole:
- guard
ABSPATHsempre; - niente output, niente HTML nell’admin se non nei metabox;
- classi con metodi statici, oppure funzioni prefissate (
headless_*); - nessuna credenziale nel codice;
- versione nel docblock e nel repository, così sai cosa è deployato.
Cosa registra headless-cms.php
Sezione intitolata “Cosa registra headless-cms.php”- CPT con
register_post_type:show_in_rest => true,rest_baseesplicito,supportsconcustom-fields. Senzacustom-fieldsi meta non vengono salvati né esposti. - Meta con
register_post_meta:single,type, schema REST (string,array,integer,boolean),auth_callback => '__return_true'per la lettura pubblica. - Metabox: generati da una configurazione dichiarativa (
CPT+PAGE_META), quindi aggiungere un campo = aggiungere una riga, non scrivere HTML a mano. - Salvataggio: nonce, autosave, capability
edit_post, sanitizzazione. I campi JSON vengono decodificati in array; se il JSON non è valido il salvataggio viene rifiutato con un avviso in admin. - Impostazioni sito: opzioni
headless_*, pagina in Impostazioni, rotta pubblica/headless/v1/settingsin sola lettura.
Perché i campi JSON
Sezione intitolata “Perché i campi JSON”Alcuni contenuti sono liste strutturate: agenda, paragrafi, benefici, opzioni di prezzo. Un campo testo con JSON è il compromesso più semplice senza ACF:
['key' => 'agenda', 'label' => 'Agenda (JSON)', 'type' => 'json', 'rest' => [ 'type' => 'array', 'items' => ['type' => 'object', 'properties' => [ 'time' => ['type' => 'string'], 'activity' => ['type' => 'string'], ]],]],La REST restituisce un array, non una stringa: lo schema Zod lo vede già come
{ time, activity }[].
Aggiungere un campo a un CPT esistente
Sezione intitolata “Aggiungere un campo a un CPT esistente”- Aggiungi la riga in
CPT[...]['fields']. - Rigenera i permalink non serve, ma ricarica la pagina di modifica.
- Aggiorna lo schema Zod (con o senza
.default()), il mapper e il dominio. - Aggiorna la fixture e il test.
- Se il campo è obbligatorio, non mettere default: la build deve fallire quando manca.
Aggiungere un’opzione sito
Sezione intitolata “Aggiungere un’opzione sito”- In
SITE_SETTINGSaggiungi'chiave' => ['label' => '...', 'type' => 'string']. - In
src/cms/wordpress/wordpress-settings.schema.tsaggiungi il campo snake_case. - Nel mapper aggiungi il camelCase.
- In
SiteSettings.tse ingetSiteSettings()gestisci il fallback.
Il mu-plugin contatti
Sezione intitolata “Il mu-plugin contatti”- Registra il CPT
contattoprivato (non in REST): è una inbox, non un contenuto. - Espone
POST /headless/v1/contact. - Valida e sanitizza con le funzioni WP (
sanitize_text_field,sanitize_email,sanitize_textarea_field). - Honeypot: se il campo
aziendaè compilato, risponde201senza salvare (i bot non capiscono di essere stati scoperti). - Rate limit: transient di 60 secondi per IP, risposta
429. - Invia una mail a
headless_email(fallback: email admin). - CORS aperto solo per la rotta contatti, utile in sviluppo locale.
Il mu-plugin webhook
Sezione intitolata “Il mu-plugin webhook”- Ascolta
wp_after_insert_posteupdated_option. - Filtra i post type rilevanti e le opzioni
headless_*. - Debounce di 60 secondi con transient: una raffica di salvataggi produce una dispatch.
- Chiama GitHub
repository_dispatchconblocking: falsesushutdown, così l’admin non aspetta la rete. - Senza costanti
HEADLESS_WEBHOOK_*non fa nulla: in locale non serve configurarlo.
Vedi Build e concorrenza per il comportamento della coda.
Sicurezza
Sezione intitolata “Sicurezza”permission_callback => '__return_true'solo su letture pubbliche e sul form.- Ogni scrittura passa da nonce + capability.
- Sanitizza in ingresso, escapa in uscita (
esc_html,esc_attr,esc_textarea). - Nessun
eval, nessuna query SQL custom. - Token e password in
wp-config.php, mai nel repository né nel database. - XML-RPC disattivato.
- Tieni WordPress e i plugin aggiornati: il backend è la parte esposta.
I mu-plugin si caricano via FTP in wp-content/mu-plugins/ e restano attivi.
Dopo un aggiornamento di headless-cms.php: salva i permalink una volta, poi verifica
/wp-json/wp/v2/{cpt}.
Versiona i file nel repository (come qui) e annota nel commit cosa è stato deployato.
Quando NON usare un mu-plugin
Sezione intitolata “Quando NON usare un mu-plugin”Se la funzionalità non è essenziale per il contratto dati (es. un redirect, un widget social, un’integrazione marketing), un plugin normale è più gestibile: si attiva, si disattiva, si aggiorna. Il mu-plugin è per ciò che il frontend pretende che esista.