Impedisci la pubblicazione dei post se i campi personalizzati non sono compilati


17

Ho un tipo di post personalizzato Eventche contiene una data / ora di inizio / fine campi personalizzati (come metabox nella schermata di modifica post).

Vorrei assicurarmi che un Evento non potesse essere pubblicato (o programmato) senza che le date fossero riempite, poiché ciò causerebbe problemi con i modelli che mostrano i dati dell'Evento (oltre al fatto che è un requisito necessario!). Tuttavia, vorrei essere in grado di organizzare eventi Bozza che non contengano una data valida mentre sono in preparazione.

Stavo pensando di collegarmi save_postper fare il controllo, ma come posso impedire che si verifichi il cambio di stato?

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

// Save the Metabox Data
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;

// Is the user allowed to edit the post or page?
if ( !current_user_can( 'edit_post', $post->ID ) )
    return;

// OK, we're authenticated: we need to find and save the data
// We'll put it into an array to make it easier to loop though

//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'];

// Add values of $events_meta as custom fields

foreach ( $events_meta as $key => $value ) { // Cycle through the $events_meta array!
    if ( $post->post_type == 'revision' ) return; // Don't store custom data twice
    $value = implode( ',', (array)$value ); // If $value is an array, make it a CSV (unlikely)
    if ( get_post_meta( $post->ID, $key, FALSE ) ) { // If the custom field already has a value
        update_post_meta( $post->ID, $key, $value );
    } else { // If the custom field doesn't have a value
        add_post_meta( $post->ID, $key, $value );
    }
    if ( !$value ) 
                delete_post_meta( $post->ID, $key ); // Delete if blank
}

}

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

EDIT2: e questo è quello che sto cercando di usare per controllare i dati dei post dopo averli salvati nel database.

add_action( 'save_post', 'ep_eventposts_check_meta', 99, 2 );
function ep_eventposts_check_meta( $post_id, $post ) {
//check that metadata is complete when a post is published
//print_r($_POST);

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

    $custom = get_post_custom($post_id);

    //make sure both dates are filled
    if ( !array_key_exists('_start_timestamp', $custom ) || !array_key_exists('_end_timestamp', $custom )) {
        $post->post_status = 'draft';
        wp_update_post($post);

    }
    //make sure start < end
    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 : l'utilizzo wp_update_post()all'interno di un save_posthook attiva un ciclo infinito.

EDIT3: ho pensato a un modo per farlo, agganciando wp_insert_post_datainvece di save_post. L'unico problema è che ora post_statusviene ripristinato, ma ora viene visualizzato un messaggio fuorviante che dice "Post pubblicato" (aggiungendo &message=6all'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 ) {
//check that metadata is complete when a post is published, otherwise revert to draft
if ( $data['post_type'] != 'event' ) {
    return $data;
}
if ( $postarr['post_status'] == 'publish' ) {
    $custom = get_post_custom($postarr['ID']);

    //make sure both dates are filled
    if ( !array_key_exists('_start_timestamp', $custom ) || !array_key_exists('_end_timestamp', $custom )) {
        $data['post_status'] = 'draft';
    }
    //make sure start < end
    elseif ( $custom['_start_timestamp'] > $custom['_end_timestamp'] ) {
        $data['post_status'] = 'draft';
    }
    //everything fine!
    else {
        return $data;
    }
}

return $data;
}

Risposte:


16

Come ha sottolineato m0r7if3r, non c'è modo di impedire la pubblicazione di un post utilizzando l' save_posthook, poiché al momento dell'attivazione dell'hook, il post è già salvato. Quanto segue, tuttavia, ti permetterà di ripristinare lo stato senza usare wp_insert_post_datae senza causare un ciclo infinito.

Quanto segue non è 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;

    // Is the user allowed to edit the post or page?
     if ( !current_user_can( 'edit_post', $post->ID ) )
         return;

   // Now perform checks to validate your data. 
   // Note custom fields (different from data in custom metaboxes!) 
   // will already have been saved.
    $prevent_publish= false;//Set to true if data was invalid.
    if ($prevent_publish) {
        // unhook this function to prevent indefinite loop
        remove_action('save_post', 'my_save_post');

        // update the post to change post status
        wp_update_post(array('ID' => $post_id, 'post_status' => 'draft'));

        // re-hook this function again
        add_action('save_post', 'my_save_post');
    }
}
?>

Non ho controllato, ma guardando il codice, il messaggio di feedback mostrerà il messaggio errato che il post è stato pubblicato. Questo perché WordPress ci reindirizza a un URL in cui la messagevariabile non è corretta.

Per cambiarlo, possiamo usare il redirect_post_locationfiltro:

add_filter('redirect_post_location','my_redirect_location',10,2);
function my_redirect_location($location,$post_id){
    //If post was published...
    if (isset($_POST['publish'])){
        //obtain current post status
        $status = get_post_status( $post_id );

        //The post was 'published', but if it is still a draft, display draft message (10).
        if($status=='draft')
            $location = add_query_arg('message', 10, $location);
    }

    return $location;
}

Riassumendo il filtro di reindirizzamento sopra: Se un post è impostato per essere pubblicato, ma è ancora una bozza, modifichiamo di conseguenza il messaggio (che è message=10). Ancora una volta, questo non è testato, ma dovrebbe funzionare. Il Codice del add_query_argsuggerisce che quando una variabile è già impostata, la funzione la sostituisce (ma come ho già detto, non l'ho testata).


Oltre ai dispersi; sulla tua riga add_query_arg, questo trucco del filtro redirect_post_location è esattamente quello di cui avevo bisogno. Grazie!
MadtownLems

@MadtownLems fixed :)
Stephen Harris

9

OK, questo è finalmente il modo in cui ho finito per farlo: una chiamata Ajax a una funzione PHP che fa il controllo, in qualche modo ispirata da questa risposta e usando un suggerimento intelligente da una domanda che ho posto su StackOverflow . Soprattutto, mi assicuro che solo quando vogliamo pubblicare il controllo sia fatto, in modo che una bozza possa sempre essere salvata senza il controllo. Questa è stata la soluzione più semplice per impedire effettivamente la pubblicazione del post. Potrebbe aiutare qualcun altro, quindi l'ho scritto qui.

Innanzitutto, aggiungi il Javascript necessario:

//AJAX to validate event before publishing
//adapted from /wordpress/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("Error: " + response);
                        jQuery("#post").data("valid", false);

                    }
                    //hide loading icon, return Publish button to normal
                    jQuery('#ajax-loading').hide();
                    jQuery('#publish').removeClass('button-primary-disabled');
                    jQuery('#save-post').removeClass('button-disabled');
                });
                return false;
            });
        });
    </script>
    <?php
}
}

Quindi, la funzione che gestisce il controllo:

add_action('wp_ajax_ep_pre_submit_validation', 'ep_pre_submit_validation');
function ep_pre_submit_validation() {
//simple Security check
check_ajax_referer( 'pre_publish_validation', 'security' );

//convert the string of data received to an array
//from /wordpress//a/26536/10406
parse_str( $_POST['form_data'], $vars );

//check that are actually trying to publish a post
if ( $vars['post_status'] == 'publish' || 
    (isset( $vars['original_publish'] ) && 
     in_array( $vars['original_publish'], array('Publish', 'Schedule', 'Update') ) ) ) {
    if ( empty( $vars['_start_date'] ) || empty( $vars['_end_date'] ) ) {
        _e('Both Start and End date need to be filled');
        die();
    }
    //make sure start < end
    elseif ( $vars['_start_date'] > $vars['_end_date'] ) {
        _e('Start date cannot be after End date');
        die();
    }
    //check time is also inputted in case of a non-all-day event
    elseif ( !isset($vars['_all_day'] ) ) {
        if ( empty($vars['_start_time'] ) || empty( $vars['_end_time'] ) ) {
            _e('Both Start time and End time need to be specified if the event is not an all-day event');
            die();              
        }
        elseif ( strtotime( $vars['_start_date']. ' ' .$vars['_start_time'] ) > strtotime( $vars['_end_date']. ' ' .$vars['_end_time'] ) ) {
            _e('Start date/time cannot be after End date/time');
            die();
        }
    }
}

//everything ok, allow submission
echo 'true';
die();
}

Questa funzione ritorna truese tutto va bene e invia il modulo per pubblicare il post sul canale normale. In caso contrario, la funzione restituisce un messaggio di errore che viene visualizzato come alert()e il modulo non viene inviato.


Ho seguito lo stesso approccio e ho ottenuto che il post fosse salvato come "Bozza" anziché "Pubblica" quando la funzione di convalida ritorna vera. Non sai come risolverlo !!! <br/> Inoltre non riesci a ottenere i dati per un campo textarea (es. Post_content, qualsiasi altro campo personalizzato dell'area di testo) durante la chiamata ajax?
Mahmudur,

1
Ho applicato questa soluzione in modo leggermente diverso: prima di tutto ho usato il codice seguente nel javascript in caso di successo: delayed_autosave(); //get data from textarea/tinymce field jQuery('#publish').data("valid", true).trigger('click'); //publish postMille grazie.
Mahmudur,

3

Penso che il modo migliore per farlo non sia PREVENIRE che il cambiamento di stato accada tanto quanto lo è RIPRISTINARLO se lo fa. Ad esempio: hook save_post, con una priorità molto alta (in modo che l'hook si attivi molto tardi, vale a dire dopo aver effettuato il tuo meta insert), quindi controlla post_statusil post che è stato appena salvato e lo aggiorni in attesa (o bozza o comunque) se non soddisfa i tuoi criteri.

Una strategia alternativa sarebbe quella di agganciare wp_insert_post_dataper impostare direttamente post_status. Lo svantaggio di questo metodo, per quanto mi riguarda, è che non avrai ancora inserito il postmeta nel database, quindi dovrai elaborarlo, ecc. Sul posto per fare i tuoi controlli, quindi elaborarlo di nuovo per inserire nel database ... che potrebbe diventare molto sovraccarico, sia in termini di prestazioni che di codice.


Attualmente sto collegando save_postcon priorità 1 per salvare i metacampi dai metabox; quello che stai proponendo quindi è di avere un secondo hook save_postcon priorità, diciamo 99? Ciò garantirebbe integrità? E se per qualche motivo il primo hook si attiva, i metadati vengono inseriti e il post pubblicato, ma il secondo hook no, quindi si finisce con campi non validi?
Englebip,

Non riesco a pensare a una situazione in cui il primo gancio sparerebbe, ma non il secondo ... che tipo di scenario stai pensando che potrebbe causare questo? Se sei così preoccupato, puoi inserire il post meta, controllare il post meta e quindi aggiornare il post_statustutto in una funzione eseguendo una singola chiamata su un hook se preferisci.
mor7ifer,

Ho pubblicato il mio codice come modifica alla mia domanda; Ho provato a usare un secondo hook save_postma questo fa scattare un loop infinito.
Englebip,

Il tuo problema è che dovresti controllare il post creato. Quindi if( get_post_status( $post_id ) == 'publish' )è quello che vuoi usare, dato che ridefinirai i dati $wpdb->posts, non i dati $_POST[].
mor7ifer,

0

Il metodo migliore può essere JAVASCRIPT:

<script type="text/javascript">
var field_id =  "My_field_div__ID";    // <----------------- CHANGE THIS

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("I cant find that field ID !!"); }
  else {
      var Enabled_Disabled= document.getElementById(field_id).value;
      if (Enabled_Disabled == "" ) { alert("Field is Empty");   }  else{passed=true;}
  }
  if (!passed) { e.preventDefault();  return false;  }
}
</script>

-1

Mi dispiace non posso darti una risposta chiara ma ricordo di aver fatto qualcosa di simile molto recentemente, non riesco proprio a ricordare esattamente come. Penso che forse l'ho fatto in giro - qualcosa come se avessi un valore predefinito e se la persona non lo avesse cambiato l'ho preso in una dichiarazione if quindi -> if(category==default category) {echo "You didn't pick a category!"; return them to the post creation page; }scusa non è una risposta semplice ma spero aiuta un po '.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.