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

12 февр. 2012 г., 01:31:44
Просмотры: 26K
Голосов: 21

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

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

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

РЕДАКЦИЯ1: Это хук, который я сейчас использую для сохранения post_meta.

// Сохранение данных метабокса
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;

// Разрешено ли пользователю редактировать запись или страницу?
if ( !current_user_can( 'edit_post', $post->ID ) )
    return;

// ОК, мы прошли аутентификацию: теперь найдем и сохраним данные
// Поместим их в массив для упрощения обработки

//отладка
//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'];

// Добавление значений $events_meta как пользовательских полей

foreach ( $events_meta as $key => $value ) { // Цикл по массиву $events_meta!
    if ( $post->post_type == 'revision' ) return; // Не сохраняем пользовательские данные дважды
    $value = implode( ',', (array)$value ); // Если $value массив, преобразуем в CSV (маловероятно)
    if ( get_post_meta( $post->ID, $key, FALSE ) ) { // Если пользовательское поле уже имеет значение
        update_post_meta( $post->ID, $key, $value );
    } else { // Если пользовательское поле не имеет значения
        add_post_meta( $post->ID, $key, $value );
    }
    if ( !$value ) 
                delete_post_meta( $post->ID, $key ); // Удаляем если пусто
}

}

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 ) {
//проверяем, что метаданные заполнены при публикации записи
//print_r($_POST);

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

    $custom = get_post_custom($post_id);

    //убеждаемся, что обе даты заполнены
    if ( !array_key_exists('_start_timestamp', $custom ) || !array_key_exists('_end_timestamp', $custom )) {
        $post->post_status = 'draft';
        wp_update_post($post);

    }
    //убеждаемся, что начало < конца
    elseif ( $custom['_start_timestamp'] > $custom['_end_timestamp'] ) {
        $post->post_status = 'draft';
        wp_update_post($post);
    }
    else {
        return;
    }
}
}

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

РЕДАКЦИЯ3: Я нашел способ сделать это, используя хук 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 ) {
//проверяем, что метаданные заполнены при публикации записи, иначе возвращаем в черновик
if ( $data['post_type'] != 'event' ) {
    return $data;
}
if ( $postarr['post_status'] == 'publish' ) {
    $custom = get_post_custom($postarr['ID']);

    //убеждаемся, что обе даты заполнены
    if ( !array_key_exists('_start_timestamp', $custom ) || !array_key_exists('_end_timestamp', $custom )) {
        $data['post_status'] = 'draft';
    }
    //убеждаемся, что начало < конца
    elseif ( $custom['_start_timestamp'] > $custom['_end_timestamp'] ) {
        $data['post_status'] = 'draft';
    }
    //все в порядке!
    else {
        return $data;
    }
}

return $data;
}
0
Все ответы на вопрос 5
3
21

Как отметил 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;

    // Имеет ли пользователь право редактировать запись или страницу?
     if ( !current_user_can( 'edit_post', $post->ID ) )
         return;

   // Теперь выполняем проверки для валидации данных. 
   // Обратите внимание, что произвольные поля (отличные от данных в произвольных метабоксах!) 
   // уже будут сохранены.
    $prevent_publish= false;//Установите true, если данные невалидны.
    if ($prevent_publish) {
        // отключаем эту функцию, чтобы избежать бесконечного цикла
        remove_action('save_post', 'my_save_post');

        // обновляем запись, чтобы изменить её статус
        wp_update_post(array('ID' => $post_id, 'post_status' => 'draft'));

        // снова подключаем эту функцию
        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 (isset($_POST['publish'])){
        //получаем текущий статус записи
        $status = get_post_status( $post_id );

        //Запись была 'опубликована', но если она всё ещё черновик, показываем соответствующее сообщение (10).
        if($status=='draft')
            $location = add_query_arg('message', 10, $location);
    }

    return $location;
}

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

13 февр. 2012 г. 18:04:17
Комментарии

Кроме пропущенной точки с запятой в строке с add_query_arg, этот трюк с фильтром redirect_post_location — именно то, что мне было нужно. Спасибо!

MadtownLems MadtownLems
2 апр. 2014 г. 22:04:21

@MadtownLems исправлено :)

Stephen Harris Stephen Harris
3 апр. 2014 г. 03:18:09

Это единственное решение, которое сработало для меня (я не хотел использовать какие-либо хаки с javascript). Ты лучший!

Cogicero Cogicero
21 янв. 2021 г. 05:59:12
2

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

Сначала добавляем необходимый JavaScript:

//AJAX для проверки события перед публикацией
//адаптировано из https://wordpress.stackexchange.com/questions/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("Ошибка: " + response);
                        jQuery("#post").data("valid", false);

                    }
                    //скрываем иконку загрузки, возвращаем кнопку "Опубликовать" в нормальное состояние
                    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() {
//простая проверка безопасности
check_ajax_referer( 'pre_publish_validation', 'security' );

//преобразуем строку полученных данных в массив
//из https://wordpress.stackexchange.com/a/26536/10406
parse_str( $_POST['form_data'], $vars );

//проверяем, что действительно пытаемся опубликовать запись
if ( $vars['post_status'] == 'publish' || 
    (isset( $vars['original_publish'] ) && 
     in_array( $vars['original_publish'], array('Опубликовать', 'Запланировать', 'Обновить') ) ) ) {
    if ( empty( $vars['_start_date'] ) || empty( $vars['_end_date'] ) ) {
        _e('Необходимо указать дату начала и дату окончания');
        die();
    }
    //проверяем, что начало < окончания
    elseif ( $vars['_start_date'] > $vars['_end_date'] ) {
        _e('Дата начала не может быть позже даты окончания');
        die();
    }
    //проверяем, что время также указано для события, которое не длится весь день
    elseif ( !isset($vars['_all_day'] ) ) {
        if ( empty($vars['_start_time'] ) || empty( $vars['_end_time'] ) ) {
            _e('Необходимо указать время начала и время окончания, если событие не длится весь день');
            die();              
        }
        elseif ( strtotime( $vars['_start_date']. ' ' .$vars['_start_time'] ) > strtotime( $vars['_end_date']. ' ' .$vars['_end_time'] ) ) {
            _e('Дата/время начала не может быть позже даты/времени окончания');
            die();
        }
    }
}

//все в порядке, разрешаем отправку
echo 'true';
die();
}

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

17 февр. 2012 г. 19:31:32
Комментарии

Я использовал тот же подход, но пост сохраняется как "Черновик" вместо "Опубликовать", когда функция валидации возвращает true. Не уверен, как это исправить!!!<br/>Также не получаю данные для поля textarea (например, post_content или любых других пользовательских полей типа textarea) во время ajax-запроса?

Mahmudur Mahmudur
10 авг. 2012 г. 06:47:00

Я применил это решение немного иначе: во-первых, использовал следующий код в javascript при успешном выполнении: delayed_autosave(); //получаем данные из поля textarea/tinymce jQuery('#publish').data("valid", true).trigger('click'); //публикуем пост Большое спасибо.

Mahmudur Mahmudur
13 авг. 2012 г. 05:25:05
4

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

Альтернативная стратегия — использовать хук wp_insert_post_data для непосредственной установки статуса записи. Недостаток этого метода, на мой взгляд, в том, что на момент срабатывания хука метаданные ещё не будут сохранены в базу данных, поэтому придётся обрабатывать их дважды: сначала для проверки условий, а затем для вставки в базу... что может привести к значительным накладным расходам — как по производительности, так и по объёму кода.

12 февр. 2012 г. 01:53:44
Комментарии

В настоящее время я подключаю save_post с приоритетом 1 для сохранения метаполей из метабоксов; вы предлагаете тогда использовать второй хук для save_post с приоритетом, скажем, 99? Гарантирует ли это целостность? Что если по какой-то причине сработает первый хук, метаданные будут сохранены и запись опубликована, но второй хук не сработает, и в итоге у нас останутся невалидные поля?

englebip englebip
12 февр. 2012 г. 14:00:03

Я не могу представить ситуацию, когда сработал бы первый хук, но не сработал второй... Какой сценарий может привести к этому? Если вы так сильно переживаете по этому поводу, вы можете сохранить пост-мета, проверить их, а затем обновить post_status в одной функции, работающей от одного хука, если вам так больше нравится.

mor7ifer mor7ifer
12 февр. 2012 г. 15:36:52

Я добавил свой код в редактирование вопроса; я попытался использовать второй хук для save_post, но это вызывает бесконечный цикл.

englebip englebip
12 февр. 2012 г. 21:58:49

Ваша проблема заключается в том, что вы должны проверять созданную запись. Поэтому вам следует использовать if( get_post_status( $post_id ) == 'publish' ), так как вы будете переопределять данные в $wpdb->posts, а не данные в $_POST[].

mor7ifer mor7ifer
12 февр. 2012 г. 23:05:39
0

Лучший метод может быть на JAVASCRIPT:

<script type="text/javascript">
var field_id =  "My_field_div__ID";    // <----------------- ИЗМЕНИТЕ ЭТО

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("Я не могу найти поле с таким ID !!"); }
  else {
      var Enabled_Disabled= document.getElementById(field_id).value;
      if (Enabled_Disabled == "" ) { alert("Поле пустое");   }  else{passed=true;}
  }
  if (!passed) { e.preventDefault();  return false;  }
}
</script>
26 июн. 2015 г. 13:56:55
0
-2

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

12 февр. 2012 г. 03:20:36