Impedir que a postagem seja publicada se os campos personalizados não forem preenchidos

17

Eu tenho um tipo de postagem personalizado Eventque contém os campos personalizados de data / hora inicial e final (como metaboxes na tela de edição de postagem).

Gostaria de garantir que um Evento não possa ser publicado (ou agendado) sem que as datas sejam preenchidas, pois isso causará problemas nos modelos que exibem os dados do Evento (além do fato de que é um requisito necessário!). No entanto, eu gostaria de poder ter eventos de rascunho que não contenham uma data válida enquanto estão em preparação.

Eu estava pensando em ligar save_postpara fazer a verificação, mas como posso impedir que a alteração de status aconteça?

EDIT1: Este é o gancho que estou usando agora para salvar o 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 é isso que estou tentando usar para verificar os dados da postagem depois de salvar no banco de dados.

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

O principal problema com isso é um problema que foi realmente descrito em outra pergunta : o uso wp_update_post()dentro de um save_postgancho aciona um loop infinito.

EDIT3: Eu descobri uma maneira de fazê-lo, conectando em wp_insert_post_datavez de save_post. O único problema é que agora o post_statusitem é revertido, mas agora aparece uma mensagem enganosa dizendo "Postagem publicada" (adicionando &message=6ao URL redirecionado), mas o status está definido como Rascunho.

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
fonte

Respostas:

16

Como m0r7if3r apontou, não há como impedir que uma postagem seja publicada usando o save_postgancho, pois quando o gancho é disparado, a postagem já está salva. No entanto, a seguir, você poderá reverter o status sem usar wp_insert_post_datae sem causar um loop infinito.

O seguinte não foi testado, mas deve 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');
    }
}
?>

Não verifiquei, mas, olhando o código, a mensagem de feedback exibirá a mensagem incorreta de que a postagem foi publicada. Isso ocorre porque o WordPress nos redireciona para um URL em que a messagevariável está incorreta.

Para mudar, podemos usar o 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 o filtro de redirecionamento acima: Se uma postagem estiver definida para ser publicada, mas ainda for um rascunho, alteramos a mensagem de acordo (o que é message=10). Novamente, isso não foi testado, mas deve funcionar. O Codex do add_query_argsugere que quando uma variável já está definida, a função a substitui (mas como eu disse, não testei isso).

Stephen Harris
fonte
Diferente dos desaparecidos; na sua linha add_query_arg, esse truque de filtro redirect_post_location é exatamente o que eu precisava. obrigado!
MadtownLems
@MadtownLems fixed :)
Stephen Harris
9

OK, foi finalmente como eu acabei fazendo isso: uma chamada do Ajax para uma função PHP que faz a verificação, meio que inspirada por esta resposta e usando uma dica inteligente de uma pergunta que fiz no StackOverflow . É importante ressaltar que somente quando queremos publicar a verificação é feita, para que um rascunho possa sempre ser salvo sem a verificação. Essa acabou sendo a solução mais fácil para realmente impedir a publicação do post. Pode ajudar alguém, então eu escrevi aqui.

Primeiro, adicione o Javascript necessário:

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

Em seguida, a função que lida com a verificação:

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

Essa função retorna truese tudo estiver bem e envia o formulário para publicar a postagem pelo canal normal. Caso contrário, a função retornará uma mensagem de erro exibida como um alert()e o formulário não será enviado.

englebip
fonte
Eu segui a mesma abordagem e obtive a postagem sendo salva como "Rascunho" em vez de "Publicar" quando a função de validação retorna verdadeira. Não tem certeza de como corrigir isso !!! <br/> Também não obtém os dados para um campo de área de texto (por exemplo, post_content, qualquer outro campo personalizado de área de texto) durante a chamada ajax?
Mahmudur
1
Eu apliquei essa solução de maneira um pouco diferente: antes de tudo, usei o código abaixo no javascript em caso de sucesso: delayed_autosave(); //get data from textarea/tinymce field jQuery('#publish').data("valid", true).trigger('click'); //publish postMuito obrigado.
Mahmudur 13/08/2012
3

Eu acho que a melhor maneira de fazer isso é não impedir que a mudança de status aconteça tanto quanto revertê-lo, se acontecer. Por exemplo: você conecta save_post, com uma prioridade muito alta (para que o gancho seja acionado muito tarde, ou seja, após a inserção meta), verifique post_statusa postagem que acabou de ser salva e atualize-a para pendente (ou rascunho ou o que for) se não atender aos seus critérios.

Uma estratégia alternativa seria ligar wp_insert_post_datapara definir o post_status diretamente. A desvantagem desse método, no que me diz respeito, é que você ainda não inseriu o postmeta no banco de dados; portanto, você terá que processá-lo, etc. no lugar para fazer suas verificações, depois processá-lo novamente para inserir no banco de dados ... o que pode se tornar um monte de sobrecarga, no desempenho ou no código.

mor7ifer
fonte
No momento, estou conectando save_posta prioridade 1 para salvar os meta campos dos metaboxes; o que você está propondo é ter um segundo gancho save_postcom prioridade, digamos, 99? Isso garantiria integridade? E se, por algum motivo, o primeiro gancho for acionado, os metadados forem inseridos e a postagem publicada, mas o segundo gancho não, então você terá campos inválidos?
englebip
Não consigo pensar em uma situação em que o primeiro gancho seria acionado, mas não o segundo ... que tipo de cenário você acha que poderia causar isso? Se você está preocupado com isso, pode inserir a meta post, verificar a meta post e atualizar a post_statusfunção tudo em uma executando uma única chamada para um gancho, se preferir.
mor7ifer
Publiquei meu código como uma edição da minha pergunta; Eu tentei usar um segundo gancho para save_postmas que desencadeia um loop infinito.
12133 Englebip
Seu problema é que você deve verificar a postagem criada. Então if( get_post_status( $post_id ) == 'publish' )é isso que você quer usar, desde que você estará redefinindo os dados no $wpdb->posts, não os dados em $_POST[].
mor7ifer
0

O melhor método pode 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
fonte
-1

Desculpe, não posso lhe dar uma resposta direta, mas lembro-me de fazer algo semelhante muito recentemente. Não consigo me lembrar exatamente como. Eu acho que talvez tenha feito isso de alguma maneira - algo como se eu tivesse um valor padrão e se a pessoa não o tivesse alterado, peguei isso em uma declaração if -> if(category==default category) {echo "You didn't pick a category!"; return them to the post creation page; }desculpe, isso não é uma resposta direta, mas espero isso ajuda um pouco.

MIINIIM
fonte