don’t publish custom post type post if a meta data field isn’t valid

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.

Read More

Many thanks,
Dasha

Related posts

Leave a Reply

4 comments

  1. 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.

  2. 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..

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