<?php
/*
Plugin Name: Wordpress Hostname Migration
Plugin URI: http://doublesharp.com/
Description: Functionality to ease in migration of Wordpress code and databases between environments.
Author: Justin Silver
Version: 1.0
Author URI: http://doublesharp.com
License: GPL2
*/
if ( ! class_exists( 'WordpressMigration' ) ):
class WordpressMigration {
// token used to replace site URLs, overridden with SITE_URL_TOKEN constant
private static $site_url_token = '%%site_url_token%%';
private static $instance;
private function __construct() { }
public static function init() {
if ( ! self::$instance ) {
self::$instance = new WordpressMigration();
// Multisite only filters
if ( defined( 'WP_DEVELOPMENT ') && WP_DEVELOPMENT && is_multisite() ):
self::$instance->set_multisite_hooks();
endif;
// Content create url token
self::$instance->set_content_update_hooks();
// Content replace url token
self::$instance->set_content_fetch_hooks();
// MetaData create url token
self::$instance->set_meta_update_hooks();
// MetaData replace url token
self::$instance->set_meta_fetch_hooks();
}
}
/**
* Hooks that are only needed on multisite
*/
function set_multisite_hooks() {
// _config_wp_siteurl() and _config_wp_home() are used by default but disabled for multisite
add_filter( 'option_siteurl', array( &$this, '_ms_config_wp_siteurl' ), 10, 1 );
add_filter( 'option_home', array( &$this, '_ms_config_wp_home' ), 10, 1 );
add_filter( 'content_url', array( &$this, '_ms_content_url' ), 10, 2 );
add_filter( 'plugins_url', array( &$this, '_ms_plugins_url' ), 10, 3 );
}
/**
* Set the filters that replace the token with the site URL in the content and comments
*/
function set_content_fetch_hooks() {
// Post Content
add_filter( 'the_content', array( &$this, '_content_url_replace_token' ), 11, 1 );
add_filter( 'the_excerpt', array( &$this, '_content_url_replace_token' ), 11, 1 );
add_filter( 'content_edit_pre', array( &$this, '_content_url_replace_token' ), 11, 1 );
add_filter( 'the_editor_content', array( &$this, '_content_url_replace_token' ), 11, 1 );
// Post Comments
add_filter( 'comment_excerpt ', array( &$this, '_content_url_replace_token' ), 11, 1 );
add_filter( 'comment_text', array( &$this, '_content_url_replace_token' ), 11, 1 );
}
/**
* Set the filters that replace the site URL with the token in the content
*/
function set_content_update_hooks() {
// Post Content
add_filter( 'content_save_pre', array( &$this, '_content_url_create_token' ), 11, 1 );
// Post Comments
add_filter( 'comment_save_pre', array( &$this, '_content_url_create_token' ), 11, 1 );
}
/**
* Set the filters that replace the token with the site URL in post, user and comment meta values
*/
function set_meta_fetch_hooks() {
add_filter( 'get_post_metadata', array( &$this, '_get_post_metadata' ), 11, 4 );
add_filter( 'get_user_metadata', array( &$this, '_get_user_metadata' ), 11, 4 );
add_filter( 'get_comment_metadata', array( &$this, '_get_comment_metadata' ), 11, 4 );
}
/**
* Unset the filters that replace the token with the site URL in post, user and comment meta values
*/
function unset_meta_fetch_hooks() {
remove_filter( 'get_post_metadata', array( &$this, '_get_post_metadata' ), 11, 4 );
remove_filter( 'get_user_metadata', array( &$this, '_get_user_metadata' ), 11, 4 );
remove_filter( 'get_comment_metadata', array( &$this, '_get_comment_metadata' ), 11, 4 );
}
/**
* Set the actions that replace the site URL with the token in post, user and comment meta values
*/
function set_meta_update_hooks() {
add_action( 'updated_post_meta', array( &$this, '_updated_post_meta' ), 11, 4 );
add_action( 'updated_comment_meta', array( &$this, '_updated_comment_meta' ), 11, 4 );
add_action( 'updated_user_meta', array( &$this, '_updated_user_meta' ), 11, 4 );
add_action( 'added_post_meta', array( &$this, '_updated_post_meta' ), 11, 4 );
add_action( 'added_comment_meta', array( &$this, '_updated_comment_meta' ), 11, 4 );
add_action( 'added_user_meta', array( &$this, '_updated_user_meta' ), 11, 4 );
}
/**
* Unset the actions that replace the site URL with the token in post, user and comment meta values.
* Primarily used to prevent loops when replacing the url and updating the value.
*/
function unset_meta_update_hooks() {
remove_action( 'updated_post_meta', array( &$this, '_updated_post_meta' ), 11, 4 );
remove_action( 'updated_comment_meta', array( &$this, '_updated_comment_meta' ), 11, 4 );
remove_action( 'updated_user_meta', array( &$this, '_updated_user_meta' ), 11, 4 );
remove_action( 'added_post_meta', array( &$this, '_updated_post_meta' ), 11, 4 );
remove_action( 'added_comment_meta', array( &$this, '_updated_comment_meta' ), 11, 4 );
remove_action( 'added_user_meta', array( &$this, '_updated_user_meta' ), 11, 4 );
}
/**
* Replace this site's WP_SITEURL URL based on constant values
*
* @param string $url - The original URL
* @return string - The replaced URL if overridden in wp-config.php
*/
function _ms_config_wp_siteurl( $url = '' ) {
if ( is_multisite() ):
global $blog_id, $current_site;
$cur_blog_id = defined( 'BLOG_ID_CURRENT_SITE' )? BLOG_ID_CURRENT_SITE : 1;
$key = ( $blog_id != $cur_blog_id )? "{$blog_id}_" : '';
$constant = "WP_{$key}SITEURL";
if ( defined( $constant ) )
return untrailingslashit( constant( $constant ) );
endif;
return $url;
}
/**
* Replace this site's WP_HOME URL based on constant values
*
* @param string $url - The original URL
* @return string - The replaced URL if overridden in wp-config.php
*/
function _ms_config_wp_home( $url = '' ) {
if ( is_multisite() ):
global $blog_id;
$cur_blog_id = defined( 'BLOG_ID_CURRENT_SITE' )? BLOG_ID_CURRENT_SITE : 1;
$key = ( $blog_id != $cur_blog_id )? "{$blog_id}_" : '';
$constant = "WP_{$key}HOME";
if ( defined( $constant ) )
return untrailingslashit( constant( $constant ) );
endif;
return $url;
}
function _ms_content_url($url, $path){
if ( is_multisite() ):
// just swap out the host
$url_array = explode( "/", $url );
$replaced = explode( "/", $this->_ms_config_wp_siteurl() );
$url_array[2] = $replaced[2];
$url = implode( "/", $url_array );
endif;
return $url;
}
function _ms_plugins_url($url, $path, $plugin){
if ( is_multisite() ):
// just swap out the host
$url_array = explode( "/", $url );
$replaced = explode( "/", $this->_ms_config_wp_siteurl() );
$url_array[2] = $replaced[2];
$url = implode( "/", $url_array );
endif;
return $url;
}
/**
* Get the URL Token for this site
*
* @return string - The URL token for this site
*/
function _ms_get_site_url_token() {
if ( defined( 'SITE_URL_TOKEN' ) ) return SITE_URL_TOKEN;
return self::$site_url_token;
}
/**
* Find instances of the current domain and replace them with a token
*
* @param string $content - The orignal content
* @return string - The updated content with the token replacement in place of the site URL
*/
function _content_url_create_token( $content ) {
$domain = get_bloginfo( 'url' );
$token = $this->_ms_get_site_url_token();
// Find instances of the current domain and replace them with the token
$content = str_replace( $domain, $token, $content );
return $content;
}
/**
* Find instances of the token and replace them with the current domain
*
* @param string $content - The orignal content
* @return string - The updated content with the site URL in place of the token
*/
function _content_url_replace_token( $content ) {
$domain = get_bloginfo( 'url' );
$token = $this->_ms_get_site_url_token();
// Find instances of the token and replace them with the current domain
$content = str_replace( $token, $domain, $content );
// case insensitive using preg_replace, may be useful?
//$content = preg_replace('/'.str_replace("/", "\/", $domain).'/i', $token, $content);
return $content;
}
/**
* Hook to get_post_metadata
*
* @param null $metadata - always null
* @param int $object_id - database id of this post object
* @param string $meta_key - The meta key used for lookup on this post id
* @param bool $single - If the value is an array, return the first element, otherwise just the value
* @return mixed - The updated value with the site URL in place of the token
*/
public function _get_post_metadata( $metadata, $object_id, $meta_key, $single ) {
return $this->_get_metadata( 'post', $metadata, $object_id, $meta_key, $single );
}
/**
* Hook to get_user_metadata
*
* @param null $metadata - always null
* @param int $object_id - database id of this user object
* @param string $meta_key - The meta key used for lookup on this user id
* @param bool $single - If the value is an array, return the first element, otherwise just the value
* @return mixed - The updated value with the site URL in place of the token
*/
public function _get_user_metadata( $metadata, $object_id, $meta_key, $single ) {
return $this->_get_metadata( 'user', $metadata, $object_id, $meta_key, $single );
}
/**
* Hook to get_comment_metadata
*
* @param null $metadata - always null
* @param int $object_id - database id of this comment object
* @param string $meta_key - The meta key used for lookup on this comment id
* @param bool $single - If the value is an array, return the first element, otherwise just the value
* @return mixed - The updated value with the site URL in place of the token
*/
public function _get_comment_metadata( $metadata, $object_id, $meta_key, $single ) {
return $this->_get_metadata( 'comment', $metadata, $object_id, $meta_key, $single );
}
/**
* This function is called by the hooked functions and will return the data with the token replaced by the site url
*
* @param string $meta_type - The object type of his meta key/value
* @param null $metadata - always null
* @param int $object_id - database id of this object
* @param string $meta_key - The meta key used for lookup on this id
* @param bool $single - If the value is an array, return the first element, otherwise just the value
* @return mixed - The updated value with the site URL in place of the token
*/
private function _get_metadata( $meta_type, $metadata, $object_id, $meta_key, $single ) {
// Unset the hooks to get the default data
$this->unset_meta_fetch_hooks();
// Fetch the default data
$data = get_metadata( $meta_type, $object_id, $meta_key, $single );
// Reset the hooks to intercept
$this->set_meta_fetch_hooks();
return $data;
// Create a small cache key that is based on the data value (cleared in update code as needed)
$key = $meta_type.$object_id.$meta_key.$single;
// Check to see if we have already processed this value
if ( false === ( $cached = wp_cache_get( $key, 'wordpress_migration' ) ) ) {
$cached = $data;
$domain = get_bloginfo( 'url' );
$token = $this->_ms_get_site_url_token();
$hastoken = false;
// Check for the token and replace it with the url
if ( is_array( $cached ) ) {
foreach ( $cached as $value ) {
// Only process strings for now
if ( is_string( $value ) && false !== strpos( $value, $token ) ) {
// Value contains token
$value = str_replace( $token, $domain, $value );
$hastoken = true;
}
}
} else {
if ( false !== strpos( $cached, $token ) ) {
// Value contains token
$cached = str_replace( $token, $domain, $cached );
$hastoken = true;
}
}
// If we didn't replace the token just show that it has been processed.
if ( ! $hastoken ) $cached = true;
// Set the cache so we don't have to loop/replace
wp_cache_set( $key, $cached, 'wordpress_migration', 600 ); // 5 mins
}
// It has been processed, but did not have the token, return $data
if ( $cached !== true ) {
$data = $cached;
}
return $data;
}
/**
* Hook to updated_post_meta
*
* @param int $meta_id - Database id of the meta key/value
* @param int $object_id - Database id of this post object
* @param string $meta_key - The meta key used for update on this post id
* @param mixed $_meta_value - The current meta value
*/
public function _updated_post_meta( $meta_id, $object_id, $meta_key, $_meta_value ) {
$this->_updated_meta( 'post', $meta_id, $object_id, $meta_key, $_meta_value );
}
/**
* Hook to updated_comment_meta
*
* @param int $meta_id - Database id of the meta key/value
* @param int $object_id - Database id of this comment object
* @param string $meta_key - The meta key used for update on this comment id
* @param mixed $_meta_value - The current meta value
*/
public function _updated_comment_meta( $meta_id, $object_id, $meta_key, $_meta_value ) {
$this->_updated_meta( 'comment', $meta_id, $object_id, $meta_key, $_meta_value );
}
/**
* Hook to updated_user_meta
*
* @param int $meta_id - Database id of the meta key/value
* @param int $object_id - Database id of this user object
* @param string $meta_key - The meta key used for update on this user id
* @param mixed $_meta_value - The current meta value
*/
public function _updated_user_meta( $meta_id, $object_id, $meta_key, $_meta_value ) {
$this->_updated_meta( 'user', $meta_id, $object_id, $meta_key, $_meta_value );
}
/**
* Called by hooked functions with the meta type. Checks the values for the site url, replaces
* with the token, and updates the meta_value if necessary.
*
* @param string $meta_type - The object type of his meta key/value
* @param int $meta_id - Database id of the meta key/value
* @param int $object_id - Database id of this object
* @param string $meta_key - The meta key used for update on this object id
* @param mixed $_meta_value - The current meta value
*/
private function _updated_meta( $meta_type, $meta_id, $object_id, $meta_key, $_meta_value ) {
$domain = get_bloginfo( 'url' );
$token = $this->_ms_get_site_url_token();
// Only update the meta value if we find an instance of the url
$update = false;
$meta_value = $_meta_value;
if ( is_array( $meta_value ) ) {
// Loop through the array and update the values
foreach ( $meta_value as &$value ) {
// only check if this is a string, don't support arrays of arrays
if ( is_string( $value ) && false!==strpos( $value, $domain ) ) {
$value = str_replace( $domain, $token, $value );
$update = true;
}
}
} else {
// Check meta value for url
if ( false!==strpos( $meta_value, $domain ) ) {
$meta_value = str_replace( $domain, $token, $meta_value );
$update = true;
}
}
// The meta value contained a url which was replaced with a token
if ( $update ) {
// Unset the update hooks to prevent a loop
$this->unset_meta_update_hooks();
// Update the meta value
update_metadata( $meta_type, $object_id, $meta_key, $meta_value, $_meta_value );
// Reset the hooks
$this->set_meta_update_hooks();
}
// Since the value was updated, clear the cache
$key = $meta_type.$object_id.$meta_key;
wp_cache_delete( $key.true, 'wordpress_migration' ); // single = true
wp_cache_delete( $key.false, 'wordpress_migration' ); // single = false
}
}
// init the class/filters
WordpressMigration::init();
endif; // class exists