How to disable broken save_post callbacks

This tutorial is part of our MultilingualPress 2 documentation. In case you are using the newer version 3, please switch to MultilingualPress 3.

Many plugins are not multisite aware. This is rarely a problem, because each site in a network works almost like a normal site in a single-site installation. But sometimes … things go really wrong.

There is a hook, the action save_post, that can be called multiple times when a post is saved: one time for each site. Many plugins are not aware of this, they run their own code on every call to save_post without a check for the site context. The result is that they are either deleting user data on the other sites, or they overwrite existing data.

This happens when MultilingualPress updates the post translations.

Site A save_post - MultilingualPress {
    creates or updates translations:
    - switch_to_blog( Site B) -> save_post
    - switch_to_blog( Site C) -> save_post
    - switch_to_blog( Site D) -> save_post
    
    return to Site A
}

MultilingualPress removes all POST data before the other save_post actions are called, and it restores it when it switches back. Normal plugins use a nonce as a basic security check before they try to save anything. Since we remove the nonce along with the POST data, they will not do anything. So far, so good.

Unfortunately, some plugin author don’t use nonces. They just try to save their data, without proper context checks. There is nothing we can do about that.

But you can. You have to find the code (function or class method) that is registered for the save_post action and then remove it earlier. There is another hook that runs right before save_post: the filter wp_insert_post_data. You can hook your own custom callback to that filter, check if the current context is in a switched site and then remove the “evil” callback. Don’t forget to return the filtered value, that’s necessary for filters.

Here is an example for the Custom Sidebars plugin:

add_filter( 'wp_insert_post_data', function( $data ) {

    if ( is_multisite()
        && ms_is_switched()
        && class_exists( 'CustomSidebarsEditor' )
    ) {
        $cse = CustomSidebarsEditor::instance();
        remove_action( 'save_post', array( $cse, 'store_replacements' ) );
    }

    return $data;
});

If you are a plugin or theme author and want to be sure that your code is safe to run in a multisite: please contact me. I,or one of my colleagues, will look at it and tell you what needs to be changed.