Запретить публикацию публикации, если пользовательские поля не заполнены

17

У меня есть пользовательский тип сообщения, Eventкоторый содержит начальные и конечные настраиваемые поля даты / времени (как метабоксы на экране редактирования сообщения).

Я хотел бы убедиться, что событие не может быть опубликовано (или запланировано) без заполнения дат, так как это вызовет проблемы с шаблонами, отображающими данные события (помимо того факта, что это необходимое требование!). Тем не менее, я хотел бы иметь возможность иметь черновики событий, которые не содержат действительной даты, пока они готовятся.

Я думал о подключении save_postдля проверки, но как я могу предотвратить изменение статуса?

EDIT1: это крюк, который я использую сейчас, чтобы сохранить 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 );

РЕДАКТИРОВАТЬ 2: и это то, что я пытаюсь использовать для проверки данных поста после сохранения в базе данных.

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

Основная проблема с этим - проблема, которая была фактически описана в другом вопросе : использование wp_update_post()в save_postловушке запускает бесконечный цикл.

EDIT3: я придумал способ сделать это, подключив wp_insert_post_dataвместо save_post. Единственная проблема заключается в том, что теперь post_statusоно отменено, но теперь появляется вводящее в заблуждение сообщение «Опубликовать публикацию» (путем добавления &message=6к перенаправленному URL-адресу), но статус установлен как Черновик.

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
источник

Ответы:

16

Как указал m0r7if3r, невозможно предотвратить публикацию сообщения с помощью save_postловушки, поскольку к моменту запуска ловушки сообщение уже сохраняется. Однако следующее позволит вам вернуть статус, не используя wp_insert_post_dataи не вызывая бесконечный цикл.

Следующее не проверено, но должно работать.

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

Я не проверял, но, глядя на код, в сообщении обратной связи будет отображаться неверное сообщение о публикации сообщения. Это потому, что WordPress перенаправляет нас на URL, где messageпеременная теперь неверна.

Чтобы изменить его, мы можем использовать redirect_post_locationфильтр:

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

Подводя итог вышеприведенному фильтру перенаправления: Если публикация настроена на публикацию, но все еще является черновиком, мы соответствующим образом изменяем сообщение (что есть message=10). Опять же, это не проверено, но должно работать. Кодекс add_query_argпредполагает, что когда переменная уже установлена, функция заменяет ее (но, как я уже сказал, я этого не проверял).

Стивен Харрис
источник
Кроме пропавших без вести; в вашей строке add_query_arg этот трюк с фильтром redirect_post_location - именно то, что мне нужно. Благодарность!
MadtownLems
@MadtownLems исправлено :)
Стивен Харрис
9

Итак, наконец-то я так и сделал: Ajax-вызов функции PHP, которая выполняет проверку, в некотором роде вдохновленный этим ответом и использующий умный совет из вопроса, который я задал в StackOverflow . Важно отметить, что проверка выполняется только тогда, когда мы хотим опубликовать, чтобы черновик всегда можно было сохранить без проверки. В конечном итоге это стало более простым решением для предотвращения публикации поста. Это может помочь кому-то еще, поэтому я написал это здесь.

Сначала добавьте необходимый 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
}
}

Затем функция, которая обрабатывает проверку:

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

Эта функция возвращает, trueесли все в порядке, и отправляет форму для публикации сообщения по обычному каналу. В противном случае функция возвращает сообщение об ошибке, которое отображается как alert(), и форма не отправляется.

englebip
источник
Я следовал тому же подходу и получал сообщение, сохраняемое как «Черновик» вместо «Опубликовать», когда функция проверки возвращает true. Не знаете, как это исправить !!! <br/> Также не получаете данные для поля textarea (например, post_content, любого другого настраиваемого поля текстовой области) во время вызова ajax?
Махмудур
1
Я применил это решение немного по-другому: прежде всего я использовал приведенный ниже код в javascript в случае успеха: delayed_autosave(); //get data from textarea/tinymce field jQuery('#publish').data("valid", true).trigger('click'); //publish postБольшое спасибо.
Махмудур
3

Я думаю, что лучший способ сделать это - не ПРЕДОТВРАТИТЬ изменение статуса от того, чтобы оно произошло так сильно, как ПРОЧИТАТЬ его, если оно произойдет. Например: вы перехватываете save_postс действительно высоким приоритетом (так что перехват будет срабатывать очень поздно, а именно после того, как вы сделаете вашу мета-вставку), затем проверяете post_statusтолько что сохраненный пост и обновляете его до ожидающего (или черновика или что угодно) если это не соответствует вашим критериям.

Альтернативная стратегия заключается wp_insert_post_dataв том, чтобы напрямую установить post_status. На мой взгляд, недостатком этого метода является то, что вы еще не вставили postmeta в базу данных, поэтому вам придется обрабатывать его и т. Д. Для проверки, а затем снова обрабатывать, чтобы вставить это в базу данных ... что может привести к большим накладным расходам, ни в производительности, ни в коде.

mor7ifer
источник
В настоящее время я перехватываю save_postприоритет 1, чтобы сохранить мета-поля из метабоксов; то, что вы предлагаете, это иметь второй крюк save_postс приоритетом, скажем, 99? Будет ли это гарантировать целостность? Что если по какой-то причине сработает первый хук, метаданные будут вставлены, а публикация опубликована, а второй хук - нет, так что вы получите недопустимые поля?
englebip
Я не могу вспомнить ситуацию, когда первый хук сработает, но не второй ... какой сценарий, по вашему мнению, может вызвать это? Если вас это беспокоит, вы можете вставить мета поста, проверить мета поста, а затем обновить post_statusфункцию « все в одной», запустив один вызов, если хотите.
mor7ifer
Я разместил свой код в качестве редактирования моего вопроса; Я попытался использовать второй крюк, save_postно это вызывает бесконечный цикл.
englebip
Ваша проблема в том, что вы должны проверять созданный пост. То if( get_post_status( $post_id ) == 'publish' ), что вы хотите использовать, так как вы будете переопределять данные $wpdb->posts, а не данные $_POST[].
mor7ifer
0

Лучший метод может быть 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
источник
-1

Извините, я не могу дать вам прямой ответ, но я помню, что делал нечто подобное совсем недавно, я просто не могу вспомнить, как именно. Я думаю, что я, возможно, сделал это примерно так - как-то так, что у меня было значение по умолчанию, и если человек не изменил его, я поднял это в утверждении if, так что -> if(category==default category) {echo "You didn't pick a category!"; return them to the post creation page; }извините, это не прямой ответ, но надежда это немного помогает.

MIINIIM
источник