development Archives - Justin Silver https://www.justinsilver.com/tag/development/ Technology, Travel, and Pictures Fri, 03 Feb 2017 07:27:07 +0000 en-US hourly 1 https://wordpress.org/?v=6.0.1 https://www.justinsilver.com/wp-content/uploads/2013/06/cropped-apple-touch-icon-160x160.png development Archives - Justin Silver https://www.justinsilver.com/tag/development/ 32 32 WordPress Plugin: wp-server-migration https://www.justinsilver.com/technology/wordpress/wordpress-plugin-wp-server-migration/?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-plugin-wp-server-migration https://www.justinsilver.com/technology/wordpress/wordpress-plugin-wp-server-migration/#comments Sun, 04 Nov 2012 04:50:12 +0000 http://justin.ag/?p=2757 This plugin is designed to make managing migration from dev->staging->production easier for WordPress and WordPress Multisite. Allows for WP_HOME and WP_SITEURL to be set in WordPress Multisite, and for URLs in content and meta...

The post WordPress Plugin: wp-server-migration appeared first on Justin Silver.

]]>
AmpedSense.OptimizeAdSpot('AP'); AmpedSense.OptimizeAdSpot('IL'); AmpedSense.OptimizeAdSpot('IR');

This plugin is designed to make managing migration from dev->staging->production easier for WordPress and WordPress Multisite. Allows for WP_HOME and WP_SITEURL to be set in WordPress Multisite, and for URLs in content and meta data to be rewritten automatically when the environment changes.

This file is always up to date based on this gist.

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

The post WordPress Plugin: wp-server-migration appeared first on Justin Silver.

]]>
https://www.justinsilver.com/technology/wordpress/wordpress-plugin-wp-server-migration/feed/ 3
Moving WordPress’ /wp-content/uploads out of your DocumentRoot https://www.justinsilver.com/technology/wordpress/moving-wordpress-wp-contentuploads-out-of-your-documentroot/?utm_source=rss&utm_medium=rss&utm_campaign=moving-wordpress-wp-contentuploads-out-of-your-documentroot https://www.justinsilver.com/technology/wordpress/moving-wordpress-wp-contentuploads-out-of-your-documentroot/#comments Sat, 03 Nov 2012 15:41:02 +0000 http://justin.ag/?p=2743 Why might you want to move your /wp-content/uploads folder out of your DocumentRoot? In my case, I use DropBox to sync files between my desktop and my laptop for doing development – no matter...

The post Moving WordPress’ /wp-content/uploads out of your DocumentRoot appeared first on Justin Silver.

]]>
AmpedSense.OptimizeAdSpot('AP'); AmpedSense.OptimizeAdSpot('IL'); AmpedSense.OptimizeAdSpot('IR');

Why might you want to move your /wp-content/uploads folder out of your DocumentRoot? In my case, I use DropBox to sync files between my desktop and my laptop for doing development – no matter where I was working last, my changes, including Subversion status from the hidden .svn directories, are ready to go on the other computer (assuming I have Internet access of course, but even then DropBox will copy over the local network if it’s available, which means it’s pretty quick at home). For some of my site’s, like this one, there are gigabytes and gigabytes of images in my uploads directory, but they don’t really do that much for me in terms of development, but I still need them.

The solution for me was to move the uploads folder outside of the Apache DocumentRoot, were it can still be accessed, but doesn’t count against my DropBox usage (and doesn’t suck up bandwidth syncing thumbnail generation and the like.)

My first order of business was to break uploads out into a different SVN path – otherwise it will just update the folder every time you run an “svn up“. On the production server, you would want to do the following:

# WordPress Files
# https://svn.example.com/mysite/web/trunk
svn co https://svn.example.com/mysite/web/trunk /var/www/mysite/html
# WordPress Uploads
# https://svn.example.com/mysite/uploads/trunk
svn co https://svn.example.com/mysite/uploads/trunk /var/www/mysite/html/wp-content/uploads
# add "uploads" to your svn:ignore in the wp-content folder
svn propedit svn:ignore /var/www/mysite/html/wp-content/
svn up /var/www/mysite/html/wp-content/
svn commit -m "saving svn:ignore for uploads" /var/www/mysite/html/wp-content/

And then on your Workstation, the following:

# WordPress Files
# https://svn.example.com/mysite/web/trunk
svn co https://svn.example.com/mysite/web/trunk ~/Dropbox/MySites/mysite
# WordPress Uploads
# https://svn.example.com/mysite/uploads/trunk
svn co https://svn.example.com/mysite/uploads/trunk  ~/Documents/MySites/mysite

I also added the following rules to /etc/subversion/config on my server and ~/.subversion/config on my workstation to prevent thumbnails from being added – there are lots of ways these can be regenerated if necessary.

[miscellany]
global-ignores = *-[0-9]*x[0-9]*.jpg -[0-9]*x[0-9]*.gif -[0-9]*x[0-9]*.png -[0-9]*x[0-9]*.bmp -[0-9]*x[0-9]*.jpeg

Now in your Apache configuration for your workstation, you need to set an AliasMatch for the uploads directory so that Apache can serve up these files. I use MAMP running as my user, so I don’t have to worry about permissions, but you should keep this in mind depending on your configuration.

<VirtualHost *:80>
    DocumentRoot "/Users/justinsilver/Dropbox/MySites/mysite"
    ServerName local.mysite.com
    
    # Reference the uploads folder in your Documents
    AliasMatch ^/wp-content/uploads/(.*) /Users/justinsilver/Documents/MySites/mysite/wp-content/uploads/$1
    
    <Directory /Users/justinsilver/Dropbox/MySites/mysite>
                AllowOverride All
    </Directory>
</VirtualHost>

With all of this done, you’re good to go as far as serving files, but WordPress will still be confused when it tries to write to the directory, since it uses an absolute path. To fix this, we can use a static defined in wp-config.php (which should be different than production anyway, considering you probably had to set overrides for WP_HOME and WP_SITEURL plus the local database configuration) and use that to selectively change the path to the uploads directory. Thus, add the absolute path to your wp-config.php like so, on the workstation:

define('WP_DEV_UPLOADS', '/Users/justinsilver/Documents/MySites/mysite/wp-content/uploads');

With this set, add the following to your theme’s functions.php, or your functions plugin (more on this at a different time) to hook to the “upload_dir” and change the uploads directory that WordPress uses.

if (defined('WP_DEV_UPLOADS')){
    if (is_dir(WP_DEV_UPLOADS)){
        // use directory outside of Dropbox for uploads so they dont go to Dropbox (uses Alias in VirtualHost)
        function wp_dev_upload_dir($upload_dir){ 
            $upload_dir['path']    = str_replace( $upload_dir['basedir'], WP_DEV_UPLOADS, $upload_dir['path'] );
            $upload_dir['basedir'] = WP_DEV_UPLOADS;
             
            return $upload_dir;
        }
        add_filter('upload_dir', 'wp_dev_upload_dir');
    } else {
        error_log('WP_DEV_UPLOADS is defined but does not exist: ' . WP_DEV_UPLOADS);
    }
}

The post Moving WordPress’ /wp-content/uploads out of your DocumentRoot appeared first on Justin Silver.

]]>
https://www.justinsilver.com/technology/wordpress/moving-wordpress-wp-contentuploads-out-of-your-documentroot/feed/ 5