Aggiungere validazione e gestione degli errori durante il salvataggio dei campi personalizzati?
Ho una funzione che definisce un campo personalizzato su un tipo di post. Supponiamo che il campo sia "subhead".
Quando il post viene salvato, voglio eseguire una validazione sull'input e visualizzare un messaggio di errore nella schermata di modifica del post se necessario. Qualcosa del tipo:
// Gestisce l'aggiornamento del post
function wpse_update_post_custom_values($post_id, $post) {
// Esegue alcuni controlli...
if($_POST['subhead'] != 'valore atteso') {
// Aggiunge un errore qui
$errors->add('oops', 'Si è verificato un errore.');
}
return $errors;
}
add_action('save_post','wpse_update_post_custom_values',1,2);
Sto cercando di agganciare questo all'azione save_post, ma non riesco a capire come gestire gli errori. Non sembra esserci un oggetto errore passato alla funzione, e se creo il mio oggetto WP_Error e lo restituisco, non viene rispettato dal meccanismo che mostra gli errori nella pagina di modifica del post.
Attualmente ho un messaggio di errore nella pagina all'interno del mio meta box personalizzato, ma questo è meno che ideale - preferirei avere un errore grande, rosso, in alto come normalmente visualizza WP.
Qualche idea?
AGGIORNAMENTO:
Basandomi sulla risposta di @Denis, ho provato diverse soluzioni. Memorizzare gli errori come variabile globale non ha funzionato, perché WordPress esegue un reindirizzamento durante il processo save_post, che elimina la variabile globale prima che si possa visualizzarla.
Ho finito per memorizzarli in un campo meta. Il problema di questo approccio è che devi eliminarli, altrimenti non scompariranno quando navighi su un'altra pagina, quindi ho dovuto aggiungere un'altra funzione collegata ad admin_footer che si occupa di eliminare gli errori.
Non mi sarei aspettato che la gestione degli errori per qualcosa di così comune (aggiornamento dei post) fosse così macchinosa. Mi sto perdendo qualcosa di ovvio o questo è il miglior approccio?
// Gestisce l'aggiornamento del post
function wpse_5102_update_post_custom_values($post_id, $post) {
// Per contenere gli errori
$errors = false;
// Esegue qualche validazione...
if($_POST['subhead'] != 'valore atteso') {
// Aggiunge un errore qui
$errors .= 'Ops... si è verificato un errore.';
}
update_option('my_admin_errors', $errors);
return;
}
add_action('save_post','wpse_5102_update_post_custom_values',1,2);
// Visualizza eventuali errori
function wpse_5102_admin_notice_handler() {
$errors = get_option('my_admin_errors');
if($errors) {
echo '<div class="error"><p>' . $errors . '</p></div>';
}
}
add_action( 'admin_notices', 'wpse_5102_admin_notice_handler' );
// Elimina eventuali errori
function wpse_5102__clear_errors() {
update_option('my_admin_errors', false);
}
add_action( 'admin_footer', 'wpse_5102_clear_errors' );

Grazie per avermi indirizzato in questa direzione! Alla fine ho usato un meta per memorizzare gli errori, perché ho avuto problemi a farlo come variabile globale o proprietà. Sto aggiornando la mia risposta proprio ora per spiegare come lo sto facendo... fammi sapere se è questo il tipo di approccio che suggerivi, o se c'è un modo migliore che non sto cogliendo.

Esattamente quel tipo di approccio, sì. Forse, ripensandoci, sarebbe meglio memorizzarlo in una variabile di sessione. Questo per permettere a più autori di modificare contemporaneamente i post. :-) Inoltre, credo non sia possibile memorizzare "false" in un'opzione. Meglio memorizzare una stringa vuota.

Consiglio di utilizzare le sessioni poiché questo eviterà effetti strani quando due utenti modificano contemporaneamente. Ecco cosa faccio:
WordPress non avvia le sessioni automaticamente. Quindi devi avviare una sessione nel tuo plugin, functions.php o anche wp-config.php:
if (!session_id())
session_start();
Quando salvi il post, aggiungi errori e notifiche alla sessione:
function my_save_post($post_id, $post) {
if($something_went_wrong) {
//Aggiungi un messaggio di errore se qualcosa è andato storto
$_SESSION['my_admin_notices'] .= '<div class="error"><p>Questo o quello è andato storto</p></div>';
return false; //potrebbe interrompere l'elaborazione qui
}
if($somthing_to_notice) { //es. salvataggio riuscito
//Aggiungi una notifica se necessario
$_SESSION['my_admin_notices'] .= '<div class="updated"><p>Post aggiornato</p></div>';
}
return true;
}
add_action('save_post','my_save_post');
Visualizza le notifiche e gli errori e poi pulisci i messaggi nella sessione:
function my_admin_notices(){
if(!empty($_SESSION['my_admin_notices'])) print $_SESSION['my_admin_notices'];
unset ($_SESSION['my_admin_notices']);
}
add_action( 'admin_notices', 'my_admin_notices' );

correzione per la versione della sessione: la prima volta che usi la variabile di sessione non usare .= ma solo = se attivi il debugging, puoi verificare perché...

Anch'io facevo così, ma se rilasci un plugin a un pubblico vasto in quel modo, finirai per essere odiato. WordPress non inizializza le sessioni perché è progettato per essere stateless e non averne bisogno, e alcune configurazioni strane dei server potrebbero romperlo. Usa invece l'API dei transienti - http://codex.wordpress.org/Transients_API al posto delle sessioni e manterrai la compatibilità. Ho pensato valesse la pena segnalare un motivo per non farlo qui.

@pospi questo sembra avere problemi simili all'uso originale delle funzioni get_option e update_option. Quindi immagino che la soluzione sarebbe aggiungere l'ID dell'utente corrente alla chiave?

Basandomi sul suggerimento di pospi di utilizzare i transients, ho sviluppato la seguente soluzione. L'unico problema è che non esiste un hook per posizionare il messaggio sotto il tag h2
dove compaiono gli altri messaggi, quindi ho dovuto ricorrere a una soluzione jQuery per posizionarlo correttamente.
Per prima cosa, salva il messaggio di errore durante il tuo handler save_post
(o simile). Assegno una breve durata di 60 secondi, così che sia disponibile giusto il tempo necessario per il reindirizzamento.
if($has_error)
{
set_transient( "acme_plugin_error_msg_$post_id", $error_msg, 60 );
}
Poi, recupera semplicemente quel messaggio di errore al caricamento successivo della pagina e visualizzalo. Lo elimino anche per evitare che venga mostrato due volte.
add_action('admin_notices', 'acme_plugin_show_messages');
function acme_plugin_show_messages()
{
global $post;
if ( false !== ( $msg = get_transient( "acme_plugin_error_msg_{$post->ID}" ) ) && $msg) {
delete_transient( "acme_plugin_error_msg_{$post->ID}" );
echo "<div id=\"acme-plugin-message\" class=\"error below-h2\"><p>$msg</p></div>";
}
}
Dato che admin_notices
viene eseguito prima della generazione del contenuto principale della pagina, l'avviso non appare dove vengono mostrati gli altri messaggi di modifica del post, quindi ho dovuto usare questo codice jQuery per spostarlo:
jQuery('h2').after(jQuery('#acme-plugin-message'));
Poiché l'ID del post fa parte del nome del transient, questa soluzione dovrebbe funzionare nella maggior parte degli ambienti multi-utente, tranne quando più utenti modificano contemporaneamente lo stesso post.

Potresti approfondire il discorso su "Dato che l'ID del post fa parte del nome del transient"? Ho creato una classe per gestire i messaggi di errore utilizzando questa tecnica, ma ho bisogno che il mio costruttore passi un user_ID. L'API dei transient utilizza l'user_id quando genera l'hash della chiave? (Lo chiedo perché il codex non sembra menzionare questo aspetto)

Quando save_post
viene eseguito, il post è già stato salvato nel database.
Analizzando il codice core di WordPress, più precisamente la funzione update_post()
in wp-includes/post.php
, non esiste un modo integrato per intercettare una richiesta prima che venga salvata nel database.
Tuttavia, possiamo agganciarci a pre_post_update
e utilizzare header()
e get_post_edit_link()
per impedire il salvataggio del post.
<?php
/**
* Esegue la validazione prima di salvare/inserire un custom post type
*/
function custom_post_site_save($post_id, $post_data) {
// Se questa è solo una revisione, non fare nulla.
if (wp_is_post_revision($post_id))
return;
if ($post_data['post_type'] == 'my_custom_post_type') {
// Nega i titoli dei post con meno di 5 caratteri
if (strlen($post_data['post_title'] < 5)) {
header('Location: '.get_edit_post_link($post_id, 'redirect'));
exit;
}
}
}
add_action( 'pre_post_update', 'custom_post_site_save', 10, 2);
Se vuoi notificare all'utente cosa è andato storto, controlla questo gist: https://gist.github.com/Luc45/09f2f9d0c0e574c0285051b288a0f935

Grazie per il suggerimento! Quello che ho omesso nella domanda (per semplificare) è che sto cercando di gestire gli errori di caricamento dei file, quindi deve essere lato server. Grazie comunque per il suggerimento!

La validazione javascript non previene da alcuni attacchi, la validazione lato server è l'unica sicura. Inoltre, WordPress offre alcuni ottimi strumenti per validare i dati degli utenti. Ma hai ragione se serve solo a verificare alcuni valori prima di inviare i dati al server, puoi risparmiare tempo su server poco potenti ^^

Provando a utilizzare lo script sopra, mi sono imbattuto in uno strano problema. Due messaggi vengono visualizzati nella schermata di modifica, dopo l'aggiornamento del post. Uno mostra lo stato del contenuto dal salvataggio precedente e l'altro da quello corrente. Ad esempio, se salvo correttamente il post e poi commetto un errore, il primo è "errore" e il secondo è "ok" - anche se vengono generati nello stesso momento. Se modifico lo script e aggiungo solo un messaggio (ad esempio "errore"), avvio un aggiornamento con "errore" e dopo un altro con "ok", il messaggio "errore" rimane (viene visualizzato una seconda volta). Devo salvare con "ok" un'altra volta per eliminarlo. Non capisco davvero cosa ci sia di sbagliato, l'ho testato su tre server locali diversi e lo stesso problema si verifica su ognuno di essi. Se qualcuno ha un'idea o un suggerimento, per favore aiutatemi!

Ho eseguito ulteriori test della versione più semplice del secondo script che ho menzionato sopra e sembra che se il messaggio di "errore" viene effettivamente aggiunto all'array della sessione, viene mostrato nella schermata di modifica. Se non c'è alcun messaggio (tutto è "ok") e il messaggio precedente era un errore, questo appare comunque sullo schermo. La cosa strana è che viene generato al momento del salvataggio (non è in cache) - l'ho verificato utilizzando date() nel corpo del messaggio di errore. Sono totalmente confuso ora.

Ho scritto un plugin che aggiunge una gestione degli errori in tempo reale per le schermate di modifica degli articoli e impedisce la pubblicazione dei post finché i campi obbligatori non vengono compilati:
https://github.com/interconnectit/required-fields
Ti permette di rendere obbligatori qualsiasi campo degli articoli, ma puoi anche utilizzare l'API fornita per rendere obbligatori campi personalizzati con un messaggio di errore personalizzabile e una funzione di validazione. Di default verifica semplicemente se il campo è vuoto o meno.
