Until now, we’ve known what custom fields is and how to use the functions provided by WordPress to work with custom fields. You’ve prepared everything needed to develop practical applications. But wait, before embarking on doing something new, you had better dig a bit deeper to have thorough understand custom fields' nature. It’s time to find out an answer to the question: “What really happens with custom fields when I click Save post?”. The two coming posts will give you the answers.

Two things are going to happen after clicking Save post. First off, your data will be processed by PHP either directly via functions or indirectly via filters, so the final values saved in the database may be different from what you enter. This post will help shed a light on the issue. Next, your data of custom fields is stored in the database, but this issue will be explained in the next post.

Take a look at the code in plugin “Hello Custom Fields” we have completed in the previous post.

function hcf_save( $post_id ) {
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }
    if ( $parent_id = wp_is_post_revision( $post_id ) ) {
        $post_id = $parent_id;
    }
    $field_list = [
        'hcf_author',
        'hcf_published_date',
        'hcf_price',
    ];
    foreach ( $field_list as $fieldName ) {
        if ( array_key_exists( $fieldName, $_POST ) ) {
            update_post_meta(
                $post_id,
                $fieldName,
                sanitize_text_field( $_POST[ $fieldName ] )
            );
        }
    }
}
add_action( 'save_post', 'hcf_save' );

We’re going to take a closer look at WordPress code and examine every function to understand the data flow when a post is submitted. Then you will understand why our plugin includes these lines of code.

I’m going to divide this data flow into 2 phases, including:

  • Before the plugin calls update_post_meta
  • After the plugin calls update_post_meta

Before calling update_post_meta

Loading the post editor

In this step. WordPress will create draft data and trigger action save_post.

Let’s start at Add New Post page (wp-admin/post-new.php). Line 70 in this file is as follows.

$post = get_default_post_to_edit( $post_type, true );

This line of code aims to create a new object post with default values as initial data. The first parameter is the post type. The second one is to determine whether to save this post into the database or not. Since the second parameter of the above code is true, the wp_insert_post function is called:

function get_default_post_to_edit( $post_type = 'post', $create_in_db = false ) {
    ...
    if ( $create_in_db ) {
        $post_id = wp_insert_post( array(
      'post_title' => __( 'Auto Draft' ),
      'post_type' => $post_type,
      'post_status' => 'auto-draft' ));
    ...

If you exit the Add New Post page and return to the All Posts page within less than 60 seconds, you won’t see any new post. However, there is still a new post in auto-draft status in the database. If you keep doing this, there will be a lot of auto drafts in your database. Don’t worry, they will be automatically deleted within 7 days.

The hook save_post will be triggered at the end of the wp_insert_post function:

function wp_insert_post( $postarr, $wp_error = false ) {
    ...
    do_action( 'save_post', $post_ID, $post, $update );
    ...
    return $post_ID;
}

At this time, the hcf_save function in our plugin will be called (because we’ve attached it to the hook save_post). However, const DOING_AUTOSAVE is not defined, so the function will continue running, which is redundant because users haven’t had submitted any data on custom fields. What is more, these auto draft posts will be deleted within 7 days, but metadata will not. This leads to a great amount of trash in the database.

We can improve the plugin by skipping posts in auto-draft status.

function hcf_save( $post_id ) {
    ...
    if (get_post_status($post_id) === 'auto-draft') {
        return;
    }
    ...
}
FYI: The Meta Box plugin has an option to let you save custom fields along with the autosave featured of Meta Box. Checkout the autosave setting.

Revision and Heartbeat

In case you’ve had no idea what the heartbeat is, you can find more information here: Heartbeat API.

By default, after 60 seconds, the heartbeat function will send a request to the server. At the screen to publish posts, if you change the values of the posts’ basic fields, the heartbeat will attach their data to post. On the server, WordPress attaches the heartbeat_autosave function to action heartbeat_received. Then the heartbeat_autosave function will call the wp_autosave function in order to update or create revisions of posts.

The wp_autosave function is primarily as the following.

function wp_autosave( $post_data ) {
    // Back-compat
    if ( ! defined( 'DOING_AUTOSAVE' ) )
        define( 'DOING_AUTOSAVE', true );
    ...
    if ( ! wp_check_post_lock( $post->ID )
      && get_current_user_id() == $post->post_author
      && ( 'auto-draft' == $post->post_status
       || 'draft' == $post->post_status ) ) {
// Drafts and auto-drafts are just overwritten by autosave for the same user if the post is not locked
        return edit_post( wp_slash( $post_data ) );
    } else {
// Non drafts or other users drafts are not overwritten. The autosave is stored in a special post revsion.
        return wp_create_post_autosave( wp_slash( $post_data ) );
    }
}

If a post is looked or neither draft nor auto draft, it won’t be edited. Instead, a revision will be created based on that post.

Two functions edit_post and wp_create_post_autosave will both call the wp_update_post function, and wp_update_post calls the hook save_post. However, the heartbeat doesn’t send the data of custom fields. It only includes the data of default basic fields of posts. That’s why the hcf_save function won’t update custom fields if const DOING_AUTOSAVE is defined.

FYI: If you want the custom fields value to be saved in revision, so you can track the changes of custom fields, then you should use MB Revision plugin.

Publish Posts

When the Add New Post page loads successfully, there is a form with its action /wp-admin/post.php, which means it will submit data to the /wp-admin/post.php page.

work flow of storing custom fields in WordPress

When you click on Publish button, the following code in the /wp-admin/post.php file will run:

check_admin_referer('update-post_' . $post_id);

$post_id = edit_post();

// Session cookie flag that the post was saved
if ( isset( $_COOKIE['wp-saving-post'] ) && $_COOKIE['wp-saving-post'] === $post_id . '-check' ) {
    setcookie( 'wp-saving-post', $post_id . '-saved', time() + DAY_IN_SECONDS, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, is_ssl() );
}

redirect_post($post_id); // Send user on their way while we keep working

exit();

The function edit_post will run, and the hook save_post will be called.

function edit_post( $post_data = null ) {
    ...
    update_post_meta( $post_ID, '_edit_last', get_current_user_id() );

    $success = wp_update_post( $post_data );
    ...
    return $post_ID;
}

However, this time, it will include the data from custom fields in the $_POST variable, so the hcf_save function will update the values of custom fields by calling the update_post_meta function.

After calling update_post_meta

The update_post_meta function is as the following:

function update_post_meta( $post_id, $meta_key, $meta_value, $prev_value = '' ) {
    // Make sure meta is added to the post, not a revision.
    if ( $the_post = wp_is_post_revision($post_id) )
        $post_id = $the_post;

    $updated = update_metadata( 'post', $post_id, $meta_key, $meta_value, $prev_value );
    if ( $updated ) {
        wp_cache_set( 'last_changed', microtime(), 'posts' );
    }
    return $updated;
}

So this function helps us check revisions. It is now possible to remove these lines of code in the hcf_save function.

function hcf_save( $post_id ) {
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }
    if (get_post_status($post_id) === 'auto-draft') {
        return;
    }
    $field_list = [
        'hcf_author',
        'hcf_published_date',
        'hcf_price',
    ];
    foreach ( $field_list as $fieldName ) {
        if ( array_key_exists( $fieldName, $_POST ) ) {
            update_post_meta(
                $post_id,
                $fieldName,
                sanitize_text_field( $_POST[ $fieldName ] )
            );
        }
    }
}
add_action( 'save_post', 'hcf_save' );

The update_post_meta function does not have much logic. The update_metadata is more interesting than it. WordPress supports metadata for different types of objects like post, comment and user and wishes to standardize APIs working with metadata into a group of functions which is called Metadata API. In brief, Metadata API can be utilized to create metadata for any table as long as you create a table that has the same structure as the tables: wp_commentmeta, wp_postmeta, wp_usermeta.

Back to our data flow, the update_metadata function calls many functions to filter and standardize the data provided by users. Many different filters are also used in each of those function. There might be a difference in your data. In case of any error, you should re-check this step first.

After having clean data, WordPress will update the database. Prior to doing this, update_metadata function will call a filter which is update_{$meta_type}_metadata. If the returned value is true, the function won’t continue updating the database. Instead, it will return the value of that filter. Probably, WordPress intends to allow some kinds of plugins to override the logic of updating database. This is a noteworthy point when debugging issues related to storing custom fields.

Conclusion

This post walks us along the data flow of custom fields to understand what happens when a custom field goes from a form in the web browser to the database as its final destination. Thanks to that, we understand the meaning of the code we wrote in the plugin and know how to improve it. Moreover, we also learn that hooks can change the data of custom fields so that we can better debug.

The next post will deal with the database structure of metadata, its grounds, strengths, weaknesses, and solutions.

Leave a Reply

Your email address will not be published. Required fields are marked *