It can be extremely challenging to write WordPress code that works across many environments and allows you to use a custom location for WordPress media, WordPress multisite media, and WordPress core itself. I wrote about this extensively here. The code I included in that post works, although a lot of it was excerpted to be shown as a short example here or there. If your site switches from one blog to the next and doesn’t intermingle content – you really don’t have much to worry about after your initial setup. But in almost all cases, if you want to start using switch_to_blog()
to co-mingle content from multiple sites inline, get ready to do some debugging!
switch_to_blog()
works like so:
// I am on my blog switch_to_blog( 2 ); // I am on your mom's blog restore_current_blog(); // I am on my blog
Simple enough. This will switch context all over the place in WP core. wp_posts
will become wp_2_posts
. get_current_blog_id()
will return 2 because the global
variable $blog_id
will be set to 2, etc.
In my post about environments, I had a lot of filters that I suggested adding in wp-content/sunrise.php
that work just great in switching initial context to a specific blog and in setting up overrides for default media upload paths etc. They work fine… unless you ever plan on using switch_to_blog()
.
Here’s an example:
// Before add_filter( 'pre_option_home', function ( $str ) use ( $domain ) { return 'http://' . $domain; } ); // After using switch_to_blog and realizing I needed to account // for any sort of weird context I may find myself in add_filter( 'pre_option_home', function () { global $current_blog; $extra = rtrim( $current_blog->path, '/' ); return 'http://' . MY_ENVIRONMENT_HOST . $extra; } );
Ok cool, so you pull a path from a global variable and append it if it still has a value after being rtrim
‘d? Easy. That would be true if $current_blog
and its properties were updated every time switch_to_blog()
is called. It is not!
$current_blog
is one of the values we set in wp-content/sunrise.php
to override the database and set our $current_blog->domain
value to our current environment. $current_blog
and $current_site
exist mainly for use on the initialization of Multisite. Outside of startup, they aren’t really accessed or modified.
Because I want a static way to access dynamic variables, I have added some code to change the context of $current_blog
when switch_to_blog()
is called in wp-content/sunrise.php
:
function emusic_switch_to_blog( $blog_id, $prev_blog_id = 0 ) { if ( $blog_id === $prev_blog_id ) return; global $current_blog, $wpdb; $current_blog = $wpdb->get_row( "SELECT * FROM {$wpdb->blogs} WHERE blog_id = {$blog_id} LIMIT 1" ); $current_blog->domain = MY_ENVIRONMENT_HOST; } emusic_switch_to_blog( $the_id ); add_action( 'switch_blog', 'emusic_switch_to_blog', 10, 2 );
Now that I have added that action, my filter pre_option_home
will work. I use the same method for pre_option_siteurl
. What I was previously doing to retrieve the path of the current blog didn’t work:
add_filter( 'pre_option_siteurl', function () { $extra = rtrim( get_blog_details( get_current_blog_id() )->path, '/' ); return 'http://' . EMUSIC_CURRENT_HOST . $extra; } );
Why didn’t it work? get_blog_details()
eventually does $details->siteurl = get_blog_option( $blog_id, 'siteurl' );
giving us a nice and hardy dose of infinite recursion. So to combat it – I implemented the setting of $current_blog
on the switch_blog
action so its properties are always the current blog’s properties. Boom.
The next 2 annoyances are media upload urls / paths and admin paths. We use custom media locations for the main blog and network blogs:
add_filter( 'pre_option_upload_path', function () { $id = get_current_blog_id(); if ( 1 < $id ) return $_SERVER['DOCUMENT_ROOT'] . "/blogs/{$id}/files"; return $_SERVER['DOCUMENT_ROOT'] . '/' . EMUSIC_UPLOADS; } ); add_filter( 'pre_option_upload_url_path', function () { $id = get_current_blog_id(); if ( 1 < $id ) return 'http://' . EMUSIC_CURRENT_HOST . "/blogs/{$id}/files"; return 'http://' . EMUSIC_CURRENT_HOST . '/' . EMUSIC_UPLOADS; } );
We switched blog context using switch_to_blog()
, which triggers our action, which then sets the global
variable $blog_id
to our current blog’s id, which can then be retrieved from within functions by get_current_blog_id()
. Yeah, that’s a mouthful. We are also using a constant for our host name, so we don’t have to query for it / output-buffer it / or str_replace()
it.
The admin is a little bit trickier because we usually don’t call switch_to_blog()
in the code, but the admin bar will list your blogs and has URLs to their Dashboards etc. We can filter those as well:
function get_admin_host( $url, $path, $blog_id = '' ) { $path = ltrim( $path, '/' ); if ( empty( $blog_id ) ) { $blog_id = get_current_blog_id(); } $blog_path = rtrim( get_blog_details( $blog_id )->path, '/' ); return sprintf( 'http://%s%s/wp-admin/%s', EMUSIC_CURRENT_HOST, $blog_path, $path ); } add_filter( 'admin_url', 'get_admin_host', 10, 3 ); function get_network_admin_host( $url, $path, $blog_id = '' ) { $path = ltrim( $path, '/' ); if ( empty( $blog_id ) ) { $blog_id = get_current_blog_id(); } $blog_path = rtrim( get_blog_details( $blog_id )->path, '/' ); return sprintf( 'http://%s%s/wp-admin/network/%s', EMUSIC_CURRENT_HOST, $blog_path, $path ); } add_filter( 'network_admin_url', 'get_network_admin_host', 10, 3 );
If you want to see this in action, I recently integrated the eMusic editors’ 17 Dots blog into eMusic proper, check it out: 17 Dots. You can see multiple blogs intertwining on the homepage.