Evite la publicación de publicaciones si los campos personalizados no se completan

17

Tengo un tipo de publicación personalizada Eventque contiene campos personalizados de fecha / hora de inicio y finalización (como metaboxes en la pantalla de edición de publicaciones).

Me gustaría asegurarme de que un Evento no se pueda publicar (o programar) sin que se completen las fechas, ya que eso causará problemas con las plantillas que muestran los datos del Evento (¡además del hecho de que es un requisito necesario!). Sin embargo, me gustaría poder tener eventos de borrador que no contengan una fecha válida mientras están en preparación.

Estaba pensando en conectarme save_postpara hacer la verificación, pero ¿cómo puedo evitar que ocurra el cambio de estado?

EDITAR1: Este es el gancho que estoy usando ahora para guardar el 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: y esto es lo que estoy tratando de usar para verificar los datos de la publicación después de guardarlos en la base de datos.

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;
    }
}
}

El problema principal con esto es un problema que en realidad se describió en otra pregunta : el uso wp_update_post()dentro de un save_postgancho desencadena un bucle infinito.

EDITAR3: pensé en una forma de hacerlo, enganchando en wp_insert_post_datalugar de hacerlo save_post. El único problema es que ahora post_statusse revierte, pero ahora aparece un mensaje engañoso que dice "Publicación publicada" (agregando &message=6a la URL redirigida), pero el estado se establece en Borrador.

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;
}
englebip
fuente

Respuestas:

16

Como señaló m0r7if3r, no hay forma de evitar que una publicación se publique usando el save_postgancho, ya que para cuando se lanza el gancho, la publicación ya está guardada. Sin embargo, lo siguiente le permitirá revertir el estado sin usar wp_insert_post_datay sin causar un bucle infinito.

Lo siguiente no está probado, pero debería funcionar.

<?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');
    }
}
?>

No lo he verificado, pero al mirar el código, el mensaje de comentarios mostrará el mensaje incorrecto de que la publicación fue publicada. Esto se debe a que WordPress nos redirige a una url donde la messagevariable ahora es incorrecta.

Para cambiarlo, podemos usar el 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;
}

Para resumir el filtro de redireccionamiento anterior: si una publicación está configurada para publicarse, pero sigue siendo un borrador, modificamos el mensaje en consecuencia (que es message=10). Nuevamente, esto no se ha probado, pero debería funcionar. El Codex add_query_argsugiere que cuando una variable ya está configurada, la función la reemplaza (pero como digo, no he probado esto).

Stephen Harris
fuente
Aparte de los desaparecidos; en su línea add_query_arg, este truco de filtro redirect_post_location es exactamente lo que necesitaba. ¡Gracias!
MadtownLems
@MadtownLems corregido :)
Stephen Harris
9

Bien, finalmente así es como terminé haciéndolo: una llamada de Ajax a una función PHP que realiza la comprobación, inspirada en esta respuesta y usando un consejo inteligente de una pregunta que hice en StackOverflow . Es importante destacar que solo me aseguro de que cuando deseamos publicar la verificación se realice, de modo que un borrador siempre se pueda guardar sin la verificación. Esto terminó siendo la solución más fácil para prevenir la publicación de la publicación. Podría ayudar a alguien más, así que lo escribí aquí.

Primero, agregue el Javascript necesario:

//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
}
}

Luego, la función que maneja la comprobación:

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();
}

Esta función se devuelve truesi todo está bien y envía el formulario para publicar la publicación por el canal normal. De lo contrario, la función devuelve un mensaje de error que se muestra como un alert(), y el formulario no se envía.

englebip
fuente
He seguido el mismo enfoque y he guardado la publicación como "Borrador" en lugar de "Publicar" cuando la función de validación devuelve verdadero. ¡No estoy seguro de cómo solucionarlo! <br/> ¿Además de no obtener los datos de un campo de área de texto (por ejemplo, post_content, cualquier otro campo personalizado de área de texto) durante la llamada ajax?
Mahmudur
1
He aplicado esta solución un poco diferente: en primer lugar, he usado el código siguiente en JavaScript en caso de éxito: delayed_autosave(); //get data from textarea/tinymce field jQuery('#publish').data("valid", true).trigger('click'); //publish postmuchas gracias.
Mahmudur
3

Creo que la mejor manera de hacerlo es NO EVITAR que el cambio de estado se produzca, sino REVERTIRLO si es así. Por ejemplo: engancha save_post, con una prioridad realmente alta (para que el enganche se dispare muy tarde, es decir, después de hacer su meta inserción), luego verifique post_statusla publicación que acaba de guardar y actualícela como pendiente (o borrador o lo que sea) si no cumple con sus criterios.

Una estrategia alternativa sería enganchar wp_insert_post_datapara establecer el post_status directamente. La desventaja de este método, en lo que a mí respecta, es que todavía no habrá insertado el postmeta en la base de datos, por lo que tendrá que procesarlo, etc. para realizar sus comprobaciones, luego procesarlo nuevamente para insertarlo. en la base de datos ... lo que podría convertirse en una sobrecarga, ya sea en rendimiento o en código.

mor7ifer
fuente
Actualmente estoy enganchando save_postcon prioridad 1 para guardar los metacampos de los metaboxes; entonces lo que está proponiendo es tener un segundo gancho parasave_post con prioridad, digamos, 99? ¿Esto garantizaría la integridad? ¿Qué sucede si por alguna razón se dispara el primer gancho, se insertan los metadatos y se publica la publicación, pero el segundo gancho no, por lo que terminas con campos no válidos?
englebip
No puedo pensar en una situación en la que el primer gancho se dispare, pero no el segundo ... ¿qué tipo de escenario estás pensando que podría causar eso? Si está tan preocupado por eso, puede insertar el mensaje meta, verificar el mensaje meta y luego actualizar la función post_statustodo en uno que se ejecuta de una sola llamada a un enlace si lo prefiere.
mor7ifer
Publiqué mi código como una edición a mi pregunta; Traté de usar un segundo gancho save_postpero eso desencadena un bucle infinito.
englebip
Su problema es que debe verificar la publicación creada. Entonces, if( get_post_status( $post_id ) == 'publish' )es lo que desea utilizar, ya que redefinirá los datos $wpdb->posts, no los datos $_POST[].
mor7ifer
0

El mejor método puede ser 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>
T.Todua
fuente
-1

Lo siento, no puedo darte una respuesta directa, pero recuerdo haber hecho algo similar muy recientemente. No puedo recordar exactamente cómo. Creo que tal vez lo hice de manera aproximada, algo así como que tenía un valor predeterminado y si la persona no lo había cambiado, lo recogí en una declaración if, así que> if(category==default category) {echo "You didn't pick a category!"; return them to the post creation page; }perdón, esta no es una respuesta directa, pero espero Ayuda un poco.

MIINIIM
fuente