goranseric
6/12/2017 - 1:37 PM

wp-server-migration.php

<?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