When building WordPress sites for clients I generally try to use different environments for development, testing and deployment to live. This means that there are three areas of the site that need to be synchronised across environments; the code, the database and any media uploads for the site.
Managing code deployments can be a smooth operation with the help of an IDE or deployments based on a Git repository. Keeping the database in sync is extremely easy with a plugin like WP Migrate DB Pro, which allows you to push or pull databases across environments. However, media uploads can prove more of a problem.
Consider this scenario. A site relies heavily on user uploaded images or other media and has been out in production for some time. Your client asks for some amendments that include front end design changes. You fire up the development site. At this point the code should be inline with production. However, the database will not be. So you use WP Migrate DB Pro to ‘pull’ the remote production database, which replaces the local database. You view your local site and you see a whole bunch of broken links to images that are in your live site’s wp-content/uploads/
folder but of course are not in the local site’s.
The solution? Well here are five different ways to solve this problem, with varying degrees of time, hassle and elegancy.
1. FTP
I have to admit I did this recently. It takes time and it is not the best option presented here, but it works. Using your favourite FTP client, connect to the live site and download the content’s of wp-content/uploads/
to your local install. Do not overwrite existing to save a bit of time. Then grab yourself a cup of tea as, depending on the site, this could take some time.
2. rsync
I was recently introduced to rsync to help copy only the missing images between environments. Wikipedia describes it well:
rsync is a utility software and network protocol for Unix-like systems (with a port to Microsoft Windows) that synchronizes files and directories from one location to another while minimizing data transfer by using delta encoding when appropriate.
rysnc needs to be run in the command line and connecting to your remote site needs to be with SSH.
First of all navigate to your local WordPress site:
cd wordpress-site
Move to your uploads folder:
cd wp-content/uploads
To perform the synchronisation (replacing the SSH credentials):
rsync -avz --rsh=ssh user@host:/path/to/site/wp-content/uploads/* .
rsync is a lot quicker as it uses an SSH connection and is a very powerful tool. Read more about the different options and configuration here.
3. Media Addon for Migrate DB Pro
WP Migrate DB Pro already does a great job of handling database migrations, but as of version 1.3 they introduced the Media Addon which:
In a nutshell, this update will allow you to pull down the media files from your remote WordPress installation into your local environment. To accomplish this we compare the attachment posts found within your remote database to the ones in your local database and only pull updated or missing media files.
This is a brilliant feature making synchronising media files now as easy as migrating databases. The add-on comes with the Developer and higher licenses of WP Migrate DB Pro.
4. .htaccess
The last two solutions are my favourites as they don’t require any form of copying of files across environments.
This involves adding an .htaccess
file to the wp-content/uploads/
directory of your site with this code:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /wp-content/uploads/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*) http://yourlivesite.com/wp-content/uploads/$1 [L,P]
</IfModule>
So for any file requested within wp-content/uploads/
, that does not exist, it will serve the image from http://yourlivesite.com
.
You may need to edit the RewriteBase
to suit your site’s base path in your various environments.
5. WordPress Filters
This last solution is the equivalent of the .htaccess code but using core WordPress filters to serve the images. To make this work on any environment you need to add some extra config to your site’s wp-config.php
:
Define your site’s url used by WordPress to be dynamic:
define('WP_SITEURL', 'http://' . $_SERVER['HTTP_HOST'] .
str_replace(DIRECTORY_SEPARATOR, '/', str_replace(realpath($_SERVER['DOCUMENT_ROOT']), '', dirname(__FILE__))));
Define the url of your production site where most up to date media will be uploaded:
define('LIVE_SITEURL', 'http://yourlivesite.com');
Then in your theme’s functions.php
, a plugin or better still a site-wide functions plugin, add:
add_action('init', 'my_replace_image_urls' );
function my_replace_image_urls() {
if ( defined('WP_SITEURL') && defined('LIVE_SITEURL') ) {
if ( WP_SITEURL != LIVE_SITEURL ){
add_filter('wp_get_attachment_url', 'my_wp_get_attachment_url', 10, 2 );
}
}
}
function my_wp_get_attachment_url( $url, $post_id) {
if ( $file = get_post_meta( $post_id, '_wp_attached_file', true) ) {
if ( ($uploads = wp_upload_dir()) && false === $uploads['error'] ) {
if ( file_exists( $uploads['basedir'] .'/'. $file ) ) {
return $url;
}
}
}
return str_replace( WP_SITEURL, LIVE_SITEURL, $url );
}
This will work for uploaded media that is being served by WordPress’ standard image functions such as the_post_thumbnail()
or wp_get_attachment_image()
.
The function my_wp_get_attachment_url()
does the work, but will only be added if it is not the live site, as it will already have all the images.
The function then hooks into the WordPress filter wp_get_attachment_url
, so for each time WordPress is serving an attachment url our function jumps in and checks if that attachment file exists in our site, and if not swaps out the site url with the live url.
Which synchronisation method do you use? How do you keep your WordPress environments aligned?