Impedire la pubblicazione di un post se i campi personalizzati non sono compilati

12 feb 2012, 01:31:44
Visualizzazioni: 26K
Voti: 21

Ho un tipo di post personalizzato Event che contiene campi personalizzati per data/ora di inizio e fine (come metabox nella schermata di modifica del post).

Vorrei assicurarmi che un Evento non possa essere pubblicato (o programmato) senza che le date siano compilate, poiché causerebbe problemi con i template che visualizzano i dati dell'Evento (oltre al fatto che è un requisito necessario!). Tuttavia, vorrei poter avere Eventi in Bozza che non contengono una data valida mentre sono in preparazione.

Stavo pensando di utilizzare l'hook save_post per fare il controllo, ma come posso impedire che avvenga il cambio di stato?

MODIFICA1: Questo è l'hook che sto usando ora per salvare il post_meta.

// Salva i Dati del Metabox
function ep_eventposts_save_meta( $post_id, $post ) {

if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
    return;

if ( !isset( $_POST['ep_eventposts_nonce'] ) )
    return;

if ( !wp_verify_nonce( $_POST['ep_eventposts_nonce'], plugin_basename( __FILE__ ) ) )
    return;

// L'utente ha i permessi per modificare il post o la pagina?
if ( !current_user_can( 'edit_post', $post->ID ) )
    return;

// OK, siamo autenticati: dobbiamo trovare e salvare i dati
// Li metteremo in un array per renderlo più facile da ciclare

//debug
//print_r($_POST);

$metabox_ids = array( '_start', '_end' );

foreach ($metabox_ids as $key ) {
    $events_meta[$key . '_date'] = $_POST[$key . '_date'];
    $events_meta[$key . '_time'] = $_POST[$key . '_time'];
    $events_meta[$key . '_timestamp'] = $events_meta[$key . '_date'] . ' ' . $events_meta[$key . '_time'];
}

$events_meta['_location'] = $_POST['_location'];

if (array_key_exists('_end_timestamp', $_POST))
    $events_meta['_all_day'] = $_POST['_all_day'];

// Aggiungi i valori di $events_meta come campi personalizzati

foreach ( $events_meta as $key => $value ) { // Cicla attraverso l'array $events_meta!
    if ( $post->post_type == 'revision' ) return; // Non salvare i dati personalizzati due volte
    $value = implode( ',', (array)$value ); // Se $value è un array, trasformalo in CSV (improbabile)
    if ( get_post_meta( $post->ID, $key, FALSE ) ) { // Se il campo personalizzato ha già un valore
        update_post_meta( $post->ID, $key, $value );
    } else { // Se il campo personalizzato non ha un valore
        add_post_meta( $post->ID, $key, $value );
    }
    if ( !$value ) 
                delete_post_meta( $post->ID, $key ); // Elimina se vuoto
}

}

add_action( 'save_post', 'ep_eventposts_save_meta', 1, 2 );

MODIFICA2: e questo è quello che sto cercando di usare per controllare i dati del post dopo il salvataggio nel database.

add_action( 'save_post', 'ep_eventposts_check_meta', 99, 2 );
function ep_eventposts_check_meta( $post_id, $post ) {
//controlla che i metadati siano completi quando un post viene pubblicato
//print_r($_POST);

if ( $_POST['post_status'] == 'publish' ) {

    $custom = get_post_custom($post_id);

    //assicurati che entrambe le date siano compilate
    if ( !array_key_exists('_start_timestamp', $custom ) || !array_key_exists('_end_timestamp', $custom )) {
        $post->post_status = 'draft';
        wp_update_post($post);

    }
    //assicurati che inizio < fine
    elseif ( $custom['_start_timestamp'] > $custom['_end_timestamp'] ) {
        $post->post_status = 'draft';
        wp_update_post($post);
    }
    else {
        return;
    }
}
}

Il problema principale con questo è un problema che è stato effettivamente descritto in un'altra domanda: usando wp_update_post() all'interno di un hook save_post si innesca un loop infinito.

MODIFICA3: Ho trovato un modo per farlo, agganciando wp_insert_post_data invece di save_post. L'unico problema è che ora lo post_status viene ripristinato, ma appare un messaggio fuorviante che dice "Post pubblicato" (aggiungendo &message=6 all'URL reindirizzato), ma lo stato è impostato su Bozza.

add_filter( 'wp_insert_post_data', 'ep_eventposts_check_meta', 99, 2 );
function ep_eventposts_check_meta( $data, $postarr ) {
//controlla che i metadati siano completi quando un post viene pubblicato, altrimenti torna a bozza
if ( $data['post_type'] != 'event' ) {
    return $data;
}
if ( $postarr['post_status'] == 'publish' ) {
    $custom = get_post_custom($postarr['ID']);

    //assicurati che entrambe le date siano compilate
    if ( !array_key_exists('_start_timestamp', $custom ) || !array_key_exists('_end_timestamp', $custom )) {
        $data['post_status'] = 'draft';
    }
    //assicurati che inizio < fine
    elseif ( $custom['_start_timestamp'] > $custom['_end_timestamp'] ) {
        $data['post_status'] = 'draft';
    }
    //tutto ok!
    else {
        return $data;
    }
}

return $data;
}
0
Tutte le risposte alla domanda 5
3
21

Come ha sottolineato m0r7if3r, non esiste un modo per impedire la pubblicazione di un post utilizzando l'hook save_post, poiché al momento in cui questo hook viene attivato, il post è già stato salvato. Tuttavia, il seguente codice ti permetterà di ripristinare lo stato del post senza utilizzare wp_insert_post_data e senza causare un loop infinito.

Il seguente codice non è stato testato, ma dovrebbe funzionare.

<?php
add_action('save_post', 'my_save_post');
function my_save_post($post_id) {
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
         return;

    if ( !isset( $_POST['ep_eventposts_nonce'] ) )
         return;

    if ( !wp_verify_nonce( $_POST['ep_eventposts_nonce'], plugin_basename( __FILE__ ) ) )
         return;

    // L'utente ha il permesso di modificare il post o la pagina?
     if ( !current_user_can( 'edit_post', $post->ID ) )
         return;

   // Ora esegui i controlli per validare i tuoi dati. 
   // Nota che i campi personalizzati (diversi dai dati nelle metabox personalizzate!) 
   // saranno già stati salvati.
    $prevent_publish= false;//Imposta su true se i dati non sono validi.
    if ($prevent_publish) {
        // Rimuovi l'hook per prevenire un loop infinito
        remove_action('save_post', 'my_save_post');

        // Aggiorna il post per cambiare lo stato
        wp_update_post(array('ID' => $post_id, 'post_status' => 'draft'));

        // Ripristina l'hook
        add_action('save_post', 'my_save_post');
    }
}
?>

Non ho verificato, ma osservando il codice, il messaggio di feedback mostrerà erroneamente che il post è stato pubblicato. Questo perché WordPress ci reindirizza a un URL in cui la variabile message ora non è corretta.

Per modificarlo, possiamo utilizzare il filtro redirect_post_location:

add_filter('redirect_post_location','my_redirect_location',10,2);
function my_redirect_location($location,$post_id){
    // Se il post è stato pubblicato...
    if (isset($_POST['publish'])){
        // Ottieni lo stato corrente del post
        $status = get_post_status( $post_id );

        // Il post è stato "pubblicato", ma se è ancora una bozza, mostra il messaggio di bozza (10).
        if($status=='draft')
            $location = add_query_arg('message', 10, $location);
    }

    return $location;
}

Per riassumere il filtro di reindirizzamento sopra: se un post è impostato per essere pubblicato, ma è ancora una bozza, allora modifichiamo il messaggio di conseguenza (che è message=10). Ancora una volta, questo non è stato testato, ma dovrebbe funzionare. Il Codex di add_query_arg suggerisce che quando una variabile è già impostata, la funzione la sostituisce (ma, come ho detto, non ho testato questo).

13 feb 2012 18:04:17
Commenti

Oltre al punto e virgola mancante nella tua riga add_query_arg, questo trucco con il filtro redirect_post_location è esattamente ciò di cui avevo bisogno. Grazie!

MadtownLems MadtownLems
2 apr 2014 22:04:21

@MadtownLems sistemato :)

Stephen Harris Stephen Harris
3 apr 2014 03:18:09

Questa è stata l'unica soluzione che ha funzionato per me (non volevo usare hack JavaScript). Sei il migliore!

Cogicero Cogicero
21 gen 2021 05:59:12
2

OK, ecco come ho finito per realizzarlo: una chiamata Ajax a una funzione PHP che esegue il controllo, ispirata in qualche modo a questa risposta e utilizzando un suggerimento intelligente da una domanda che ho posto su StackOverflow. Importante, mi assicuro che il controllo venga eseguito solo quando vogliamo Pubblicare, così che una Bozza possa sempre essere salvata senza il controllo. Alla fine questa si è rivelata la soluzione più semplice per effettivamente impedire la pubblicazione del post. Potrebbe essere utile a qualcun altro, quindi l'ho documentata qui.

Per prima cosa, aggiungi il Javascript necessario:

//AJAX per validare l'evento prima della pubblicazione
//adattato da https://wordpress.stackexchange.com/questions/15546/dont-publish-custom-post-type-post-if-a-meta-data-field-isnt-valid
add_action('admin_enqueue_scripts-post.php', 'ep_load_jquery_js');   
add_action('admin_enqueue_scripts-post-new.php', 'ep_load_jquery_js');   
function ep_load_jquery_js(){
global $post;
if ( $post->post_type == 'event' ) {
    wp_enqueue_script('jquery');
}
}

add_action('admin_head-post.php','ep_publish_admin_hook');
add_action('admin_head-post-new.php','ep_publish_admin_hook');
function ep_publish_admin_hook(){
global $post;
if ( is_admin() && $post->post_type == 'event' ){
    ?>
    <script language="javascript" type="text/javascript">
        jQuery(document).ready(function() {
            jQuery('#publish').click(function() {
                if(jQuery(this).data("valid")) {
                    return true;
                }
                var form_data = jQuery('#post').serializeArray();
                var data = {
                    action: 'ep_pre_submit_validation',
                    security: '<?php echo wp_create_nonce( 'pre_publish_validation' ); ?>',
                    form_data: jQuery.param(form_data),
                };
                jQuery.post(ajaxurl, data, function(response) {
                    if (response.indexOf('true') > -1 || response == true) {
                        jQuery("#post").data("valid", true).submit();
                    } else {
                        alert("Errore: " + response);
                        jQuery("#post").data("valid", false);

                    }
                    //nascondi l'icona di caricamento, ripristina il pulsante Pubblica
                    jQuery('#ajax-loading').hide();
                    jQuery('#publish').removeClass('button-primary-disabled');
                    jQuery('#save-post').removeClass('button-disabled');
                });
                return false;
            });
        });
    </script>
    <?php
}
}

Poi, la funzione che gestisce il controllo:

add_action('wp_ajax_ep_pre_submit_validation', 'ep_pre_submit_validation');
function ep_pre_submit_validation() {
//semplice controllo di Sicurezza
check_ajax_referer( 'pre_publish_validation', 'security' );

//converte la stringa di dati ricevuta in un array
//da https://wordpress.stackexchange.com/a/26536/10406
parse_str( $_POST['form_data'], $vars );

//verifica che si stia effettivamente tentando di pubblicare un post
if ( $vars['post_status'] == 'publish' || 
    (isset( $vars['original_publish'] ) && 
     in_array( $vars['original_publish'], array('Pubblica', 'Programma', 'Aggiorna') ) ) ) {
    if ( empty( $vars['_start_date'] ) || empty( $vars['_end_date'] ) ) {
        _e('Entrambe le date di Inizio e Fine devono essere compilate');
        die();
    }
    //assicurati che inizio < fine
    elseif ( $vars['_start_date'] > $vars['_end_date'] ) {
        _e('La data di inizio non può essere dopo la data di fine');
        die();
    }
    //controlla che l'orario sia specificato nel caso di un evento non per tutta la giornata
    elseif ( !isset($vars['_all_day'] ) ) {
        if ( empty($vars['_start_time'] ) || empty( $vars['_end_time'] ) ) {
            _e('Entrambi gli orari di Inizio e Fine devono essere specificati se l\'evento non è per tutta la giornata');
            die();              
        }
        elseif ( strtotime( $vars['_start_date']. ' ' .$vars['_start_time'] ) > strtotime( $vars['_end_date']. ' ' .$vars['_end_time'] ) ) {
            _e('La data/ora di inizio non può essere dopo la data/ora di fine');
            die();
        }
    }
}

//tutto ok, permetti l'invio
echo 'true';
die();
}

Questa funzione restituisce true se tutto è ok, e invia il modulo per pubblicare il post tramite il canale normale. Altrimenti, la funzione restituisce un messaggio di errore che viene mostrato come alert(), e il modulo non viene inviato.

17 feb 2012 19:31:32
Commenti

Ho seguito lo stesso approccio ma il post viene salvato come "Bozza" invece che "Pubblica" quando la funzione di validazione restituisce true. Non sono sicuro di come risolvere questo problema!!!<br/>Inoltre non riesco a ottenere i dati per un campo textarea (es. post_content, o qualsiasi altro campo personalizzato di tipo textarea) durante la chiamata ajax?

Mahmudur Mahmudur
10 ago 2012 06:47:00

Ho applicato questa soluzione in modo leggermente diverso: innanzitutto ho utilizzato il seguente codice nel javascript in caso di successo: delayed_autosave(); //recupera i dati dal campo textarea/tinymce jQuery('#publish').data("valid", true).trigger('click'); //pubblica il post Grazie mille.

Mahmudur Mahmudur
13 ago 2012 05:25:05
4

Penso che il modo migliore per affrontare questo problema non sia tanto PREVENIRE il cambiamento di stato quanto REVERTIRLO se dovesse verificarsi. Ad esempio: puoi agganciarti a save_post con una priorità molto alta (in modo che l'hook venga eseguito molto tardi, cioè dopo aver inserito i tuoi meta), quindi verificare lo post_status del post appena salvato e aggiornarlo a "pending" (o "draft" o altro) se non soddisfa i tuoi criteri.

Una strategia alternativa sarebbe agganciarsi a wp_insert_post_data per impostare direttamente il post_status. Lo svantaggio di questo metodo, per quanto mi riguarda, è che non avrai ancora inserito i postmeta nel database, quindi dovrai elaborarli, ecc. sul momento per fare i tuoi controlli, poi elaborarli nuovamente per inserirli nel database... il che potrebbe comportare un sovraccarico significativo, sia in termini di prestazioni che di codice.

12 feb 2012 01:53:44
Commenti

Attualmente sto agganciando save_post con priorità 1 per salvare i campi meta dalle metabox; quello che stai proponendo quindi è avere un secondo hook per save_post con priorità, diciamo, 99? Questo garantirebbe l'integrità? E se per qualche motivo il primo hook viene eseguito, i metadati vengono inseriti e il post pubblicato, ma il secondo hook no, finendo così con campi non validi?

englebip englebip
12 feb 2012 14:00:03

Non riesco a immaginare una situazione in cui il primo hook verrebbe eseguito ma non il secondo... a quale scenario stai pensando che potrebbe causare ciò? Se sei così preoccupato, puoi inserire i post meta, verificare i post meta e poi aggiornare lo post_status tutto in una singola funzione eseguita da una singola chiamata a un hook, se preferisci.

mor7ifer mor7ifer
12 feb 2012 15:36:52

Ho pubblicato il mio codice come modifica alla mia domanda; ho provato a usare un secondo hook per save_post ma questo innesca un loop infinito.

englebip englebip
12 feb 2012 21:58:49

Il tuo problema è che dovresti controllare il post creato. Quindi if( get_post_status( $post_id ) == 'publish' ) è ciò che dovresti usare, poiché andrai a ridefinire i dati in $wpdb->posts, non i dati in $_POST[].

mor7ifer mor7ifer
12 feb 2012 23:05:39
0

Il metodo migliore potrebbe essere JAVASCRIPT:

<script type="text/javascript">
var field_id =  "My_field_div__ID";    // <----------------- CAMBIA QUESTO

var SubmitButton = document.getElementById("save-post") || false;
var PublishButton = document.getElementById("publish")  || false; 
if (SubmitButton)   {SubmitButton.addEventListener("click", SubmCLICKED, false);}
if (PublishButton)  {PublishButton.addEventListener("click", SubmCLICKED, false);}
function SubmCLICKED(e){   
  var passed= false;
  if(!document.getElementById(field_id)) { alert("Non riesco a trovare quell'ID di campo !!"); }
  else {
      var Enabled_Disabled= document.getElementById(field_id).value;
      if (Enabled_Disabled == "" ) { alert("Il campo è vuoto");   }  else{passed=true;}
  }
  if (!passed) { e.preventDefault();  return false;  }
}
</script>
26 giu 2015 13:56:55
0
-2

Mi dispiace non poterti dare una risposta diretta, ma ricordo di aver fatto qualcosa di simile molto recentemente, solo che non ricordo esattamente come. Penso di averlo fatto in modo indiretto - qualcosa come impostarlo come valore predefinito e se l'utente non l'avesse cambiato, lo rilevavo in un'istruzione if quindi -> if(categoria==categoria predefinita) {echo "Non hai selezionato una categoria!"; return reindirizza alla pagina di creazione articolo; } scusa se non è una risposta diretta ma spero possa aiutare un po'.

12 feb 2012 03:20:36