Zapobiegaj publikowaniu postów, jeśli pola niestandardowe nie zostaną wypełnione

17

Mam niestandardowy typ postu Eventzawierający początkowe i końcowe pola niestandardowej daty / godziny (jako metaboksy na ekranie edycji postów).

Chciałbym się upewnić, że wydarzenie nie może zostać opublikowane (lub zaplanowane) bez wypełnienia dat, ponieważ spowoduje to problemy z szablonami wyświetlającymi dane zdarzenia (poza tym, że jest to konieczny wymóg!). Chciałbym jednak mieć możliwość organizowania wydarzeń Draft, które nie zawierają prawidłowej daty podczas ich przygotowywania.

Myślałem o save_postsprawdzeniu, ale jak mogę zapobiec zmianie statusu?

EDYCJA 1: To jest hak, którego używam teraz do zapisania 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 );

EDYCJA 2: i właśnie tego próbuję użyć do sprawdzenia danych postu po zapisaniu w bazie danych.

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

Głównym problemem jest problem, który został opisany w innym pytaniu : użycie wp_update_post()w obrębie save_posthaka uruchamia nieskończoną pętlę.

EDYCJA 3: Wymyśliłem sposób na zrobienie tego, zaczepiając wp_insert_post_datazamiast save_post. Jedyny problem polega na tym, że teraz post_statusjest on cofany, ale teraz pojawia się mylący komunikat „Opublikuj opublikowany” (poprzez dodanie &message=6do przekierowanego adresu URL), ale status jest ustawiony na Wersja robocza.

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
źródło

Odpowiedzi:

16

Jak wskazał m0r7if3r, nie ma sposobu, aby zapobiec opublikowaniu posta za pomocą save_posthaka, ponieważ do czasu uruchomienia haka post jest już zapisany. Poniższe czynności pozwolą jednak przywrócić status bez użycia wp_insert_post_datai bez powodowania nieskończonej pętli.

Poniższe nie jest testowane, ale powinno działać.

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

Nie sprawdziłem, ale patrząc na kod, komunikat zwrotny wyświetli niepoprawną wiadomość, że post został opublikowany. Wynika to z faktu, że WordPress przekierowuje nas na adres URL, gdzie messagezmienna jest teraz niepoprawna.

Aby to zmienić, możemy użyć redirect_post_locationfiltra:

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

Podsumowując powyższy filtr przekierowań: Jeśli post ma zostać opublikowany, ale nadal jest wersją roboczą, odpowiednio zmieniamy wiadomość (która jest message=10). Znów jest to niesprawdzone, ale powinno działać. Kodeks add_query_argsugeruje, że gdy zmienna jest już ustawiona, funkcja zastępuje ją (ale jak mówię, nie testowałem tego).

Stephen Harris
źródło
Inne niż brakujące; w linii add_query_arg ta sztuczka filtrująca redirect_post_location jest dokładnie tym, czego potrzebowałem. dzięki!
MadtownLems
@MadtownLems naprawiono :)
Stephen Harris
9

OK, tak w końcu to zrobiłem: wywołanie Ajax do funkcji PHP, która wykonuje sprawdzanie, niejako inspirowane tą odpowiedzią i używając sprytnej wskazówki z pytania, które zadałem na StackOverflow . Co ważne, upewniam się, że tylko wtedy, gdy chcemy opublikować sprawdzanie jest zakończone, aby Szkic można zawsze zapisać bez sprawdzania. Skończyło się to łatwiejszym rozwiązaniem, które faktycznie uniemożliwi publikację postu. To może pomóc komuś innemu, więc napisałem to tutaj.

Najpierw dodaj niezbędny Javascript:

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

Następnie funkcja, która obsługuje sprawdzanie:

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

Ta funkcja zwraca, truejeśli wszystko jest w porządku, i przesyła formularz do opublikowania postu normalnym kanałem. W przeciwnym razie funkcja zwraca komunikat o błędzie wyświetlany jako alert()i formularz nie zostanie przesłany.

englebip
źródło
Zastosowałem to samo podejście i zapisywanie postu jako „Szkic” zamiast „Publikuj”, gdy funkcja sprawdzania poprawności zwróci wartość prawda. Nie wiesz, jak to naprawić !!! <br/> Nie otrzymujesz również danych dla pola tekstowego (np. Post_content, inne pole niestandardowe pola tekstowego) podczas wywołania ajax?
Mahmudur,
1
Zastosowałem to rozwiązanie trochę inaczej: po pierwsze użyłem poniższego kodu w javascript w przypadku sukcesu: wielkie delayed_autosave(); //get data from textarea/tinymce field jQuery('#publish').data("valid", true).trigger('click'); //publish postdzięki.
Mahmudur,
3

Myślę, że najlepszym sposobem na to nie jest ZAPOBIEGANIE zmianie stanu, a raczej ZWRÓCENIE go, jeśli tak się stanie. Na przykład: Przechwytujesz save_postz naprawdę wysokim priorytetem (aby hak zadziałał bardzo późno, a mianowicie po wykonaniu wstawienia meta), następnie sprawdź post_statuswpis, który właśnie został zapisany, i zaktualizuj go do oczekującego (lub wersji roboczej lub cokolwiek), jeśli nie spełnia twoich kryteriów.

Alternatywną strategią byłoby zaczepienie w wp_insert_post_datacelu ustawienia bezpośrednio post_status. Moim zdaniem wadą tej metody jest to, że nie wstawiłeś jeszcze postmeta do bazy danych, więc będziesz musiał ją przetworzyć itp. W celu wykonania kontroli, a następnie przetworzyć ją ponownie, aby wstawić w bazie danych ... co może stać się dużym obciążeniem, zarówno pod względem wydajności, jak i kodu.

mor7ifer
źródło
Obecnie przechodzę save_postdo priorytetu 1, aby zapisać pola meta przed metaboxami; to, co proponujesz, to mieć drugi hak save_postz priorytetem, powiedzmy 99? Czy to zapewni integralność? Co się stanie, jeśli z jakiegoś powodu pierwszy hak zostanie uruchomiony, metadane zostaną wstawione, a post opublikowany, ale drugi hak się nie powiedzie, więc otrzymacie nieprawidłowe pola?
englebip
Nie mogę sobie wyobrazić sytuacji, w której wystrzeliłby pierwszy hak, ale nie drugiej ... jaki scenariusz według ciebie mógłby to spowodować? Jeśli martwisz się tym, możesz wstawić meta postu, sprawdzić meta postu, a następnie zaktualizować funkcję post_statusall in one po uruchomieniu pojedynczego połączenia, jeśli chcesz.
mor7ifer
Mój kod opublikowałem jako edycję mojego pytania; Próbowałem użyć drugiego zaczepu, save_postale to wyzwala nieskończoną pętlę.
englebip
Problem polega na tym, że powinieneś sprawdzać utworzony post. Więc if( get_post_status( $post_id ) == 'publish' )tego chcesz używać, ponieważ przedefiniujesz dane $wpdb->posts, a nie dane $_POST[].
mor7ifer
0

Najlepszą metodą może być 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
źródło
-1

Przepraszam, nie mogę dać ci prostej odpowiedzi, ale pamiętam, że ostatnio zrobiłem coś podobnego. Po prostu nie pamiętam dokładnie jak. Myślę, że może zrobiłem to na swój sposób - coś takiego, jakbym miał wartość domyślną, a jeśli osoba tego nie zmieniła, podniosłem to w instrukcji if ->if(category==default category) {echo "You didn't pick a category!"; return them to the post creation page; } przepraszam, to nie jest prosta odpowiedź, ale mam nadzieję to trochę pomaga.

MIINIIM
źródło