How to make open/closed and hidden/shown metaboxes status saved on a per-post basis?

The Main Problem:

The main problem here is that in the closing-, hiding- and ordering- ajax calls, there’s no post ID sent with the payload. Here are two form data examples:

1) action:closed-postboxes
closed:formatdiv,tagsdiv-post_tag,trackbacksdiv,authordiv
hidden:slugdiv
closedpostboxesnonce:8723ee108f
page:post

2) action:meta-box-order
_ajax_nonce:b6b48d2d16
page_columns:2
page:post
order[side]:submitdiv,formatdiv,categorydiv,tagsdiv-post_tag,postimagediv
order[normal]:postexcerpt,postcustom,trackbacksdiv,commentsdiv,authordiv
order[advanced]:

We could get around this by using another custom ajax call.

We could of course just hook into the save_post hook and modify the data each time the post is saved. But that’s not the normal UI experience, so that’s not consider here

There’s another non-elegant solution available with PHP, described here below:

A Non Javascript Solution:

The question is where to store the data? As user meta data, post meta data or maybe in a custom table?

Here we store it as user meta data and take the closing of post meta boxes as an example.

When the closedpostboxes_post meta value is updated, we save it into the closedpostboxes_post_{post_id} meta value as well.

Then we hijack the fetching of closedpostboxes_post to override it with the corresponding meta value based on user id and post id.

a) Updating during the closed-postboxes ajax action:

We can fetch the post ID, through the wp_get_referer() and then use the
handy url_to_postid() function. I first knew about this “funny” function after reading the answer from @s_ha_dum, few months ago 😉 Unfortunately the function doesn’t recognize ?post=123 GET variables, but we can do a little trick by just changing it to p=123 to get around it.

We can hook into updated_user_meta, that’s fired just after the user meta data for closedpostboxes_post has been updated:

add_action( 'updated_user_meta',                           
    function ( $meta_id, $object_id, $meta_key, $_meta_value )
    {
        $post_id = url_to_postid( str_replace( 'post=", "p=', wp_get_referer() ) );
        if( 'closedpostboxes_post' === $meta_key && $post_id > 0 )
            update_user_meta( 
                $object_id, 
                'closedpostboxes_post_' . $post_id, 
                $_meta_value 
            );
    }
, 10, 4 );

b) Fetching data:

We can hook into the get_user_option_closedpostboxes_post hook to modify the data fetched from the closedpostboxes_post user meta:

add_filter( 'get_user_option_closedpostboxes_post',
    function ( $result, $option, $user )
    {
        $post_id = filter_input( INPUT_GET, 'post', FILTER_SANITIZE_NUMBER_INT );
        $newresult = get_user_option( 'closedpostboxes_post_'. $post_id , $user->ID );
        return ( $newresult ) ? $newresult : $result;
    }
, 10, 3 );

We might also want to think about the case where there’s no post based closedpostboxes_post_{post_id} available. So it will use the last saved settings from closedpostboxes_post. Maybe you would want to have it all open or all closed, in that default case. It would be easy to modify this behaviour.

For other custom post types we can use the corresponding closedpostboxes_{post_type} hook.

Same should be possible for the ordering and hiding of metaboxes with the metaboxhidden_{post_type} and meta-box-order_{post_data} user meta.

ps: sorry for this too long weekend answer, since they should always be short & jolly 😉

Leave a Comment