14

I have a custom post type (CPT) called event. I have a meta box for the type with several fields. I would like to validate some fields before publishing an event. For example, if an event's date is not specified I would like to display an informative error message, save the event for future editing, but prevent that event from being published. Is 'pending' status for an CPT post without all necessary info the right way to treat it?

What's the best practice to do CPT fields validation and prevent a post from being published, but save it for future editing.

Many thanks, Dasha

1
  • Gentle nudge to remind you this question still needs an accepted answer.. ;) If neither answer addressed your question please consider updating your question with additional comments detailing what has not been addressed(or where you may need help, if applicable).
    – t31os
    Commented Jul 28, 2011 at 9:14

4 Answers 4

15

You can stop the post from saving all together with minor JQuery hacks and validate the fields before saving on the client side or server side with ajax:

first we add our JavaScript to capture the submit/publish event and use it to submit our own ajax function before the actual submit:

 add_action('wp_print_scripts','my_publish_admin_hook');

function my_publish_admin_hook(){
if (is_admin()){
        ?>
        <script language="javascript" type="text/javascript">
            jQuery(document).ready(function() {
                jQuery('#post').submit(function() {

                    var form_data = jQuery('#post').serializeArray();
                    form_data = jQuery.param(form_data);
                    var data = {
                        action: 'my_pre_submit_validation',
                        security: '<?php echo wp_create_nonce( 'pre_publish_validation' ); ?>',
                        form_data: form_data
                    };
                    jQuery.post(ajaxurl, data, function(response) {
                        if (response.indexOf('True') > -1 || response.indexOf('true') > -1 || response === true ||  response) {
                            jQuery('#ajax-loading').hide();
                            jQuery('#publish').removeClass('button-primary-disabled');
                            return true;
                        }else{
                            alert("please correct the following errors: " + response);
                            jQuery('#ajax-loading').hide();
                            jQuery('#publish').removeClass('button-primary-disabled');
                            return false;
                        }
                    });
                    return false;
                });
            });
        </script>
        <?php
    }
}

then we create the function to do the actual validation:

add_action('wp_ajax_my_pre_submit_validation', 'pre_submit_validation');
function pre_submit_validation(){
    //simple Security check
    check_ajax_referer( 'pre_publish_validation', 'security' );

    //do your validation here
    //all of the form fields are in $_POST['form_data'] array
    //and return true to submit: echo 'true'; die();
    //or your error message: echo 'bal bla bla'; die();
}

you can always change it up a bit to do the validation only for your post type by adding a conditional check to my_publish_admin_hook function for your post type and to validate on the client side but i prefer on the server side.

6
  • Is there no server-side way to do this?
    – Jeff
    Commented Jul 4, 2014 at 21:58
  • 1
    This is a server side way to do it.
    – Bainternet
    Commented Jul 5, 2014 at 4:59
  • 1
    Maybe I am misunderstanding something. It looks like you're just using PHP to render JavaScript which does the validation. That isn't server side validation. I'm not really understanding how the pre_submit_validation fits in.
    – Jeff
    Commented Jul 7, 2014 at 16:42
  • 2
    This is still client-side validation, even if it's using AJAX to do the validation. The client has to run the JavaScript in the first place in order for any validation to take place. However... I still found this answer useful for pre-submission validation. Thanks!
    – cr0ybot
    Commented Aug 15, 2016 at 18:35
  • 1
    Sad if there is no wp-native way to do such a typical thing
    – Fanky
    Commented Oct 24, 2019 at 12:02
8

There are two steps to the method: first, a function to save your custom metabox field data (hooked to save_post), and second, a function to read that new post_meta (which you just saved), validate it, and modify the result of saving as necessary (also hooked to save_post, but after the first). The validator function, if validation fails, actually changes the post_status right back to "pending", effectively preventing the post from being published.

Since the save_post function gets called a lot, each function has checks to only execute when the user means to publish, and only for your custom post type (mycustomtype).

I also typically add some custom notice messages to help the user know why their post didn't publish, but those got a bit complicated to include here...

I haven't tested this exact code, but it's a simplified version of what I've done in large-scale custom post type setups.

add_action('save_post', 'save_my_fields', 10, 2);
add_action('save_post', 'completion_validator', 20, 2);

function save_my_fields($pid, $post) {
    // don't do on autosave or when new posts are first created
    if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || $post->post_status == 'auto-draft' ) return $pid;
    // abort if not my custom type
    if ( $post->post_type != 'mycustomtype' ) return $pid;

    // save post_meta with contents of custom field
    update_post_meta($pid, 'mymetafield', $_POST['mymetafield']);
}


function completion_validator($pid, $post) {
    // don't do on autosave or when new posts are first created
    if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || $post->post_status == 'auto-draft' ) return $pid;
    // abort if not my custom type
    if ( $post->post_type != 'mycustomtype' ) return $pid;

    // init completion marker (add more as needed)
    $meta_missing = false;

    // retrieve meta to be validated
    $mymeta = get_post_meta( $pid, 'mymetafield', true );
    // just checking it's not empty - you could do other tests...
    if ( empty( $mymeta ) ) {
        $meta_missing = true;
    }

    // on attempting to publish - check for completion and intervene if necessary
    if ( ( isset( $_POST['publish'] ) || isset( $_POST['save'] ) ) && $_POST['post_status'] == 'publish' ) {
        //  don't allow publishing while any of these are incomplete
        if ( $meta_missing ) {
            global $wpdb;
            $wpdb->update( $wpdb->posts, array( 'post_status' => 'pending' ), array( 'ID' => $pid ) );
            // filter the query URL to change the published message
            add_filter( 'redirect_post_location', create_function( '$location','return add_query_arg("message", "4", $location);' ) );
        }
    }
}

For multiple metabox fields, just add more completion markers and retrieve more post_meta and do more tests..

2

you have to check/validate your meta field value on ajax i.e. when user hit "Publish/Update" button. Here i am validating woocommerce product having meta field "product_number" for empty value.

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 == 'product' ){
        ?>
        <script language="javascript" type="text/javascript">
            (function($){
                jQuery(document).ready(function() {

                    jQuery('#publish').click(function() {
                        if(jQuery(this).data("valid")) {
                            return true;
                        }

                        //hide loading icon, return Publish button to normal
                        jQuery('#publishing-action .spinner').addClass('is-active');
                        jQuery('#publish').addClass('button-primary-disabled');
                        jQuery('#save-post').addClass('button-disabled');

                        var data = {
                            action: 'ep_pre_product_submit',
                            security: '<?php echo wp_create_nonce( "pre_publish_validation" ); ?>',
                            'product_number': jQuery('#acf-field-product_number').val()
                        };
                        jQuery.post(ajaxurl, data, function(response) {

                            jQuery('#publishing-action .spinner').removeClass('is-active');
                            if ( response.success ){
                                jQuery("#post").data("valid", true).submit();
                            } else {
                                alert("Error: " + response.data.message );
                                jQuery("#post").data( "valid", false );

                            }
                            //hide loading icon, return Publish button to normal
                            jQuery('#publish').removeClass('button-primary-disabled');
                            jQuery('#save-post').removeClass('button-disabled');
                        });
                        return false;
                    });
                });
            })(jQuery);
        </script>
        <?php
    }
}

After that add ajax handler function,

add_action('wp_ajax_ep_pre_product_submit', 'ep_pre_product_submit_func');
function ep_pre_product_submit_func() {
    //simple Security check
    check_ajax_referer( 'pre_publish_validation', 'security' );

    if ( empty( $_POST['product_number'] ) || empty( $_POST['file_attachment'] ) ) {
         $data = array(
            'message' => __('Please enter part number and specification document.'),
        );
        wp_send_json_error( $data );
    }
    wp_send_json_success();
}
0

Just wanted to add that to read the post variables, using Bainternet's solution, you'll have to parse the string in $_POST['form_data'] using PHP parse_str function (just to save you some research time).

$vars = parse_str( $_POST['form_data'] );

Then you can access each variable just using $varname. For example, if you have a meta field called "my_meta" you would access it like this:

$vars = parse_str ( $_POST['form_data'] ) 
if ( $my_meta == "something" ) { // do something }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.