問題描述
我有一個自定義的帖子型別 Event 包含一個開始和結束日期/時間自定義欄位 (作為字尾編輯螢幕中的 metaboxes) 。
我想確保一個事件不會被髮布 (或安排),而沒有填寫日期,因為這將導致顯示事件資料的模板的問題 (除了事實,這是一個必要的要求!) 。但是,我希望能夠在準備中不包含有效日期的草稿事件。
我正在想掛鉤 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 );
EDIT2:這是我在儲存到資料庫後試圖用來檢查帖子資料。
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;
}
}
}
主要問題是 another question 中實際描述的一個問題:save_post 中的 wp_update_post()鉤子觸發無限迴圈。
EDIT3:我想到了一種方法,透過掛接 wp_insert_post_data 而不是 save_post 。唯一的問題是現在 post_status 被恢復,但現在有一個誤導性的訊息,表示”Post published” 顯示 (透過將&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;
}
最佳解決方案
正如 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 將我們重定向到了 message 變數現在不正確的 URL 。
要更改它,我們可以使用 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 的 Codex 表明,當已經設定了一個變數時,該函式將替換它 (但是如我所說,我還沒有測試過) 。
次佳解決方案
好的,這是最後我怎麼做到這一點:一個 Ajax 呼叫一個 PHP 函式進行檢查,這種靈感來自於 this answer,並使用了一個來自 question I asked on StackOverflow 的一個聰明的提示。重要的是,我確保只有當我們想要釋出檢查完成時,才能始終儲存草稿,而無需檢查。這最終成為實際阻止該職位釋出的更容易的解決方案。它可能會幫助別人,所以我寫在這裡。
首先,新增必要的 Javascript:
//AJAX to validate event before publishing
//adapted from 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("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 https://wordpress.stackexchange.com/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()的錯誤訊息,並且表單未提交。
參考文獻
注:本文內容整合自 Google/Baidu/Bing 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。