I have a custom post type Event
that contains a starting and ending date/times custom fields (as metaboxes in the post edit screen).
I would like to make sure that an Event cannot get published (or scheduled) without the dates being filled, as that will cause problems with the templates displaying the Event data (besides the fact that it is a necessary requirement!). However, I would like to be able to have Draft events that do not contain a valid date while they are in preparation.
I was thinking of hooking save_post
to do the checking, but how can I prevent the status change from happening?
EDIT1: This is the hook I’m using now to save the 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: and this is what I’m trying to use to check the post data after saving to the database.
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;
}
}
}
The main issue with this is a problem that was actually described in another question: using wp_update_post()
within a save_post
hook triggers an infinite loop.
EDIT3: I figured a way to do it, by hooking wp_insert_post_data
instead of save_post
. The only problem is that now the post_status
is reverted, but now a misleading message saying “Post published” shows up (by adding &message=6
to the redirected URL), but the status is set to Draft.
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;
}
As m0r7if3r pointed out, there is no way of preventing a post from being published using the
save_post
hook, since the by the time that hook is fired, the post is already saved. The following, however, will allow you to revert the status without usingwp_insert_post_data
and without causing an infinite loop.The following is not tested, but should work.
I’ve not checked, but looking at the code, the feedback message will display the incorrect message that the post was published. This is because WordPress redirects us to an url where the
message
variable is now incorrect.To change it, we can use the
redirect_post_location
filter:To summarise the above redirect filter: If a post is set to be published, but is still a draft then we alter the message accordingly (which is
message=10
). Again, this is untested, but should work. The Codex of theadd_query_arg
suggests that when a variable is already it set, the function replaces it (but as I say, I haven’t tested this).OK, this is finally how I ended up doing it: an Ajax call to a PHP function that does the checking, sort of inspired by this answer and using a clever tip from a question I asked on StackOverflow. Importantly, I make sure that only when we want to Publish the checking is done, so that a Draft can always be saved without the checking. This ended up being the easier solution to actually prevent the publication of the post. It might help someone else, so I wrote it up here.
First, add the necessary Javascript:
Then, the function that handles the checking:
This function returns
true
if everything is fine, and submits the form to publish the post by the normal channel. Otherwise, the function returns an error message that is shown as analert()
, and the form is not submitted.I think that the best way to go about this is not to PREVENT the status change from happening so much as it is to REVERT it if it does. For example: You hook
save_post
, with a really high priority (so that the hook will fire very late, namely after you do your meta insert), then check thepost_status
of the post that’s just been saved, and update it to pending (or draft or whatever) if it doesn’t meet your criteria.An alternate strategy would be to hook
wp_insert_post_data
to set the post_status directly. The disadvantage to this method, as far as I’m concerned, is that you will not have inserted the postmeta into the database yet, so you will have to process it, etc in place to do your checks, then process it again to insert it into the database…which could become a lot of overhead, either in performance or in code.Best method may be JAVASCRIPT:
Sorry I cant give you a straight up answer but I do recall doing something similar very recently I just cant remember exactly how. I think I maybe did it around about way – something like I had it being a default value and if the person hadn’t changed it I picked this up in an if statement so ->
if(category==default category) {echo "You didn't pick a category!"; return them to the post creation page; }
sorry this isn’t a straight up answer but hope it helps a bit.