Quasi tutto lo sviluppo di plugin WordPress segue lo stesso arco. Qualcuno ha bisogno di un modulo di prenotazione, di un importatore di feed o di un campo in più al checkout, uno sviluppatore lo scrive, funziona, tutti passano oltre. Due anni dopo il sito è bloccato su una vecchia versione di WordPress perché nessuno è sicuro che quel plugin sopravviverà a un aggiornamento, e chi lo ha scritto se n’è andato.

La causa raramente è che il core si muova troppo in fretta. WordPress è prudente quando si tratta di rompere le cose, e parecchi plugin scritti bene cinque anni fa girano ancora immutati su WordPress 7.1. I plugin si rompono per decisioni prese nella prima settimana: funzionalità messa nel tema, file del core modificati anziché agganciati, dati salvati nella struttura più a portata di mano, e nessuno che abbia mai fatto una prova contro una release candidate.

Che cosa rende un plugin WordPress su misura capace di sopravvivere agli aggiornamenti del core? Quattro cose. Il codice vive in un plugin anziché nel tema. Estende WordPress con azioni e filtri invece di modificare i file del core. Conserva ogni dato nella struttura che corrisponde alla forma di quel dato. E qualcuno lo prova contro ogni release candidate prima che quella versione esca.


Perché lo sviluppo di plugin WordPress appartiene a un plugin e non al tema

La casa predefinita del codice su misura è il functions.php del tema, perché è già lì e viene già eseguito. È anche il file che sparisce al prossimo restyling.

Un tema è presentazione. Cambiate tema e tutto ciò che faceva il vecchio si ferma. I tipi di contenuto personalizzati smettono di essere registrati, quindi i contenuti restano nel database senza schermata di amministrazione e senza permalink. Gli shortcode compaiono come testo grezzo a metà pagina. Lo snippet di analytics, il markup schema e la chiamata notturna al CRM se ne vanno, e niente segnala un errore.

La regola è abbastanza semplice da mettere in un capitolato. Tutto ciò che deve restare vero dopo un restyling appartiene a un plugin: tipi di contenuto e tassonomie personalizzati, integrazioni con qualsiasi cosa esterna, shortcode e blocchi, regole di business, lavori pianificati e qualunque cosa scriva nel database. Al tema restano template, stili e parti di template.

Il conto arriva tardi. Al restyling successivo o pagate di nuovo per ricostruire ciò che già esisteva, oppure vi portate dietro il vecchio functions.php. Su un sito con anni di frammenti accumulati sono diverse migliaia di sterline di lavoro evitabile, ed è il motivo per cui un preventivo di restyling torna al doppio di quanto il cliente si aspettava. Un tema figlio è comunque un tema.

Il modello di estensione e l’unica regola che conta

WordPress è costruito per essere modificato dall’esterno. Il meccanismo sono gli hook, e la documentazione degli hook li descrive come i punti predefiniti in cui un pezzo di codice può interagire con un altro o modificarlo. Un’azione scatta in un momento definito e vi lascia fare qualcosa: inviare una notifica dopo la pubblicazione di un articolo, o registrare un tipo di contenuto. Un filtro vi consegna un valore, si aspetta che lo cambiate o lo lasciate stare, e si aspetta che glielo restituiate.

La regola che ne discende è assoluta. Se state modificando un file dentro wp-admin, wp-includes o la cartella di un altro plugin, avete già perso. Quelle modifiche vengono cancellate dal prossimo aggiornamento, senza avvisi, senza errori e di solito senza che nessuno se ne accorga finché un cliente non segnala che qualcosa ha smesso di funzionare. Chiedetelo direttamente a uno sviluppatore prima di ingaggiarlo.

Quando l’hook che vi serve non esiste, avvolgete il comportamento anziché sostituirlo, salite a un hook più ampio, forkate il plugin di terze parti sotto controllo di versione documentando la divergenza, oppure chiedete l’hook a monte, che è il modo in cui la maggior parte di essi è nata.

Nomi, prefissi e uno spazio dei nomi molto affollato

In WordPress il PHP gira in un unico spazio dei nomi globale condiviso con il core, con il tema attivo e con ogni altro plugin attivo. Due plugin che dichiarano entrambi una funzione get_settings() non se la giocano educatamente: il secondo è un errore fatale e il sito diventa bianco.

I prefissi sono più lunghi di quanto pensiate

La pagina sulle buone pratiche per i plugin del manuale chiede un prefisso univoco su tutto ciò che è accessibile globalmente, almeno quattro caratteri e preferibilmente cinque, evitando le parole inglesi comuni e senza mai usare wp_, _ o WordPress stesso. Con decine di migliaia di plugin in circolazione, tre lettere prese dalle iniziali del cliente sono un lancio di moneta.

Spazi dei nomi e autoloading

La pratica moderna risolve metà del problema. Dichiarate uno spazio dei nomi PHP, mettete una classe per file e lasciate che un autoloader PSR-4 le trovi, così non ci sono istruzioni require scritte a mano né alcuna possibilità che il nome di una classe collida con quello di un altro plugin. Rende inoltre il codice testabile, perché le classi con dipendenze nel costruttore possono essere istanziate senza che WordPress sia caricato.

L’autoloading non risolve il caso di due plugin che distribuiscono versioni diverse della stessa libreria. Vince quello che carica per primo. Prefissate gli spazi dei nomi delle dipendenze in fase di build per qualsiasi cosa venga distribuita.

Le stringhe che gli spazi dei nomi non possono aiutare

Uno spazio dei nomi copre i simboli PHP. Buona parte di ciò che un plugin registra però non è un simbolo PHP ma una stringa in un registro condiviso, e per quelle vale ancora la vecchia convenzione dei prefissi: nomi di hook, chiavi di opzioni e transient, chiavi di meta degli articoli, nomi di tipi di contenuto e tassonomie, tag degli shortcode, nomi di eventi cron, spazi dei nomi REST e nomi di tabelle personalizzate. Vivono in uno spazio piatto dove vince l’ultima registrazione, oppure due plugin condividono lo stesso stato in silenzio.

Due limiti vanno conosciuti prima di dare un nome a qualsiasi cosa. La chiave di un tipo di contenuto non può superare 20 caratteri e quella di una tassonomia 32, entrambe minuscole alfanumeriche con trattini e trattini bassi. Un prefisso di cinque caratteri lascia 15 caratteri per il nome di un tipo di contenuto, che è meno spazio di quanto sembri.

Scegliere dove vivono i dati

È la decisione con la coda più lunga. Se la sbagliate, il plugin funziona benissimo al lancio, diventa più lento ogni mese man mano che i dati crescono, e quando qualcuno se ne accorge la correzione è una migrazione e non una modifica.

Opzioni e transient

Le opzioni servono per le impostazioni valide su tutto il sito: una manciata di chiavi, valori piccoli, letti nella maggior parte delle richieste. La trappola è l’autoloading, perché ogni opzione autocaricata viene letta a ogni singola richiesta, comprese quelle admin-ajax e REST, che qualcuno la usi oppure no.

WordPress 6.6 ha cambiato la meccanica, come spiega il post sul blog Make WordPress Core dedicato a disattivare l’autoload per le opzioni grandi. Il valore memorizzato ora è on, off o auto, e un’opzione più grande di 150.000 byte non viene autocaricata per impostazione predefinita, con la soglia regolabile tramite il filtro wp_max_autoloaded_option_size. Trattatelo come un tetto, non come un obiettivo. I transient sono opzioni con una scadenza, e sono la casa giusta per tutto ciò che arriva da altrove.

I meta degli articoli non sono un archivio chiave-valore

I meta degli articoli servono per gli attributi di un singolo articolo: un sottotitolo, un prezzo, un riferimento di fornitura. Non sono un archivio chiave-valore generico, e il motivo è visibile nella definizione della tabella. La tabella wp_postmeta ha quattro colonne e tre chiavi. Sono indicizzati solo post_id e i primi 191 caratteri di meta_key. La colonna meta_value è un longtext senza alcun indice.

Una query che filtra su un valore meta non può quindi usare un indice. Ogni clausola di una meta query aggiunge un’altra join, e su un sito con 50.000 articoli che portano 20 righe di meta ciascuno la tabella contiene un milione di righe. Tre clausole significano tre join contro un milione di righe a ogni caricamento di pagina. È una delle ragioni più comuni per cui un sito veloce al primo anno è inutilizzabile al terzo, e ricorre di continuo nel lavoro sulle prestazioni di WooCommerce.

Tipi di contenuto e tassonomie personalizzati

Un tipo di contenuto personalizzato è la scelta giusta quando la cosa è contenuto. Ha bisogno di una propria schermata di elenco, di permalink, di revisioni e di un flusso redazionale, e ha senso come pagina che qualcuno potrebbe visitare. Una tassonomia personalizzata è la scelta giusta quando serve un vocabolario condiviso che raggruppa quelle cose e merita pagine di archivio proprie.

Entrambi portano in dote una macchina gratuita: schermate di amministrazione, permessi, ricerca, editor a blocchi e API REST. Impostate show_in_rest a true, altrimenti l’editor a blocchi non gestirà il tipo, e registrate entrambi sull’hook init, mai prima.

Quando serve davvero una tabella tutta vostra

Una tabella vostra è la scelta giusta quando i dati non sono contenuto: record ad alto volume in sola aggiunta come registri di eventi, code di importazione, storico dei prezzi o tracce di audit, oppure qualsiasi cosa filtriate e ordiniate per un campo che non è una colonna dell’articolo. Oltre qualche centinaio di migliaia di righe interrogate per i propri campi, una tabella con gli indici giusti batte i meta degli articoli di ordini di grandezza e resta prevedibile mentre cresce.

Il prezzo è che tutto diventa vostro: creazione della tabella e migrazioni versionate, pulizia in uninstall.php, schermate di amministrazione, endpoint REST e caching vostri. Per questo la risposta onesta, per la maggior parte dei plugin, resta un tipo di contenuto personalizzato.

La sicurezza è fatta di quattro abitudini, e tre vengono saltate

Il manuale di sicurezza di WordPress enuncia il principio senza giri di parole: non fidatevi dell’input degli utenti, delle API di terze parti né dei dati già presenti nel vostro database. Quattro abitudini portano quasi tutto il rischio, e nei plugin che verifichiamo vengono saltate in un ordine costante. Prima i controlli di capacità, poi i nonce, terzo l’escaping in uscita. Le prepared statement vengono per ultime, perché una mancante viene intercettata in revisione.

Controlli di capacità

Ogni handler che modifica qualcosa deve chiedere se questo utente ne ha il diritto, il che significa current_user_can() con la capacità specifica, verificata dentro l’handler e non solo attorno al pulsante che lo richiama.

is_admin() non è un controllo di permessi. Riferisce su quale lato del sito si trova la richiesta e restituisce true per qualsiasi abbonato autenticato che raggiunga un endpoint admin-ajax. Un’azione admin_post_ o wp_ajax_ senza controllo di capacità è raggiungibile da ogni utente registrato, il che in un negozio significa ogni cliente che abbia mai ordinato. La nostra checklist di hardening della sicurezza WordPress copre i controlli a livello di sito attorno a questo tema.

I nonce

Un nonce protegge un modulo o un URL da una richiesta che l’utente non intendeva fare. Usate wp_nonce_field() nel modulo e check_admin_referer() nell’handler, oppure check_ajax_referer() per l’AJAX. Malgrado il nome non sono monouso: sono hash validi per una finestra, un giorno per impostazione predefinita, con uno schema a due tick che colloca la durata reale fra dodici e ventiquattro ore.

La documentazione dei nonce è esplicita nel dire che non ci si deve mai affidare a loro per autenticazione, autorizzazione o controllo degli accessi. Un nonce stabilisce che la richiesta è arrivata dal vostro modulo. Non dice nulla sul fatto che quella persona debba essere autorizzata a fare quella cosa.

Sanificare in entrata, fare escaping in uscita

Validate dove potete, perché la validazione è specifica: un codice postale o corrisponde al modello o non corrisponde. Sanificate dove non potete validare, con sanitize_text_field(), sanitize_email(), sanitize_key(), absint() o wp_kses_post() a seconda del campo.

Poi fate escaping nel punto di output, ogni volta, con esc_html(), esc_attr(), esc_url() o wp_kses_post(). La documentazione sull’escaping chiede di farlo il più tardi possibile, così chi rilegge vede escaping e output sulla stessa riga. L’escaping viene saltato più di ogni altra cosa perché, quando lo si salta, non sembra esserci nulla di sbagliato. La pagina si visualizza perfettamente finché qualcuno non mette un tag script in un campo.

Le prepared statement

Qualsiasi query scritta da voi passa per $wpdb->prepare(), che accetta %d per gli interi, %f per i numeri in virgola mobile, %s per le stringhe e %i per gli identificatori come nomi di tabelle e colonne. I segnaposto restano senza virgolette, un segno di percentuale letterale si scrive due volte, e un carattere jolly LIKE si passa dentro l’argomento di sostituzione anziché digitarlo nella query. Concatenare una variabile dentro l’SQL non è un disaccordo di stile, è la vulnerabilità.

L’API REST e l’editor a blocchi

Un plugin scritto quest’anno dovrebbe esporre i suoi dati attraverso l’API REST e le sue impostazioni attraverso l’editor, non attraverso una pagina di opzioni costruita a mano.

Le rotte si registrano con register_rest_route() sull’hook rest_api_init. Da WordPress 5.5 l’argomento permission_callback è obbligatorio, e ometterlo fa scattare un avviso _doing_it_wrong() che nomina la rotta. Un endpoint davvero pubblico usa __return_true, ed è il senso di questo disegno: rendere pubblica una rotta diventa una riga di codice deliberata anziché una dimenticanza. La documentazione sugli endpoint personalizzati copre anche lo schema degli argomenti, dove stanno le callback di sanificazione e validazione, così un input cattivo non raggiunge mai il vostro handler.

Le impostazioni si registrano con register_setting() e show_in_rest a true. Questo le mette sull’endpoint delle impostazioni del core, così l’editor a blocchi o uno script esterno possono leggerle e scriverle tramite un’interfaccia che già gestisce autenticazione, permessi e validazione. Elimina una pagina di opzioni, il suo nonce, il suo gestore del modulo e i bug che ci abitavano.

I blocchi si registrano da un file block.json, il metodo canonico raccomandato da WordPress 5.8. La documentazione sui metadati dei blocchi ne spiega il vantaggio: le risorse dichiarate lì si caricano solo sulle pagine in cui il blocco compare, anziché su tutto il sito perché un plugin è attivo.

Disciplina delle prestazioni dentro un plugin

Quattro cose spiegano quasi tutta la lentezza causata dai plugin che troviamo negli audit, e tutte e quattro costano poco da evitare e molto da correggere dopo. La prima sono le opzioni autocaricate, perché costano qualcosa a ogni richiesta e per sempre.

La seconda sono le richieste remote non memorizzate in cache durante il caricamento di una pagina. Una wp_remote_get() verso l’API di un fornitore senza cache significa che ogni visitatore aspetta quel fornitore. Quando il fornitore è lento il vostro sito è lento, e quando è fuori servizio il vostro sito resta appeso fino alla scadenza del timeout. Memorizzate la risposta in un transient, impostate un timeout esplicito e decidete in anticipo che cosa mostra la pagina quando la chiamata fallisce.

La terza sono le query dentro un ciclo. Chiamare get_post_meta() per ognuna di 200 righe sono 200 viaggi di andata e ritorno, a meno che la cache dei meta non sia stata preparata, e WP_Query la prepara per voi se glielo lasciate fare. La correzione di solito consiste nello smettere di disattivare qualcosa, il che vale per la maggior parte dei riscontri di un audit dei Core Web Vitals.

La quarta è il lavoro svolto dentro la richiesta di qualcuno. WP-Cron non è il cron di sistema: viene innescato al caricamento di una pagina, quindi un lavoro pianificato gira dentro la richiesta di un visitatore, e su un sito tranquillo il lavoro delle due di notte non parte finché alle cinque non passa qualcuno. Definite DISABLE_WP_CRON, guidate wp-cron.php da uno scheduler di sistema vero e tenete i lavori brevi e idempotenti.

Sopravvivere agli aggiornamenti del core: la parte che nessuno mette a budget

Il core raramente cancella qualcosa di netto. Le funzioni vengono deprecate, continuano a funzionare ed emettono un avviso, ed è per questo che tenere lo staging con WP_DEBUG attivo è il sistema di allarme precoce meno costoso a disposizione. Un avviso di deprecazione è un invito con data a sistemare qualcosa mentre costa ancora poco.

Il processo che evita le sorprese costa circa un’ora a trimestre. Seguite il blog di sviluppo del core così sapete quando esistono una beta e poi una release candidate. Leggete la Field Guide, pubblicata durante la fase di release candidate, che elenca le novità rivolte agli sviluppatori e le rotture di quella versione. Poi mettete la release candidate su una copia di staging ed eseguite uno smoke test scritto sulle funzioni reali del plugin.

Il supporto delle versioni conta quanto il codice. WordPress richiede PHP 7.4 come pavimento assoluto e raccomanda 8.3 o più recente, insieme a MariaDB 10.11 o MySQL 8.0. Dichiarate Requires PHP e Requires at least nell’intestazione del plugin con onestà, poi provate sulla versione più bassa che avete dichiarato anziché su quella che gira sul portatile dello sviluppatore.

Versionate il vostro plugin in modo semantico e fate sul serio. Una patch ripara qualcosa, una minore aggiunge comportamento senza rompere nulla, e una maggiore può rompere purché dica che cosa ha rotto. I clienti con gli aggiornamenti automatici contano su quella promessa.

Distribuzione, licenza e come gli aggiornamenti arrivano al sito

WordPress è rilasciato sotto GPL versione 2 o successiva, e la pagina della licenza su wordpress.org espone la posizione del progetto secondo cui plugin e temi sono opere derivate che ereditano la licenza, pur riconoscendo una zona grigia giuridica su che cosa conti come derivato.

Il sorgente lo ottenete sempre, e potete incaricare chiunque altro di modificarlo. Quello che la GPL non fa è obbligarvi a pubblicarlo, quindi un plugin costruito per un’azienda può restare privato. Non impedisce nemmeno allo sviluppatore di vendere lo stesso lavoro a qualcun altro. Se l’esclusiva conta, è una clausola di contratto e non di licenza.

Se il plugin finisce nella directory pubblica deve soddisfare le linee guida della directory dei plugin, diciotto in tutto. La prima richiede una licenza compatibile con la GPL per tutto ciò che è nel pacchetto, immagini comprese. Altre escludono il trialware, cioè funzionalità chiuse dietro un pagamento o un upgrade, vietano il codice offuscato, proibiscono il tracciamento degli utenti senza consenso e vietano link o crediti aggiunti al sito pubblico senza autorizzazione.

Se resta privato, gli aggiornamenti diventano un vostro problema. Impostate l’intestazione Update URI, che esiste per impedire che un plugin privato venga sovrascritto da uno con nome simile preso dalla directory, e servite gli aggiornamenti dal vostro endpoint. Lasciare questo alla fine è il modo in cui un cliente finisce ad aggiornare via FTP.

Quanto costa un plugin WordPress su misura

Le fasce qui sotto sono prezzi di agenzia britannica in sterline, per un lavoro consegnato allo standard descritto qui: con test, documentazione e una persona designata come responsabile dopo il lancio. Uno sviluppatore WordPress e PHP capace fattura all’incirca da 400 a 600 sterline al giorno, quindi queste sono affermazioni sul perimetro e non sulla tariffa.

Un piccolo plugin di utilità va da 1.500 a 3.000 sterline. Un solo compito, qualche hook, magari un interruttore nelle impostazioni: gestione dei reindirizzamenti, un campo in più su un ordine, un export notturno verso un fornitore.

Un’integrazione di media grandezza va da 3.000 a 15.000 sterline. Un’API di terze parti con autenticazione, ritentativi e gestione degli errori, un tipo di contenuto personalizzato, schermate di amministrazione ed elaborazione in background. È la dimensione commissionata più spesso e sottostimata più spesso, perché l’integrazione è una settimana e la gestione dei fallimenti sono due settimane.

Un plugin di prodotto consistente va da 20.000 a 75.000 sterline e oltre. Tabelle proprie, interfacce nell’editor a blocchi, infrastruttura di licenza e aggiornamento, supporto multisito e un carico di assistenza che comincia il giorno in cui esce.

Pagare nella fascia bassa non è automaticamente sbagliato. Lo è quando il prezzo nasce da un perimetro che ha escluso in silenzio i deliverable qui sotto. La nostra guida alle tariffe degli sviluppatori WordPress e alle domande da fare spiega come si legge un preventivo, e la nostra pagina sullo sviluppo WordPress espone come definiamo il perimetro di questo lavoro.

Che cosa deve esserci nella consegna

Chiedete tutto questo per iscritto prima che il lavoro cominci, perché ogni voce costa poco da includere e molto da aggiungere dopo. Il sorgente, in un repository vostro, con la sua storia intatta, anziché uno zip mandato per email l’ultimo giorno. Test unitari per le regole di business e un test di integrazione per tutto ciò che scrive nel database o chiama un servizio esterno, che è ciò che rende sicuro cambiarlo fra due anni per qualcuno che non c’era.

Un readme che dice che cosa fa il plugin, a che cosa si aggancia, che cosa conserva e dove, quali servizi esterni chiama e che cosa succede quando ognuno di questi cade. Due pagine bastano, e la sua assenza è il motivo per cui i plugin vengono sostituiti anziché mantenuti. Un uninstall.php che rimuove opzioni, tabelle, eventi cron e meta. E un accordo di assistenza con un nome sopra, che copra le prove contro ogni versione del core e la correzione di ciò che quelle prove trovano.

Farlo costruire

Mecanik costruisce plugin in tutte e tre le dimensioni descritte sopra e prende in carico plugin scritti da altri, spesso l’incarico più utile. Le nostre pagine su sviluppatore WordPress e sviluppo software spiegano come definiamo il perimetro e consegniamo. Se avete già un plugin che nessuno vuole toccare, un audit rispetto alle pratiche qui sopra richiede circa un giorno e vi dice se è riparabile o sostituibile.



Domande frequenti

Le funzionalità su misura vanno in un plugin o nel tema? In un plugin, a meno che non siano puramente di presentazione. Un tema viene sostituito al restyling successivo e tutto ciò che faceva si ferma: i tipi di contenuto personalizzati perdono le schermate di amministrazione, gli shortcode compaiono come testo grezzo e le integrazioni smettono di girare in silenzio. Tutto ciò che deve restare vero dopo un restyling appartiene a un plugin.

Quanto costa un plugin WordPress su misura nel Regno Unito? Un piccolo plugin di utilità costa di solito da 1.500 a 3.000 sterline, un’integrazione di media grandezza con un’API di terze parti e schermate di amministrazione costa da 3.000 a 15.000 sterline, e un plugin di prodotto consistente con tabelle proprie e infrastruttura di aggiornamento costa da 20.000 a 75.000 sterline o più. Gli sviluppatori capaci di questo lavoro fatturano all’incirca da 400 a 600 sterline al giorno.

È mai accettabile modificare il core di WordPress o i file di un altro plugin? No. Quelle modifiche vengono cancellate dal prossimo aggiornamento, senza errori e di solito senza che nessuno se ne accorga finché qualcosa non smette di funzionare. Usate invece azioni e filtri. Se l’hook che vi serve non esiste, avvolgete il comportamento, forkate il plugin sotto controllo di versione, oppure chiedete l’hook a monte.

Un plugin che ho pagato a qualcuno è di mia proprietà? Vostra è la copia, insieme a quanto dice il contratto. La GPL vi dà il sorgente, il diritto di modificarlo e quello di incaricare chiunque altro di mantenerlo, e non vi obbliga a pubblicarlo, quindi un plugin costruito per un’azienda può restare privato. Non impedisce allo sviluppatore di rivendere lo stesso lavoro, perciò mettete l’esclusiva nel contratto se conta.

Come si evita che un plugin si rompa quando WordPress si aggiorna? Provatelo contro ogni release candidate su una copia di staging prima che quella versione esca, tenete lo staging con WP_DEBUG attivo così gli avvisi di deprecazione emergono presto, e dichiarate nell’intestazione le versioni di PHP e WordPress supportate dal plugin. WordPress richiede PHP 7.4 come pavimento e raccomanda 8.3 o più recente.