Please note this post has not been updated and may contain broken links or outdated information.

During the development process at Plank, a website tends to be worked on in a number of different environments. Most of the development process takes place on the local machines of each of the developers (some use localhost, others use MAMP Pro virtual domain hosts). We also test the website in numerous browsers, emulators and mobile devices, before it goes on a staging server for our clients to evaluate. Finally, the website is deployed on the live server. This is quite a lot of movement. As far as content management systems go, WordPress is not ideally suited to this kind of mobility. Given its dependency on absolute paths, changing the domain or install directory of WordPress tends to break various aspects of the site. Even viewing the same install from a different domain name (http://localhost/site, http://*.local/site or http://site.mamp) causes problems. Over the past few months, we have come up with a number of solutions for addressing this issue and making our lives easier. This article is intended as a collection of tips and tricks that facilitate the mobility of WordPress sites. The ultimate goal is to make the site environment-agnostic, meaning regardless of where the site ends up, it should run just fine with minimal adjustment.

DISCLAIMER: This article is intended for developers. If you already have your finished WordPress blog installed on your personal website and have no intention of ever moving its install directory, then this article does not have much to offer you. Also, it is recommended that you have some experience with PHP and Apache before delving into some of these tips. Not for you? Check out some of our other awesome articles instead.

Some things to bear in mind

When changing environments, you still need to keep your upload and plugin directories and your database in sync across across your environments. All content management systems still need to keep track of their resources. There is no catch-all solution for dealing with these. We use a few tricks in the Plank office, but they may not suit everyone's use cases. If your database is accessible by a URL, you can make each installation of your site use the same database (though a second db for the live site is recommended). For the uploads and plugins directorys, if the different installs are on the same local network, we often mount a central copy of the site on the other systems and create a symbolic link to the upload and plugin directories of that install. Alternatively, you could conceivably make your entire WordPress install part of your version control repo. This is certainly not ideal (a lot of images could bloat the repo), but this could be feasible for smaller websites. Lastly, you could write an rsync script to synchronize between machines, but this is the most complicated option.

Dynamic site URL

WordPress gets very attached to its install directory and configurations. It determines the URL of its install directory with an absolute path stored in the database (the siteurl and home rows of the wp_options table). If this value does not line up with your actual install location, the site will break. As a result, in order to move an install from one domain or directory to another, there is quite a checklist of things to do. This is one of the most problematic aspect of moving WP across environments, especially if different environments are sharing a database (only one person could view the site at a time)! Luckily it is also one of the easiest to remedy. WordPress allows use to override these default paths with our own by defining the WP_SITEURL\ and WP_HOME constants. You could set these to the absolute path of the current environment, though this still requires editing files each time we create a new install. Instead, we can have PHP determine the directory of the install dynamically. /wp-config.php (add anywhere)

$base_path = substr(ABSPATH, strlen($_SERVER['DOCUMENT_ROOT']));
define('WP_SITEURL', "http://${_SERVER['HTTP_HOST']}${base_path}");
define('WP_HOME',    "http://${_SERVER['HTTP_HOST']}${base_path}");

If you use the default permalink settings (i.e. http://site.com/?p=42), then you are already set. You can change the directory of your install without breaking anything. However, any of the other permalink styles require an extra step. When you select any of those options, wordpress will generate a .htaccess file in your install directory or modify it, if one already exists (note: this file is hidden, you will need to select 'show hidden files' to find it). The default options it provides lock the site in to a path relative to domain root ('/'), which can be problematic for us. To fix this we need to remove the RewriteBase and remove the leading slashes and directories from the rewrite rule. The result should look something like this. /.htaccess (replace existing wordpress segment)

# BEGIN WordPress

RewriteEngine On
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [L]

# END WordPress

Why isn't this the default? I cannot fathom.

Media & TinyMCE

For the most part, media handling works fine in WordPress. Uploaded media files go to the upload directory and a WP_Attachment entry is added to the database for each one. The featured image (a.k.a. post thumbnail) feature of WP 2.9+ is already fairly environment-agnostic. The association between the post and the attachment image is done through unique IDs, the domain or directory where the site ends up should never be a problem as long as the image actually exists in the upload directory. Using said media within the TinyMCE editor is an entirely different story. The HTML added to the editor when using the 'add media' feature uses absolute URLs, relative to the uploader's environment. Changing environments will break all such inline images throughout the website. This problem also applies to links to just about any content elsewhere on the site (i.e. href="/about"). To remedy this, one would normally perform a find/replace on the database to set all these URLs to the new path. A much better solution is to use shortcodes to inject the correct path on the fly. I've created a handful of shortcodes for areas that I frequently want to link to. Each of these can also be set to different locations in different environments, so it occasionally important to keep them separate. /wp-content/themes/{your_theme}/functions.php (add anywhere)

//shortcode for the install directory
function site_url_shortcode(){
	return site_url();
}
add_shortcode( 'site_url', 'site_url_shortcode' );

//shortcode for the upload directory
function upload_url_shortcode(){
	$upload_dir = wp_upload_dir();
	return $upload_dir['baseurl'];
}
add_shortcode( 'upload_url', 'upload_url_shortcode' );

//shortcode for the theme directory
function theme_url_shortcode(){
	return get_stylesheet_directory_url();
}
add_shortcode( 'theme_url', 'theme_url_shortcode' );

Now, Instead of writing the paths out absolutely or relatively, we can do something like the following.

//before
...
//after
...

That shortcode gets evaluated by the site that is displaying the content, rather than being locked into the version that uploaded it. The last step to make the 'add media' feature automatically insert shortcodes instead of absolute links. We can create a filter for the send_to_editor hooks so that we can strip out the old links and replace them with our snazzy shortcodes. /wp-content/themes/{your_theme}/functions.php (add anywhere)

/*
 * Force send_to_editor to use shortcodes for paths rather than hardcoded absolute links
 */
function environment_safe_send_to_editor($html){
	//media uploads -> upload_url
	$upload_dir = wp_upload_dir();
	$html = str_replace($upload_dir['baseurl'], '[upload_url]', $html);

	//theme assets -> theme_url
	$html = str_replace(get_stylesheet_directory_uri(), '[theme_url]', $html);

	//attachment pages -> site_url
	$html = str_replace(site_url(),'[site_url]',$html);

	return $html;
}
add_filter('image_send_to_editor','environment_safe_send_to_editor');
add_filter('image_send_to_editor_url','environment_safe_send_to_editor');
add_filter('media_send_to_editor','environment_safe_send_to_editor');


And there you have it. No more dependency on the domain name/directory. Your site should work wherever it goes.