John Russell Blog

Running a Development Copy of WordPress Multisite

The Problems with Multisite

I had recently been tasked with setting up production and development environments for WordPress Multisite with the intention of following traditional development methods. Having a dedicated development environment allows for testing themes, plugins, and updates without affecting the production environment. While researching best practices for creating production and development environments for WordPress I found frustratingly little examples or documentation covering WordPress in a Multisite configuration. Much of what I found was also dated to the point of being obsolete.

I came across a page in the WordPress Codex titled Running a Development Copy of WordPress (reading this article before proceeding will clarify what follows) which seemed to document the exact configuration that was needed. It made no mention of Multisite, however its merits were worth following. Proceeding with testing the concepts covered in the article I found that the “dev” site did indeed work, however only the main site would load properly, thus being an unusable solution for Multisite.

To clarify, the Multisite test environment that I was using was a sub-directory install using an IP address as the domain. So, at the time I thought moving the “dev” WordPress install from a sub-directory on the web server (being the production server, called WP-1 from now on) over to the web root of a clone of WP-1 (being the development server, called WP-2 from now on) would fix the problem, assuming the word “dev” in the url was causing the problem. Ultimately I planned to be using the WordPress MU Domain Mapping plugin, which requires WordPress to be in the web root so this step would eventually be required anyway.

Once WP-2 was setup (pointing to the same database used by WP-1) I quickly realized that regardless of which site was being viewed it always redirected back to WP-01, which is obviously not what is wanted.

The solution

After many hours of troubleshooting and testing I was finally able to get WP-2 to directly mirror WP-1, whether WP-1 used an A record or IP address as the primary domain. In short, here is what you need to do to setup a production and development environment that share the same database using WordPress Multisite.

Before proceeding, note that none of what is mentioned below is intended to be applied to already existing production/development environments and if attempted please backup your WordPress files and database right now!

What You’ll Need

  1. WP-1: A web server with WordPress 3.8 installed in the web root as a sub-directory Multisite setup, using either an IP address or an A record.
  2. WP-2: A clone of WP-1.
  3. DB: A MySQL server with the WordPress database and separate users for WP-1 and WP-2. The WP-1 user account should have full access to the WordPress database while the WP-2 user account should only have SELECT privileges so that WP-2 cannot “break” the production environment.

Next, be sure to update the wp-config.php file (in the WordPress root) on WP-2 to be sure it’s using the WP-2 MySQL user account. Also, note that the wp-config.php file on WP-2 should be identical to that on WP-1 with the exception of the MySQL user and the items listed below.

While editing the wp-config.php on WP-2, I recommend adding the following lines of code to make troubleshooting plugins and themes easier.

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', true);

Within the wp-config.php file of both WP-1 and WP-2 add the following lines of code. These variables will allow the WP-1 or WP-2 domains to change and only need to change the domain names in one place. Note that the IP addresses are only examples and should be changed to match your own server environments, and the WP_PRODUCTION_DOMAIN should match DOMAIN_CURRENT_SITE.

define('SUNRISE', 'on');
define('WP_PRODUCTION_DOMAIN', '192.168.1.10');
define('WP_DEVELOPMENT_DOMAIN', '192.168.1.20');

Once that is done, install the WordPress MU Domain Mapping plugin on WP-1 and then copy the sunrise.php file (in the wp-content directory) and domain-mapping plugin files to WP-2.

Next, you’ll need to make some edits to the sunrise.php file on WP-2. This will remove the error stating that escape is deprecated, if WP_DEBUG is set to true.

// Change line 11 from
$dm_domain = $wpdb->escape( $_SERVER['HTTP_HOST'] );

// To
$dm_domain = $wpdb->_escape( $_SERVER['HTTP_HOST'] );

Also, add the following code to the sunrise.php file, just before the closing php tag.

// Filters the domain that is displayed/output into HTML
add_filter('pre_option_home', 'dev_pre_url_filter', 1 );
add_filter('pre_option_siteurl', 'dev_pre_url_filter', 1 );
add_filter('the_content', 'dev_content_filter', 100);
add_filter('content_url', 'dev_content_url_filter', 100, 2);
add_filter('post_thumbnail_html', 'dev_content_filter', 100);
add_filter('wp_get_attachment_link', 'dev_content_filter', 100);
add_filter('wp_get_attachment_url', 'dev_content_filter', 100);
add_filter('upload_dir', 'dev_upload_dir_filter', 10);

function dev_pre_url_filter() {
	global $wpdb, $path, $switched;
	$url;
	$switched_path;
	$blog_id = get_current_blog_id();

	if (!$switched) {
		$url = is_ssl() ? 'https://' : 'http://';
		$url .= WP_DEVELOPMENT_DOMAIN;
		if (!is_main_site()) $url .= rtrim($path, '/');
		return $url;
	} else {
		$switched_path = $wpdb->get_var( "SELECT path FROM {$wpdb->blogs} WHERE blog_id = {$blog_id} ORDER BY CHAR_LENGTH(path) DESC LIMIT 1" );
		$url = is_ssl() ? 'https://' : 'http://';
		$url .= WP_DEVELOPMENT_DOMAIN;
		$url .= rtrim($switched_path, '/');
		return $url;
	}
}
function dev_content_filter($post_content) {
	global $wpdb;
	
	$blog_details = get_blog_details();
	$original_url = $wpdb->get_var( "SELECT domain FROM {$wpdb->dmtable} WHERE blog_id = {$blog_details->blog_id} ORDER BY CHAR_LENGTH(domain) DESC LIMIT 1" );
	$dev_url = WP_DEVELOPMENT_DOMAIN . $blog_details->path;
	
	if($original_url !== null) {
		$post_content = str_replace($original_url . '/', $original_url, $post_content);
		$post_content = str_replace($original_url, $dev_url, $post_content);	
	}
	// Change all url's to point to staging (images, anchors, anything within the post content)
	$post_content = str_replace(WP_PRODUCTION_DOMAIN, WP_DEVELOPMENT_DOMAIN, $post_content);
	// Change urls for "uploads" to point to production so images are visible
	$post_content = str_replace(WP_DEVELOPMENT_DOMAIN . $blog_details->path . 'wp-content/uploads', WP_PRODUCTION_DOMAIN . $blog_details->path . 'wp-content/uploads', $post_content);
	return $post_content;
}
/*
* Filters the content_url function - specifically looking for content_url('upload') calls where path has uploads in the string
*
* Added so MU-Plugins could use content_url on DEV and PROD
*/
function dev_content_url_filter($url, $path) {
	if (!empty($path) && strpos($path, 'uploads') !== false) {
		return str_replace(WP_DEVELOPMENT_DOMAIN, WP_PRODUCTION_DOMAIN, $url);
	}
	
	return $url;
}
function dev_upload_dir_filter($param) {
	$param['url'] = str_replace(WP_DEVELOPMENT_DOMAIN, WP_PRODUCTION_DOMAIN, $param['url']);
	$param['baseurl'] = str_replace(WP_DEVELOPMENT_DOMAIN, WP_PRODUCTION_DOMAIN, $param['baseurl']);
	
	return $param;
}

Since urls are stored in the WordPress database using absolute paths, if a site is viewed on WP-2 all hyperlinks will point to WP-1, so navigation would require manually typing in the url of a page wanting to be viewed. The above code essentially intercepts the WordPress option for siteurl and home, and post/page content, and changes it to match the domain of WP-2 which changes dynamically generated hyperlinks, such as the page title and menus, to point to the WP-2 server. In addition to changing link urls there are several attachment and upload filters to do the same thing, thus allowing us to upload images to the Production environment in the WordPress dashboard and the Development environment will point to the same location so attachments don’t need to be cloned. This allows for natural navigation of WP-2 in a web browser where all hyperlinks (and requests for external CSS and JavaScript files) will point to WP-2.

Now that most of the heavy lifting is done there are just a few things left to fix, mostly because WordPress is still redirecting all requests from WP-2 over to WP-1.

WARNING: The following code requires making some changes to the WordPress Core and the WordPress MU Domain Mapping files on WP-2, which may cause problems with plugins or themes, but most importantly these changes would need to be made each time WordPress is updated.

Comment out the following lines of code in the domain_mapping.php file inside the domain mapping plugin directory. This will prevent the domain mapping plugin from redirecting requests from WP-2 to WP-1.

// Line 708
header( "Location: {$url}{$_SERVER[ 'REQUEST_URI' ]}", true, $redirect );
// Line 709
exit;

Comment out the following lines of code in the ms-settings.php file inside the wp-includes directory. This will prevent WordPress from redirecting requests from WP-2 to WP-1.

// Line 98
header( 'Location: http://' . $current_site->domain . $current_site->path );
// Line 99
exit;

Add the following lines of code to the ms-blogs.php file inside the wp-includes directory. The code will be placed within the scope of the get_blog_details function.

// After these lines of code on line 104 and 105
function get_blog_details( $fields = null, $get_all = true ) {
	global $wpdb;

// Add this one line of code
	if (isset($fields['domain'])) $fields['domain'] = WP_PRODUCTION_DOMAIN;

Instead of modifying the WordPress core files please read my follow up guide, which explains how to do this using a new filter brought in with WordPress 3.9.

Congratulations! At this point, provided nothing went wrong during the changes listed above, you should have a production environment using a database that is shared by the development environment, thus alleviating the need for migrating database tables as development stages progress. The idea is that new sites can be created in the production environment (with no CNAME pointing to it so it is not visible to the public) and then built in the production environment. Once the new site is ready for a soft launch the theme files can be copied to the production environment and a domain can be mapped to the site, thus making it visible to the public. The same concept can be applied to already existing sites that need updates to the theme, where changes can be made in the development environment (thus thankfully not visible to the public when you create a typo in the functions.php file and completely break the site) and once finished migrated to the production environment.

There are unfortunately a few drawbacks, such as not being able to make content changes in only the development environment (without duplicating it to a non public url), not being able to make changes to plugins that require scheme changes, and so on. However, themes can be completely developed and edited in the development environment and then migrated to the production environment by simply moving the theme files, completely avoiding database migrations and search/replace processes altogether. This type of setup isn’t for everyone, obviously, but for those who have large amount of users/editors/administrators who can make changes to posts, pages, calendars, and terms/categories, who are outside of the web development team, being able to avoid direct interaction with the database and being able to develop using “live” data is priceless!

Lastly, when it comes to testing plugin updates or WordPress updates my current thought is to clone WP-1 and the DB to another virtual server, and once vetted the updates will be installed on WP-1 and copied over to WP-2, so the drawbacks above are not much of a concern in my situation.

When it comes to WordPress there are certainly other ways of accomplishing the same thing and I’d be very excited to hear feedback from others, but for now this is a solution that will meet my needs and I hope will help others who are in the same situation I was.

Update – January 8, 2014

I’ve extended this post in a Part 2 where I shared a plugin that I created to add a “development sites” menu to the adminbar to allow easy switching between viewing the production environment and development environment.

Update – August 8, 2014

I’ve written a follow up guide, now that WordPress 3.9 has been released, explaining how to get the same functionality out of the Development environment without needing to modify WordPress core files.


Posted

in

by

Comments

5 responses to “Running a Development Copy of WordPress Multisite”

  1. Mervin Avatar

    Appreciate the recommendation. Will try it out.

  2. Vern Avatar

    Awesome article.

  3. Roy Avatar

    It’s actually a great and helpful piece of information. I am glad that you shared this useful info with us.
    Please keep us up to date like this. Thank you for sharing.

  4. www.ghostnetwork.fr Avatar

    Wonderful article! We will be linking to this particularly
    great article on our site. Keep up the good
    writing.

  5. Brett Avatar
    Brett

    Hi, cool article. How would this change for a subdomain MultiSite installation?

    Thanks
    Brett

Leave a Reply

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