Realizzare un plug-in per WordPress che utilizza le API di servizio, "Dalla zuppa alle noci"
Pubblicato: 2022-03-10Un numero sempre maggiore di API pubblicamente disponibili fornisce potenti servizi per espandere la funzionalità delle nostre applicazioni. WordPress è un CMS incredibilmente dinamico e flessibile che alimenta qualsiasi cosa, dai piccoli blog personali ai principali siti di e-commerce e tutto il resto. Parte di ciò che rende WordPress così versatile è il suo potente sistema di plugin , che rende incredibilmente facile aggiungere funzionalità.
Illustreremo come ho realizzato GitHub Pipeline, un plug-in che ti consente di visualizzare i dati dell'API GitHub sulle pagine di WordPress utilizzando gli shortcode. Fornirò esempi specifici e frammenti di codice, ma considera la tecnica qui descritta un progetto su come utilizzare qualsiasi API di servizio con un plug-in.
Ulteriori letture su SmashingMag:
- Essenziali di WordPress: come creare un plugin per WordPress
- Come distribuire i plugin di WordPress con GitHub usando i transitori
- Tre approcci per aggiungere campi configurabili al tuo plugin
Inizieremo dall'inizio, ma si presuppone un certo grado di familiarità con WordPress e lo sviluppo di plugin e non perderemo tempo su argomenti per principianti, come l'installazione di WordPress o Composer.
Avrai bisogno:
- un ambiente PHP, con una nuova installazione di WordPress;
- un account GitHub (o un altro provider API se vuoi improvvisare);
- Compositore (consigliato).
Scegliere un'API
Il primo passo per scrivere questo tipo di plugin è scegliere un'API. In questo tutorial utilizzeremo l'API GitHub. Se stai pensando di utilizzare un'altra API, considera alcuni fattori importanti che possono influenzare la gratificazione del tuo progetto.
Una delle prime cose che guardo è la completezza e la qualità della documentazione dell'API. Se è scarso o obsoleto, preparati a passare del tempo a setacciare il codice sorgente per rispondere alle tue domande. Inoltre, considera quanto è matura l'API e quanto responsabilmente il provider l'ha modificata. Niente è peggio che investire tempo nella creazione di qualcosa di eccezionale, solo che si rompe a causa delle modifiche all'API a monte. Cerca un sistema di controllo delle versioni negli URL degli endpoint.
// good https://api.stable.com/v1/user/ // danger https://api.dodgy.com/user/
A rischio di affermare l'ovvio, la programmazione su un'API di terze parti implica una relazione di fiducia e non tutte le API sono create uguali.
Shopping per una Biblioteca
I marchi affermati spesso distribuiscono librerie o SDK per semplificare il lavoro con la loro API. In caso contrario, ricordati di verificare se qualcun altro ha già scritto una libreria prima di andare e reinventare la ruota. Google e GitHub sono due ottimi posti per iniziare la tua ricerca. Il numero di stelle o fork su GitHub è una buona indicazione dell'efficacia di una libreria. L'età dei commit più recenti e/o il numero di questioni aperte sono un'indicazione di quanto attivamente vengono mantenuti. Nel mio caso, sono stato fortunato a trovare l'adorabile API PHP GitHub, di KNP Labs.
Scrivi il tuo con Guzzle
Se non esiste una libreria soddisfacente per il tuo provider, non dovresti comunque iniziare da zero perché strumenti come Guzzle rendono molto più semplice il lavoro con le richieste HTTP. Guzzle esegue il wrapping della libreria cURL di PHP e rimuove molti dei mal di testa tipicamente associati alla configurazione e all'esecuzione manuale delle richieste. Anche se stai facendo solo una o due richieste, ti consiglio comunque di usarlo perché renderà il tuo codice più robusto e installarlo con Composer è un gioco da ragazzi.
Configurazione del plug-in
Inizieremo con lo scheletro di base di un plugin WordPress minimo, una directory con due file. La scelta di un nome di cartella descrittivo e univoco è importante per evitare conflitti con altri plugin. Se il nome del tuo plugin è in qualche modo generico, considera l'aggiunta di un prefisso univoco.
github-api/ readme.txt github-api.php
Il readme.txt
contiene i metadati per il tuo plugin che appariranno su wordpress.org
se decidi di pubblicarlo lì. Leggi informazioni sulla pubblicazione dei plugin di WordPress nella documentazione o controlla l'esempio completo readme.txt.
Il file PHP contiene anche alcuni metadati nell'intestazione che verranno utilizzati per visualizzare le informazioni sul tuo plug-in nella dashboard. Inizia con un'intestazione simile a questa:
<?php /** Plugin Name: GitHub API description: >- Add GitHub project information using shortcode Version: 1.0 Author: Your Name License: GPLv2 or later Text Domain: github-api */
Per motivi di sicurezza, è anche una buona idea negare l'accesso diretto al file, in questo modo:
defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
A questo punto, il plugin non farà nulla, ma quando copi i file in wp-content/plugins
, dovrebbe apparire nell'elenco dei plugin e dovresti essere in grado di attivarlo.

Successivamente, vorremo includere la libreria che gestirà le richieste API. Nell'esempio di codice seguente, includiamo l'API GitHub PHP di KNP Labs, ma puoi includere qualsiasi dipendenza sostituendo knplabs/github-api
con il pacchetto che stai utilizzando.
$ cd wp-content/plugins/github-api $ composer require knplabs/github-api
Ora, la struttura del tuo file dovrebbe apparire come segue:
github-api/ composer.json composer.lock github-api.php readme.txt vendor/
Ora che i file sono a posto, dobbiamo richiedere il vendor/autoload.php
che è stato creato al momento dell'installazione.
require_once 'vendor/autoload.php';
Se tutto è stato impostato correttamente, dovresti essere in grado di creare un'istanza di una classe dalla libreria senza che venga generato un errore irreversibile.
$testing = new \Github\Client();
Ma questo è solo un modo rapido per verificare che le tue dipendenze siano disponibili. Consiglio di definire una nuova classe che estenda la libreria.
class MyGithub extends \Github\Client {};
Questo non è fondamentale, ma rende il tuo codice più flessibile in un paio di modi. Il modo più ovvio è che puoi modificare o aggiungere nuove funzionalità se necessario. Ma rende anche la vita più facile se in futuro vorrai cambiare libreria, perché dovrai apportare le modifiche in una sola posizione, anziché in tutto il codice.
Shortcode di WordPress
Ora è il momento di impostare il nostro primo shortcode, che è uno snippet speciale che ti consente di generare contenuto inserendolo in un post o in una pagina. Se non conosci gli shortcode o desideri una comprensione più approfondita di come funzionano, consulta la documentazione ufficiale degli shortcode. Nel mio esempio, visualizzerò un elenco di problemi di GitHub ovunque l'autore inserisca questo shortcode: [github_issues]
Come abbiamo fatto prima, iniziamo con un esempio minimo per assicurarci che lo shortcode sia registrato correttamente prima di iniziare a fare la chiamata API.
function github_issues_func( $atts ) { return "Hello world!"; } add_shortcode( "github_issues", "github_issues_func" );
Ora, se pubblichi una pagina contenente [github_issues]
, dovresti vedere "Hello world" quando visiti la pagina. Ora che funziona, impostiamo la chiamata API.
Nella sezione precedente, abbiamo impostato il caricamento automatico per la nostra libreria GitHub e definito la nostra classe per estenderlo. Ora possiamo iniziare a usarlo per effettuare chiamate API. Quindi, lo aggiungeremo alla funzione di callback dello shortcode. Come prova di concetto, recuperiamo tutti i problemi nel repository GitHub Pipeline. La documentazione per questo metodo è disponibile.
function github_issues_func( $atts ) { // Instantiate our class $gh = new MyGithub(); // Make the API call to get issues, passing in the GitHub owner and repository $issues = $gh->api('issue')->all('TransitScreen', 'wp-github-pipeline'); // Handle the case when there are no issues if ( empty($issues) ) return "<strong>" . __("No issues to show", 'githup-api') . "</strong>"; // We're going to return a string. First, we open a list. $return = "<ul>"; // Loop over the returned issues foreach( $issues as $issue ) { // Add a list item for each issue to the string // (Feel free to get fancier here) // Maybe make each one a link to the issue issuing $issue['url] ) $return .= "<li>{$issue['title']}</li>"; } // Don't forget to close the list $return .= "</ul>"; return $return; } add_shortcode( 'github_issues', 'github_issues_func' );
Ora dovresti essere in grado di visualizzare la stessa pagina che abbiamo usato sopra per testare lo shortcode e questa volta dovresti vedere un elenco di problemi non ordinato. Ma aspetta! Prima di andare avanti, facciamo un'ottimizzazione in modo che il nostro codice sia più facile da testare. (Stai testando, giusto?!) Invece di usare new
nella nostra funzione per istanziare la classe della libreria GitHub, passiamola come parametro e istanziarla solo se necessario.
function github_issues_func( $atts, $gh=null ) { // Conditionally instantiate our class $gh = ( $gh ) ? $gh : new MyGithub(); …
Questa modifica assomiglia al modello di iniezione delle dipendenze, il che rende la nostra funzione molto più facile da testare. Il test unitario di WordPress va oltre lo scopo di questo tutorial, ma è facile vedere come questa nuova versione ci renda facile passare dati API "falsi" alla funzione in modo che le asserzioni del nostro test sappiano esattamente cosa aspettarsi.
Ciò che abbiamo realizzato finora va bene per un progetto interno che verrà utilizzato solo su un singolo repository, ma sarebbe molto più utile se gli utenti potessero configurare da quale repository recuperare i problemi. Mettiamolo a punto.
Innanzitutto, utilizzeremo un hook di WordPress per registrare la nostra nuova pagina delle impostazioni e la funzione di richiamata definirà le etichette e i titoli del menu. Useremo lo stesso approccio incrementale per farlo funzionare.
// Register the menu. add_action( "admin_menu", "gh_plugin_menu_func" ); function gh_plugin_menu_func() { add_submenu_page( "options-general.php", // Which menu parent "GitHub", // Page title "GitHub", // Menu title "manage_options", // Minimum capability (manage_options is an easy way to target administrators) "github", // Menu slug "gh_plugin_options" // Callback that prints the markup ); } // Print the markup for the page function gh_plugin_options() { if ( !current_user_can( "manage_options" ) ) { wp_die( __( "You do not have sufficient permissions to access this page." ) ); } echo "Hello world!"; }
Ora dovresti essere in grado di accedere alla dashboard e vedere il nuovo menu GitHub in "Impostazioni" e quindi fare clic per visualizzare una pagina delle impostazioni vuota con "Hello world!"


Successivamente, sostituiremo Hello word
con il markup effettivo del modulo. Ti farò un esempio semplice che puoi modellare il meno o quanto vuoi.
?> <form method="post" action="<?php echo admin_url( 'admin-post.php'); ?>"> <input type="hidden" name="action" value="update_github_settings" /> <h3><?php _e("GitHub Repository Info", "github-api"); ?></h3> <p> <label><?php _e("GitHub Organization:", "github-api"); ?></label> <input class="" type="text" name="gh_org" value="<?php echo get_option('gh_org'); ?>" /> </p> <p> <label><?php _e("GitHub repository (slug):", "github-api"); ?></label> <input class="" type="text" name="gh_repo" value="<?php echo get_option('gh_repo'); ?>" /> </p> <input class="button button-primary" type="submit" value="<?php _e("Save", "github-api"); ?>" /> </form> <?php
Le classi CSS che ho incluso corrispondono a quelle utilizzate dal core di WordPress, il che è un bel modo per far apparire decente la tua pagina delle opzioni personalizzate senza alcuno sforzo aggiuntivo.

Ci sono due cose importanti da capire su questo modulo. Innanzitutto, l'endpoint a cui invia deve essere /wp-admin/admin-post.php
. Puoi codificare questo percorso, ma se il sito Web è installato in una sottodirectory non funzionerà. Quindi, utilizziamo il built-in admin_url()
per crearlo dinamicamente.
In secondo luogo, nota l'input nascosto con il nome action
. Questo campo è il modo in cui WordPress sa cosa fare con la richiesta di post che viene inviata dal modulo. Il value
corrisponde al nome dell'action hook che utilizzeremo per impostare la funzione di callback. Il nome dell'azione a cui ci collegheremo sarà questo valore, preceduto da admin post
. Quindi, nel nostro caso, dobbiamo aggiungere questo:
add_action( 'admin_post_update_github_settings', 'github_handle_save' );
Trovo che questo sia uno degli aspetti più bizzarri e meno intuitivi della creazione di menu di amministrazione personalizzati, ma una volta che ti ci abitui, è abbastanza indolore. Come avrai intuito, il secondo parametro di add_action()
è il nome della nostra funzione di callback che salverà effettivamente i valori nel database.
function github_handle_save() { // Get the options that were sent $org = (!empty($_POST["gh_org"])) ? $_POST["gh_org"] : NULL; $repo = (!empty($_POST["gh_repo"])) ? $_POST["gh_repo"] : NULL; // Validation would go here // Update the values update_option( "gh_repo", $repo, TRUE ); update_option("gh_org", $org, TRUE); // Redirect back to settings page // The ?page=github corresponds to the "slug" // set in the fourth parameter of add_submenu_page() above. $redirect_url = get_bloginfo("url") . "/wp-admin/options-general.php?page=github&status=success"; header("Location: ".$redirect_url); exit; }
L'esempio è anche abbastanza minimale. In un ambiente di produzione, probabilmente vorresti aggiungere qualche convalida. Inoltre, in questo esempio, stiamo automaticamente restituendo status=success
all'URL. Il markup del modulo in realtà non lo utilizza ancora. Per mostrare condizionatamente un messaggio di successo, aggiungi qualcosa di simile al seguente sopra il modulo in gh_plugin_options()
:
if ( isset($_GET['status']) && $_GET['status']=='success') { ?> <div class="updated notice is-dismissible"> <p><?php _e("Settings updated!", "github-api"); ?></p> <button type="button" class="notice-dismiss"> <span class="screen-reader-text"><?php _e("Dismiss this notice.", "github-api"); ?></span> </button> </div> <?php }
Se aggiungi la convalida, utilizza questo stesso modello per restituire stati e messaggi diversi in modo che l'utente sappia se e perché l'invio del modulo non è riuscito.
Ora dovresti essere in grado di salvare il nuovo proprietario e i valori del repository. Testalo inserendo il proprietario e il repository di qualsiasi progetto pubblico su GitHub. Il passaggio finale è tornare alla funzione di callback dello shortcode github_issues_func()
e sostituire i valori del proprietario e del repository hardcoded.
… $issues = $gh->api("issue")->all(get_option("gh_org"), get_option("gh_repo")); …
Una volta che questo è a posto, rivisita la pagina in cui hai aggiunto lo shortcode. Ora dovresti vedere problemi da qualsiasi progetto hai impostato.
Bonus Round: Autenticazione OAuth 2.0
L'approccio che abbiamo usato sopra funziona alla grande per i repository pubblici, ma cosa succede se vogliamo utilizzare questo plugin con un repository privato che richiede l'autenticazione? L'API GitHub esegue l'autenticazione utilizzando il protocollo OAuth 2.0, che è ciò che incontrerai lavorando con le API più popolari al giorno d'oggi. Il flusso di lavoro di base di OAuth 2.0 è il seguente:
- Si registra un'applicazione (a volte chiamata "client") presso il provider e si riceve un ID univoco e una chiave segreta.
- La tua applicazione effettua una richiesta all'endpoint di autenticazione del provider, passando le credenziali sopra e un URL di reindirizzamento che il provider utilizza per reindirizzare la richiesta a un endpoint della tua applicazione.
- All'utente viene quindi richiesto di accettare la richiesta di accesso. In tal caso, il provider utilizza l'URL che hai inviato con la richiesta per reindirizzare l'utente alla tua applicazione, insieme a un codice temporaneo.
- L'applicazione acquisisce questo codice e quindi effettua una seconda richiesta, restituendo questo codice al provider. Il provider risponde con un token di accesso, che l'applicazione utilizza quindi per autenticarsi con il provider.
Inizieremo registrando un'applicazione con GitHub. Come con le API più popolari, questa sezione si trova nell'area sviluppatori del sito web.

La cosa più importante qui è l'URL di richiamata dell'autorizzazione. L'URL di reindirizzamento passato nel passaggio tre deve corrispondere a quanto immesso qui o includerlo. Quindi, durante lo sviluppo, di solito entro nella home page del sito web. In questo modo, la mia app può reindirizzare a qualsiasi percorso.
Successivamente, aggiungeremo un secondo modulo alla nostra pagina delle impostazioni, in gh_plugin_options()
, per inviare l'ID client e il segreto dell'applicazione.
<form method="post" action="<?php echo admin_url( 'admin-post.php'); ?>"> <input type="hidden" name="action" value="oauth_submit" /> <h3>Oauth 2.0</h3> <p> <label><?php _e("GitHub Application Client ID:", "github-api"); ?></label> <input class="" type="text" name="client_id" value="<?php echo get_option('client_id')?>" /> </p> <p> <label><?php _e("GitHub Application Client Secret:", "github-api"); ?></label> <input class="" type="password" name="client_secret" value="<?php echo get_option('client_secret')?>" /> </p> <input class="button button-primary" type="submit" value="<?php _e("Authorize", "github-api"); ?>" /> </form>
Per semplificarti la vita, userò il provider GitHub per il client OAuth 2.0 di The League of Extraordinary Packages. Quindi, per prima cosa, usiamo di nuovo Composer per aggiungere la dipendenza:
composer require league/oauth2-github
Nota che la libreria GitHub di KNP Labs ha anche il supporto OAuth 2.0 integrato. Quindi, nel mio esempio specifico, questo è alquanto ridondante. Ma volevo introdurre questa libreria perché appartiene a una suite di librerie client OAuth 2.0 specifiche del provider che estendono tutte lo stesso framework gestito dalla potente League of Extraordinary Packages. È possibile visualizzare un elenco completo dei provider supportati o leggere le istruzioni su come estendere il framework per supportare un nuovo provider.
Creiamo una funzione per richiedere e salvare il token quando l'utente invia il modulo. Poiché GitHub è uno dei provider già supportati, posso copiare l'esempio nella sua documentazione con solo un paio di modifiche.
function handle_oauth() { // If the form was just submitted, save the values // (Step 1 above) if ( isset($_POST["client_id"]) && isset($_POST["client_secret"]) ) { update_option( "client_id", $_POST["client_id"], TRUE ); update_option("client_secret", $_POST["client_secret"], TRUE); } // Get the saved application info $client_id = get_option("client_id"); $client_secret = get_option("client_secret"); if ($client_id && $client_secret) { $provider = new League\OAuth2\Client\Provider\Github([ "clientId" => $client_id, "clientSecret" => $client_secret, "redirectUri" => admin_url("options-general.php?page=github"), ]); } // If this is a form submission, start the workflow // (Step 2) if (!isset($_GET["code"]) && $_SERVER["REQUEST_METHOD"] === "POST") { // If we don't have an authorization code, then get one $authUrl = $provider->getAuthorizationUrl(); $_SESSION["oauth2state"] = $provider->getState(); header("Location: ".$authUrl); exit; // Check given state against previously stored one to mitigate CSRF attack // (Step 3 just happened and the user was redirected back) } elseif (empty($_GET["state"]) || ($_GET["state"] !== $_SESSION["oauth2state"])) { unset($_SESSION["oauth2state"]); exit("Invalid state"); } else { // Try to get an access token (using the authorization code grant) // (Step 4) $token = $provider->getAccessToken("authorization_code", [ "code" => $_GET["code"] ]); // Save the token for future use update_option( "github_token", $token->getToken(), TRUE ); } }
E, proprio come abbiamo fatto con l'altro form, dobbiamo aggiungere l'action hook in modo che la funzione venga chiamata quando il form viene salvato.
add_action( "admin_post_oauth_submit", "handle_oauth" );
Il salvataggio del modulo dovrebbe ora indirizzarti alla pagina di autorizzazione del provider dell'API. Dopo l'autorizzazione, la tua applicazione può utilizzare il token salvato per richiedere dati da repository privati a cui hai accesso. La libreria che sto usando da KNP Labs ha un metodo pratico per questo.
$gh = new MyGithub(); $gh->authenticate( get_option("github_token"), NULL, Github\Client::AUTH_HTTP_TOKEN);
Le biblioteche differiranno nel modo preciso in cui gestiscono l'autenticazione, ma in un modo o nell'altro passerai il token, che farà quindi richieste autenticate per conto di un utente.
Conclusione
Abbiamo coperto molto terreno e ho cercato di ridurre al minimo gli esempi in modo che il flusso di lavoro generale rimanesse chiaro. È disponibile il codice sorgente completo di questo tutorial. Spero che ora tu abbia una chiara comprensione dei pezzi in movimento coinvolti nella creazione di un plug-in WordPress che utilizza API di servizi di terze parti e spero che tu sia ispirato a scrivere il tuo plug-in API per WordPress.
Note finali
- Codice WordPress (documentazione)
- API GitHub (documentazione)
- OAuth 2.0 (documentazione)
- Compositore (documentazione)
- La Lega dei Pacchetti Straordinari
- Documentazione Guzzle