Tutorial

Sharing Sidebars Across a Multisite Network

Sidebars are great.  Multisite is great.  Doing something once so that it can be shared across an entire network is great, too.

So, it should stand to reason that sharing a specific sidebar, or set of sidebars, across an entire multisite network should be just as great (and easy), right?

Well, if it were, we wouldn’t be here right now.

I know what you may be thinking…

Just use switch_to_blog(), ya dingus!

Oh, how I wish it were that easy!  That was our first guess as well, but it unfortunately didn’t do the trick.  Apparently, switch_to_blog() is a trick that does not work when attempting to share a sidebar across a network.  Total bummer.

In our situation, though, we had a set of sidebars in our footer that we wanted the client to be able to manage but also share across every site within their network.  So, hardcoding the widgets in place wouldn’t have been a proper solution; neither would have been telling the client that they would need to manage three sidebars on 300+ sites.  Ouch.

We had our task: allow a set of sidebars to be displayed across the sites in a multisite network.

We had our problem: switch_to_blog() doesn’t work for this, so we needed to figure out another way to accomplish our goal.

Now we have our solution.

The first thing we want to do is build the output of our sidebars. That seems kind of important, right? Can’t see some sidebars if we’re not telling the sidebars to display. We don’t want to just display the sidebars and be done with it, though. What we want to do is store our sidebars in a transient. This transient will be deleted whenever a widget is updated in one of our three sidebars (you’ll see this later, be patient!). In order to store our set of sidebars as a variable, we need to wrap everything in an output buffer.

/**
 * Generate sidebar for footer across all sites
 */
add_action( 'init', 'wds_footer_widgets_all_sites' );
function wds_footer_widgets_all_sites() {

    if ( ! is_main_site() )
        return;

    if ( ! ( $wds_footer_widgets = get_site_transient( 'wds_footer_widgets' ) ) ) {
        // start output buffer
        ob_start();

        // Display our footer sidebars
        if ( ! dynamic_sidebar( 'sidebar-footer-1' ) ) :
                endif; // end sidebar widget area

        if ( ! dynamic_sidebar( 'sidebar-footer-2' ) ) :
        endif; // end sidebar widget area

        if ( ! dynamic_sidebar( 'sidebar-footer-3' ) ) :
        endif; // end sidebar widget area

        // grab the data from the output buffer and put it in our variable
        $wds_footer_widgets = ob_get_contents();
        ob_end_clean();

        // Put sidebar into a transient for 4 hours
        set_site_transient( 'wds_footer_widgets', $wds_footer_widgets, 4*60*60 );
    }
}

Next comes our theme’s footer. This is a bit more straight forward – we want to get the site transient that we established in our functions file above, then display its contents.

// Display our stored footer widgets
$show_footer_sidebar = get_site_transient( 'wds_footer_widgets' );
echo $show_footer_sidebar;

Now we should have our sidebars displaying, but we also want some additional magic to happen. We need to clear our transient when a widget is updated or saved in one of our three footer sidebars.  The first step of this process is to enqueue some scripts that we’re going to be using. I’m doing this in a separate file housing all of our AJAX-related commands, ajax-functions.php in our theme’s /inc/ directory.

/**
 * Enqueue and localize our scripts
 */
add_action( 'admin_enqueue_scripts', 'wds_enqueue_ajax_scripts' );
function wds_enqueue_ajax_scripts() {
    global $current_screen;

    // Only register these scripts if we're on the widgets page
    if ( $current_screen->id == 'widgets' ) {
        wp_enqueue_script( 'wds_ajax_scripts', get_stylesheet_directory_uri() . '/js/admin-widgets.js', array( 'jquery' ), 1, true );
        wp_localize_script( 'wds_ajax_scripts', 'wds_AJAX', array( 'wds_widget_nonce' => wp_create_nonce( 'widget-update-nonce' ) ) );
    }
}

In the same file, we want to create the AJAX function that runs when updating our sidebars. This function checks to make sure our nonce is present; if it is, it deletes the site transient.

/**
 * Register our AJAX call
 */
add_action( 'wp_ajax_wds-reset-transient', 'wds_ajax_wds_reset_transient', 1 );

/**
 * AJAX Helper to delete our transient when a widget is saved
 */
function wds_ajax_wds_reset_transient() {

    // Delete our footer transient.  This runs when a widget is saved or updated.  Only do this if our nonce is passed.
    if ( ! empty( $_REQUEST['wds-widget-nonce'] ) )
        delete_site_transient( 'wds_footer_widgets' );
}

We’ll need a JavaScript file to handle some back-end functionality, and we’ll need to localize the script so we can interact with that file.  We’re passing a nonce in our script as well; this will ensure that our widget form is being updated properly.

Finally, we want to create the AJAX that is going to listen for an updated widget. With jQuery first, we’re listening for a click on the remove, close, or save links on individual widgets within one of our three specific sidebars. If one of those links is clicked and we’re in one of our specified sidebars, we want to run our AJAX function.

jQuery(document).ready(function($){

    function wds_reset_footer_transient() {

        // Run our AJAX call to delete our site transient
        $.ajax({
            type : 'post',
            dataType : 'json',
            url : ajaxurl,
            data : {
                'action' : 'wds-reset-transient',
                'wds-widget-nonce' : wds_AJAX.wds_widget_nonce
            },
            error: function ( xhr, ajaxOptions, thrownError ) {
                console.log( thrownError );
                console.log( ajaxOptions );
            }
        });
    }

    // If one of our update buttons is clicked on a single widget
    $( '.widgets-holder-wrap' ).on( 'click', '.widget-control-remove, .widget-control-close, .widget-control-save', function() {

        // Get our parent, or sidebar, ID
        var widget_parent_div = $(this).parents().eq(5).attr( 'id' );

        // And if our parent div ID, or our sidebar ID, is one of the following
        if ( widget_parent_div == 'sidebar-footer-1' || widget_parent_div == 'sidebar-footer-2' || widget_parent_div == 'sidebar-footer-3' ) {

            // Run our function
            wds_reset_footer_transient();
        }
    });
});

And that’s about it! Now you can manage a set of sidebars on the main site within your network and pass those sidebars to every site within your network. One set of widgets to manage for an entire network of sites. It wasn’t the solution that jumped out at us at first, but it’s the one we found and it’s the one that works for us. We’re pretty happy with the way it turned out, and we think the client is going to be happy only having to edit their widgets once and have those changes visible on 300+ sites.

Comments

27 thoughts on “Sharing Sidebars Across a Multisite Network

  1. I’ve had to do quite a bit of wrestling with sidebars and widgets recently, and I found an unfortunate condition inside of wp_get_sidebars_widgets():

    // If loading from front page, consult $_wp_sidebars_widgets rather than options
    // to see if wp_convert_widget_settings() has made manipulations in memory.
    if ( !is_admin() ) {
        if ( empty($_wp_sidebars_widgets) )
            $_wp_sidebars_widgets = get_option('sidebars_widgets', array());
        $sidebars_widgets = $_wp_sidebars_widgets;
    } else {
        $sidebars_widgets = get_option('sidebars_widgets', array());
    }

    So you can see here that after the sidebar widgets are initially loaded, they will persist in that private global variable $_wp_sidebars_widgets.

    This was a problem for Widget Customizer because it needed to be able to override the sidebars’ widgets on the fly. This is what we did to get around the above behavior:

    add_filter( 'sidebars_widgets', function ( $sidebars_widgets ) {
        $sidebars_widgets = get_option( 'sidebars_widgets' );
        unset( $sidebars_widgets['array_version'] );
        return $sidebars_widgets;
    } );

    Would this workaround also allow switch_to_blog() to be able to render sidebars from other blogs on the network?

    1. You would need to include all of the pieces as outlined in the post. The JS function you’re referencing contains the AJAX which deletes the transient when a widget in one of our specified sidebars is saved/updated.

  2. I’m not entirely certain about where each chunk of code is supposed to go. Does chunk 1 go in the parent site’s function file, and then chunk 2 goes in the child site’s footer? My particular network has three sites (a parent and two children) that all use child themes of a parent theme. (none of the sites use the parent theme).

    1. Other than chunk 2 (the get_site_transient() function used to display the sidebar), everything could go in your functions file. Typically you would want to make these changes to your child themes, not your parent theme; if you make changes to your parent theme’s functions file then update that theme, you would lose those updates.

      1. Corey, what if I’m using a different theme for all sites except the main site. Does this code still go into the sub-site theme?

      2. Hey Dan – it’s been a while since I originally wrote this code, so I would probably do things a little bit differently and include this in /mu-plugins/ or as a standard plugin for ease of troubleshooting and updating. This also allows you to change the theme of your main site and still retain the code.

        Once your code is in place in a more global place like /mu-plugins/ or /plugins/, you should still be able to call everything in the second function the same way. You’re still going to look for the site transient, which is set for the main site in the first function, and return the information found.

        It may be time to take a look at this and give it a refresh, though, so thanks for the comment and question!

  3. This is working great for me. Thank you.
    There is an abandoned plugin on .org called Multisite Widgets 😉

    1. I think we came across that plugin when the issue initially arose, but chose to roll our own solution since it had fallen by the wayside as far as updates were concerned.

  4. By setting the transient to 4 hours, is there not a chance that some low traffic websites might show up without the sidebars? EG if no one has visited the main site in over 4 hours and then someone directly visits one of the sub sites, the transient would not exist and thus they wouldn’t see the sidebars. Would it not be better to omit the $expiration value as default is 0 (never expires)?

    Great solution to a tricky problem. Thanks for posting.

    1. The transient will be deleted when you updated widgets, so once you either add those initially or update them down the road, your transient will be deleted.

      You can also change the time to as long or as short as you’d like; 4 hours was used for this example, but you can certainly change that up.

  5. Hello, followed your tutorial and everything is working well except for one thing. For some reason I need to click “Save” twice on my widgets in order for them to update on the frontend. Also, if I delete a widget, then I need to click save again on one of the remaining widgets to get it to update on the frontend.

    I modified your code a little bit so that I could set_site_transient() variables for each of my sidebars as opposed to one transient for all of the sidebars. Could this be the issue?

    Any ideas on what else could be causing this?

    Thanks

      1. Hi Sarah – this code is about 4 years old at this point and there are probably portions of it that could be reworked or updated. I haven’t revisited the code since the blog post was originally written, but I’m sure there are portions that are either buggy or may not work with updates to WordPress core over the years.

  6. Hey! Thank you for putting this out there! Would you know if this code is still valid? I’ve been messing with it all day trying to copy it properly and all I get isstring(0) “”

    in my footer. If you have any ideas about what I’m doing wrong, please let me know!

    Thank you so much in advance!

    1. It sure has been a while since this post went live and I haven’t had to replicate this functionality again since; it might be a good topic for an updated blog post, though, so I’ll add that to my list to revisit!

    1. Thanks for this solution! The original blog post got me started, but I didn’t like that I had to hit the main site in order to store the sidebar. Your code was even closer, but I needed to add two lines to get it to work.

      * After setting the transient on line 11, I echo $markup. Without this, the markup wasn’t displaying on subsites on first load.

      * After line 22, I had to echo the result for the wp_remote_get, which appears to just be any empty page anyway so nothing shows. But this is what kicks off wp_multisite_sidebar_save.

  7. The following code worked for me…

    [code language=”php”]
    add_action( ‘wpmu_new_blog’, ‘side_bar_ini’ );

    function side_bar_ini ($blog_id){
    switch_to_blog($blog_id);
    // the name is (probably) the slug/id
    $new_active_widgets = array (
    ‘sidebar-1’ => array (
    ‘categories-2’,
    ‘search-2’,
    ‘recent-posts-2’
    )
    );

    // save new widgets to DB
    update_option(‘sidebars_widgets’, $new_active_widgets);

    restore_current_blog();
    }
    [/code]

  8. Creating your own Widget_Factory (less than 50 lines) you could share any widget dragged on the main site on every network site, on every sidebar

Have a comment?

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

accessibilityadminaggregationanchorarrow-rightattach-iconbackupsblogbookmarksbuddypresscachingcalendarcaret-downcartunifiedcouponcrediblecredit-cardcustommigrationdesigndevecomfriendsgallerygoodgroupsgrowthhostingideasinternationalizationiphoneloyaltymailmaphealthmessagingArtboard 1migrationsmultiple-sourcesmultisitenewsnotificationsperformancephonepluginprofilesresearcharrowscalablescrapingsecuresecureseosharearrowarrowsourcestreamsupporttwitchunifiedupdatesvaultwebsitewordpress