WordPress Plugins Archives - Justin Silver https://www.justinsilver.com/category/technology/wordpress/wordpress-plugins/ Technology, Travel, and Pictures Fri, 03 Feb 2017 07:27:07 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.1 https://www.justinsilver.com/wp-content/uploads/2013/06/cropped-apple-touch-icon-160x160.png WordPress Plugins Archives - Justin Silver https://www.justinsilver.com/category/technology/wordpress/wordpress-plugins/ 32 32 WordPress Plugin: Custom Functions.php https://www.justinsilver.com/technology/wordpress/wordpress-plugins/wordpress-plugin-custom-functions-php/?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-plugin-custom-functions-php https://www.justinsilver.com/technology/wordpress/wordpress-plugins/wordpress-plugin-custom-functions-php/#respond Wed, 07 Nov 2012 19:04:23 +0000 http://justin.ag/?p=2814 One of the limitations of adding code to your theme’s function.php file is that these modifications are lost if you change themes, and another is that it is executed after your plugin files are...

The post WordPress Plugin: Custom Functions.php appeared first on Justin Silver.

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

One of the limitations of adding code to your theme’s function.php file is that these modifications are lost if you change themes, and another is that it is executed after your plugin files are loaded. This means that if you want to modify any behavior in your other plugins that call filtered functions but aren’t hooked to WordPress Actions, you’re out of luck.

I have worked around this on all my sites by creating a custom plugin to handle all of my non-theme specific additions. The only thing this plugin does is ensure that it is loaded first when you activate it, and then pulls in the contents of a functions.php file in the parent plugins directory. The result is that you have custom code running before any of your other plugins. The functions.php file is specifically not included, but it would just but a standard PHP file like the one in your theme.

This file is always up to date based on the trunk of my SVN repository.

custom-functions.php


The post WordPress Plugin: Custom Functions.php appeared first on Justin Silver.

]]>
https://www.justinsilver.com/technology/wordpress/wordpress-plugins/wordpress-plugin-custom-functions-php/feed/ 0
WordPress Plugin: validated-field-for-acf https://www.justinsilver.com/technology/wordpress/wordpress-plugins/wordpress-plugin-validated-field-for-acf/?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-plugin-validated-field-for-acf https://www.justinsilver.com/technology/wordpress/wordpress-plugins/wordpress-plugin-validated-field-for-acf/#comments Sun, 04 Nov 2012 14:23:27 +0000 http://justin.ag/?p=2792 This plugin will create an add-on field wrapper for Advanced Custom Fields (ACF in the WordPress Plugin Repository) that will give you additional validation functionality – PHP functions, regular expression, input field masking, and...

The post WordPress Plugin: validated-field-for-acf appeared first on Justin Silver.

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

This plugin will create an add-on field wrapper for Advanced Custom Fields (ACF in the WordPress Plugin Repository) that will give you additional validation functionality – PHP functions, regular expression, input field masking, and uniqueness (post type/meta key, meta key, or site-wide). You can find and install the plugin from the WordPress repository, located at http://wordpress.org/extend/plugins/validated-field-for-acf/.

UPDATED: This plugin is compatible with Advanced Custom Fields 4 & 5

The plugin code

This file is always up to date based on the trunk of the WordPress.org SVN repository.

validated_field.php

<?php
/*
Plugin Name: Advanced Custom Fields: Validated Field
Plugin URI: http://www.doublesharp.com/
Description: Server side validation, input masking and more for Advanced Custom Fields
Author: Justin Silver
Version: 1.7.7
Author URI: http://doublesharp.com/
*/

if ( ! defined( 'ACF_VF_VERSION' ) )
	define( 'ACF_VF_VERSION', '1.7.7' );

if ( !defined('ACF_VF_PLUGIN_FILE') )
	define( 'ACF_VF_PLUGIN_FILE', __FILE__ );

// Load the add-on field once the plugins have loaded, but before init (this is when ACF registers the fields)
if ( ! function_exists( 'register_acf_validated_field' ) ):	
	function register_acf_validated_field(){
		// create field
		include_once 'validated_field_v4.php';
	}
	function include_acf_validated_field(){
		// create field
		include_once 'validated_field_v5.php';
	}

	add_action( 'acf/include_fields', 'include_acf_validated_field' );
	add_action( 'acf/register_fields', 'register_acf_validated_field' );

	function load_textdomain_acf_vf() {
		load_plugin_textdomain( 'acf_vf', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
	}
	add_action( 'plugins_loaded', 'load_textdomain_acf_vf' );
endif;

validated_field_v4.php

<?php
if ( class_exists( 'acf_Field' ) && ! class_exists( 'acf_field_validated_field' ) ):
class acf_field_validated_field extends acf_field {
	// vars
	var $slug,
		$config,
		$settings,					// will hold info such as dir / path
		$defaults,					// will hold default field options
		$sub_defaults,				// will hold default sub field options
		$debug,						// if true, don't use minified and confirm form submit					
		$drafts,
		$frontend;

	/*
	*  __construct
	*
	*  Set name / label needed for actions / filters
	*
	*  @since	3.6
	*  @date	23/01/13
	*/
	function __construct(){
		// vars
		$this->slug 	= 'acf-validated-field';
		$this->strbool 	= array( 'true' => true, 'false' => false );
		$this->config 	= array(
			'acf_vf_debug' => array(
				'type' 		=> 'checkbox',
				'default' 	=> 'false',
				'label'  	=> __( 'Enable Debug', 'acf_vf' ),
				'help' 		=> __( 'Check this box to turn on debugging for Validated Fields.', 'acf_vf' ),
			),
			'acf_vf_drafts' => array(
				'type' 		=> 'checkbox',
				'default' 	=> 'true',
				'label'  	=> __( 'Enable Draft Validation', 'acf_vf' ),
				'help' 		=> __( 'Check this box to enable Draft validation globally, or uncheck to allow it to be set per field.', 'acf_vf' ),
			),
			'acf_vf_frontend' => array(
				'type' 		=> 'checkbox',
				'default' 	=> 'false',
				'label'  	=> __( 'Enable Front-End Validation', 'acf_vf' ),
				'help'		=> __( 'Check this box to turn on validation for front-end forms created with', 'acf_vf' ) . ' <code>acf_form()</code>.',
			),
			'acf_vf_frontend_css' => array(
				'type' 		=> 'checkbox',
				'default' 	=> 'true',
				'label'  	=> __( 'Enqueue Admin CSS on Front-End', 'acf_vf' ),
				'help' 		=> __( 'Uncheck this box to turn off "colors-fresh" admin theme enqueued by', 'acf_vf' ) . ' <code>acf_form_head()</code>.',
			),
		);
		$this->name		= 'validated_field';
		$this->label 	= __( 'Validated Field', 'acf_vf' );
		$this->category	= __( 'Basic', 'acf' );
		$this->drafts	= $this->option_value( 'acf_vf_drafts' );
		$this->frontend = $this->option_value( 'acf_vf_frontend' );
		$this->frontend_css = $this->option_value( 'acf_vf_frontend_css' );
		$this->debug 	= $this->option_value( 'acf_vf_debug' );

		$this->defaults = array(
			'read_only' => false,
			'mask'		=> '',
			'mask_autoclear' => true,
			'mask_placeholder' => '_',
			'function'	=> 'none',
			'pattern'	=> '',
			'message'	=>  __( 'Validation failed.', 'acf_vf' ),
			'unique'	=> 'non-unique',
			'unique_statuses' => apply_filters( 'acf_vf/unique_statuses', array( 'publish', 'future' ) ),
			'drafts'	=> true,
		);

		$this->sub_defaults = array(
			'type'		=> '',
			'key'		=> '',
			'name'		=> '',
			'_name'		=> '',
			'id'		=> '',
			'value'		=> '',
			'field_group' => '',
		);

		$this->input_defaults = array(
			'id'		=> '',
			'value'		=> '',
		);

		// do not delete!
		parent::__construct();

		// settings
		$this->settings = array(
			'path'		=> apply_filters( 'acf/helpers/get_path', __FILE__ ),
			'dir'		=> apply_filters( 'acf/helpers/get_dir', __FILE__ ),
			'version'	=> ACF_VF_VERSION,
		);

		if ( is_admin() || $this->frontend ){ // admin actions
			// ACF 5.0+ http://www.advancedcustomfields.com/resources/filters/acf-validate_value/
			add_action( 'wp_ajax_validate_fields', array( $this, 'ajax_validate_fields' ) );

			add_action( $this->frontend? 'wp_head' : 'admin_head', array( $this, 'input_admin_head' ) );
			if ( ! is_admin() && $this->frontend ){
				if ( ! $this->frontend_css ){
					add_action( 'acf/input/admin_enqueue_scripts',  array( $this, 'remove_acf_form_style' ) );
				}

				add_action( 'wp_ajax_nopriv_validate_fields', array( $this, 'ajax_validate_fields' ) );
				add_action( 'wp_head', array( $this, 'ajaxurl' ), 1 );
				add_action( 'wp_head', array( $this, 'input_admin_enqueue_scripts' ), 1 );
			}
			if ( is_admin() ){
				add_action( 'admin_init', array( $this, 'admin_register_settings' ) );
				add_action( 'admin_menu', array( $this, 'admin_add_menu' ), 11 );
			}
		}
	}

	function option_value( $key ){
		return ( false !== $option = get_option( $key ) )?
			$option == $this->config[$key]['default'] :
			$this->strbool[$this->config[$key]['default']];
	}

	function ajaxurl(){
		?>
		<script type="text/javascript">var ajaxurl = '<?php echo admin_url('admin-ajax.php'); ?>';</script>
		<?php
	}

	function admin_add_menu(){
		$page = add_submenu_page( 'edit.php?post_type=acf', __( 'Validated Field Settings', 'acf_vf' ), __( 'Validated Field Settings', 'acf_vf' ), 'manage_options', $this->slug, array( &$this,'admin_settings_page' ) );
	}

	function admin_register_settings(){
		foreach ( $this->config as $key => $value ) {
			register_setting( $this->slug, $key );
		}
	}

	function admin_settings_page(){
		?>
		<div class="wrap">
		<h2>Validated Field Settings</h2>
		<form method="post" action="options.php">
		    <?php settings_fields( $this->slug ); ?>
		    <?php do_settings_sections( $this->slug ); ?>
			<table class="form-table">
			<?php foreach ( $this->config as $key => $value ) { ?>
				<tr valign="top">
					<th scope="row"><?php echo $value['label']; ?></th>
					<td>
						<input type="checkbox" id="<?php echo $key; ?>" name="<?php echo $key; ?>" value="<?php echo $value['default']; ?>" <?php if ( $this->option_value( $key ) ) echo 'checked'; ?>/>
						<small><em><?php echo $value['help']; ?></em></small>
					</td>
				</tr>
			<?php } ?>
			</table>
		    <?php submit_button(); ?>
		</form>
		</div>
    	<?php
	}

	function remove_acf_form_style(){
		wp_dequeue_style( array( 'colors-fresh' ) );
	}

	function setup_field( $field ){
		// setup booleans, for compatibility
		$field['read_only'] = ( false == $field['read_only'] || 'false' === $field['read_only'] )? false : true;
		$field['drafts'] = ( false == $field['drafts'] || 'false' === $field['drafts'] )? false : true;
		$field =  array_merge( $this->defaults, $field );


		$sub_field = isset( $field['sub_field'] )? 
			$field['sub_field'] :	// already set up
			array();				// create it
		// mask the sub field as the parent by giving it the same key values
		foreach( array( 'key', 'name', '_name', 'id', 'value', 'field_group' ) as $key ){
			$sub_field[$key] = isset( $field[$key] )? $field[$key] : '';
		}

		$field['sub_field'] = array_merge( $this->sub_defaults, $sub_field );

		return $field;
	}

	function setup_sub_field( $field ){
		return $field['sub_field'];
	}

	/*
	*  get_post_statuses()
	*
	*  Get the various post statuses that have been registered
	*
	*  @type		function
	*
	*/
	function get_post_statuses() {
		global $wp_post_statuses;
		return $wp_post_statuses;
	}

	/*
	*  ajax_validate_fields()
	*
	*  Parse the input when a page is submitted to determine if it is valid or not.
	*
	*  @type		ajax action
	*
	*/
	function ajax_validate_fields() {
		$post_id = isset( $_REQUEST['post_id'] )?				// the submitted post_id
			$_REQUEST['post_id'] : 
			0;
		$post_type = get_post_type( $post_id );					// the type of the submitted post
		$frontend = isset( $_REQUEST['frontend'] )?
			$_REQUEST['frontend'] :
			false;

		$click_id =  isset( $_REQUEST['click_id'] )? 			// the ID of the clicked element, for drafts/publish
			$_REQUEST['click_id'] : 
			'publish';

		// the validated field inputs to process
		$inputs = ( isset( $_REQUEST['fields'] ) && is_array( $_REQUEST['fields'] ) )? 
			$_REQUEST['fields'] : 
			array();

		header( 'HTTP/1.1 200 OK' );							// be positive!
		header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) );
		$return_fields = array();								// JSON response to the client
		foreach ( $inputs as $i=>$input ){						// loop through each field
			// input defaults
			$input = array_merge( $this->input_defaults, $input );
			$valid = true;										// wait for a any test to fail

			// extract the field key
			preg_match( '/\\[([^\\]]*?)\\](\\[(\d*?)\\]\\[([^\\]]*?)\\])?/', $input['id'], $matches );
			$key = isset( $matches[1] )? $matches[1] : false;	// the key for this ACF
			$index = isset( $matches[3] )? $matches[3] : false;	// the field index, if it is a repeater
			$sub_key = isset( $matches[4] )? $matches[4] : false; // the key for the sub field, if it is a repeater

			// load the field config, set defaults
			$field = $this->setup_field( get_field_object( $key, $post_id ) );
			
			// if it's a repeater field, get the validated field so we can do meta queries...
			if ( $is_repeater = ( 'repeater' == $field['type'] && $index ) ){
				foreach ( $field['sub_fields'] as $repeater ){
					$repeater = $this->setup_field( $repeater );
					$sub_field = $this->setup_sub_field( $repeater );
					if ( $sub_key == $sub_field['key'] ){
						$parent_field = $field;					// we are going to map down a level, but track the top level field
						$field = $repeater;						// the '$field' should be the Validated Field
						break;
					}
					$sub_field = false;							// in case it isn't the right one
				}
			} else {
				// the wrapped field
				$sub_field = $this->setup_sub_field( $field );
			}

			$value = $input['value'];							// the submitted value
			if ( $field['required'] && empty( $value ) ){
				continue;										// let the required field handle it
			}

			if ( $click_id != 'publish' && !$field['drafts'] ){
				continue;										// we aren't publishing and we don't want to validate drafts
			}
			
			$function = $field['function'];						// what type of validation?
			$pattern = $field['pattern'];						// string to use for validation
			$message = $field['message'];						// failure message to return to the UI
			if ( ! empty( $function ) && ! empty( $pattern ) ){
				switch ( $function ){							// only run these checks if we have a pattern
					case 'regex':								// check for any matches to the regular expression
						$pattern_fltr = '/' . str_replace( "/", "\/", $pattern ) . '/';
						if ( ! preg_match( $pattern_fltr, $value ) ){
							$valid = false;						// return false if there are no matches
						}
						break;
					case 'sql':									// todo: sql checks?
						break;
					case 'php':									// this code is a little tricky, one bad eval() can break the lot. needs a nonce.
						$this_key = $field['name'];
						if ( $is_repeater ) $this_key .= '_' . $index . '_' . $sub_sub_field['name'];

						// get the fields based on the keys and then index by the meta value for easy of use
						$input_fields = array();
						foreach ( $input_fields as $key => $val ){
							if ( $false !== ( $input_field = get_field_object( $key, $post_id ) ) ){
								$meta_key = $input_field['name'];
								$input_fields[$meta_key] = array(
									'field'=>$input_field,
									'value'=>$val,
									'prev_val'=>get_post_meta( $post_id, $meta_key, true )
								);
							}
						}

						// it gets tricky but we are trying to account for an capture bad php code where possible
						$pattern = addcslashes( trim( $pattern ), '$' );
						if ( substr( $pattern, -1 ) != ';' ) $pattern.= ';';

						$value = addslashes( $value );

						// not yet saved to the database, so this is the previous value still
						$prev_value = addslashes( get_post_meta( $post_id, $this_key, true ) );

						$function_name = 'validate_' . preg_replace( '~[\\[\\]]+~', '_', $input['id'] ) . 'function';
						
						$php = <<<PHP
if ( ! function_exists( '$function_name' ) ):
function $function_name( \$args, &\$message ){
	extract( \$args );
	try {
		\$code = <<<INNERPHP
		$pattern return true;
INNERPHP;
		return @eval( \$code );
	} catch ( Exception \$e ){
		\$message = "Error: ".\$e->getMessage(); return false;
	}
}
endif; // function_exists
\$valid = $function_name( array( 'post_id'=>'$post_id', 'post_type'=>'$post_type', 'this_key'=>'$this_key', 'value'=>'$value', 'prev_value'=>'$prev_value', 'inputs'=>\$input_fields ), \$message );
PHP;

						if ( true !== eval( $php ) ){			// run the eval() in the eval()
							$error = error_get_last();			// get the error from the eval() on failure
							// check to see if this is our error or not.
							if ( strpos( $error['file'], basename( __FILE__ ) ) && strpos( $error['file'], "eval()'d code" ) ){
								preg_match( '/eval\\(\\)\'d code\\((\d+)\\)/', $error['file'], $matches );
								$message = __( 'PHP Error', 'acf_vf' ) . ': ' . $error['message'] . ', line ' . $matches[1] . '.';
								$valid = false;
							} 
						}
						// if a string is returned, return it as the error.
						if ( is_string( $valid ) ){
							$message = $valid;
							$valid = false;
						}
						break;
				}
			} elseif ( ! empty( $function ) && $function != 'none' ) {
				$message = __( 'This field\'s validation is not properly configured.', 'acf_vf' );
				$valid = false;
			}
				
			$unique = $field['unique'];
			if ( $valid && ! empty( $value ) && ! empty( $unique ) && $unique != 'non-unique' ){
				global $wpdb;
				$status_in = "'" . implode( "','", $field['unique_statuses'] ) . "'";

				// WPML compatibility, get code list of active languages
				if ( function_exists( 'icl_object_id' ) ){
					$languages = $wpdb->get_results( "SELECT code FROM {$wpdb->prefix}icl_languages WHERE active = 1", ARRAY_A );
					$wpml_ids = array();
					foreach( $languages as $lang ){
						$wpml_ids[] = (int) icl_object_id( $post_id, $post_type, true, $lang['code'] );
					}
					$post_ids = array_unique( $wpml_ids );
				} else {
					$post_ids = array( (int) $post_id );
				}

				$sql_prefix = "SELECT pm.meta_id AS meta_id, pm.post_id AS post_id, p.post_title AS post_title FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id AND p.post_status IN ($status_in)";
				switch ( $unique ){
					case 'global': 
						// check to see if this value exists anywhere in the postmeta table
						$sql = $wpdb->prepare( 
							"{$sql_prefix} AND post_id NOT IN ([NOT_IN]) WHERE ( meta_value = %s OR meta_value LIKE %s )",
							$value,
							'%"' . $wpdb->esc_like( $value ) . '"%'
						);
						break;
					case 'post_type':
						// check to see if this value exists in the postmeta table with this $post_id
						$sql = $wpdb->prepare( 
							"{$sql_prefix} AND p.post_type = %s AND post_id NOT IN ([NOT_IN]) WHERE ( meta_value = %s OR meta_value LIKE %s )", 
							$post_type,
							$value,
							'%"' . $wpdb->esc_like( $value ) . '"%'
						);
						break;
					case 'post_key':
						// check to see if this value exists in the postmeta table with both this $post_id and $meta_key
						if ( $is_repeater ){
							$this_key = $parent_field['name'] . '_' . $index . '_' . $field['name'];
							$meta_key = $parent_field['name'] . '_%_' . $field['name'];
							$sql = $wpdb->prepare(
								"{$sql_prefix} AND p.post_type = %s WHERE ( ( post_id NOT IN ([NOT_IN]) AND meta_key != %s AND meta_key LIKE %s ) OR ( post_id NOT IN ([NOT_IN]) AND meta_key LIKE %s ) ) AND ( meta_value = %s OR meta_value LIKE %s )", 
								$post_type,
								$this_key,
								$meta_key,
								$meta_key,
								$value,
								'%"' . $wpdb->esc_like( $value ) . '"%'
							);
						} else {
							$sql = $wpdb->prepare( 
								"{$sql_prefix} AND p.post_type = %s AND post_id NOT IN ([NOT_IN]) WHERE meta_key = %s AND ( meta_value = %s OR meta_value LIKE %s )", 
								$post_type,
								$field['name'],
								$value,
								'%"' . $wpdb->esc_like( $value ) . '"%'
							);
						}
						break;
					default:
						// no dice, set $sql to null
						$sql = null;
						break;
				}

				// Only run if we hit a condition above
				if ( ! empty( $sql ) ){

					// Update the [NOT_IN] values
					$sql = $this->prepare_not_in( $sql, $post_ids );

					// Execute the SQL
					$rows = $wpdb->get_results( $sql );
					if ( count( $rows ) ){
						// We got some matches, but there might be more than one so we need to concatenate the collisions
						$conflicts = "";
						foreach ( $rows as $row ){
							$permalink = ( $frontend )? get_permalink( $row->post_id ) : "/wp-admin/post.php?post={$row->post_id}&action=edit";
							$conflicts.= "<a href='{$permalink}' style='color:inherit;text-decoration:underline;'>{$row->post_title}</a>";
							if ( $row !== end( $rows ) ) $conflicts.= ', ';
						}
						$message = __( 'The value', 'acf_vf' ) . " '$value' " . __( 'is already in use by', 'acf_vf' ) . " {$conflicts}.";
						$valid = false;
					}
				}
			}

			$return_fields[] = array(
				'id'		=> $input['id'],
				'message'	=> true === $valid? '' : ! empty( $message )? htmlentities( $message, ENT_NOQUOTES, 'UTF-8' ) : __( 'Validation failed.', 'acf_vf' ),
				'valid'		=> $valid,
			);
		}
		
		// Send the results back to the browser as JSON
		die( version_compare( phpversion(), '5.3', '>=' )? 
			json_encode( $return_fields, $this->debug? JSON_PRETTY_PRINT : 0 ) :
			json_encode( $return_fields ) );
	}

	private function prepare_not_in( $sql, $post_ids ){
		global $wpdb;
		$not_in_count = substr_count( $sql, '[NOT_IN]' );
		if ( $not_in_count > 0 ){
			$args = array( str_replace( '[NOT_IN]', implode( ', ', array_fill( 0, count( $post_ids ), '%d' ) ), str_replace( '%', '%%', $sql ) ) );
			for ( $i=0; $i < substr_count( $sql, '[NOT_IN]' ); $i++ ) { 
				$args = array_merge( $args, $post_ids );
			}
			$sql = call_user_func_array( array( $wpdb, 'prepare' ), $args );
		}
		return $sql;
	}

	/*
	*  create_options()
	*
	*  Create extra options for your field. This is rendered when editing a field.
	*  The value of $field['name'] can be used (like below) to save extra data to the $field
	*
	*  @type	action
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	$field	- an array holding all the field's data
	*/
	function create_options( $field ){
		// defaults?
		$field = $this->setup_field( $field );

		// key is needed in the field names to correctly save the data
		$key = $field['name'];
		$html_key = preg_replace( '~[\\[\\]]+~', '_', $key );
		$sub_field = $this->setup_sub_field( $field );
		$sub_field['name'] = $key . '][sub_field';

		// get all of the registered fields for the sub type drop down
		$fields_names = apply_filters( 'acf/registered_fields', array() );

		// remove types that don't jive well with this one
		unset( $fields_names[__( 'Layout', 'acf' )] );
		unset( $fields_names[__( 'Basic', 'acf' )][ 'validated_field' ] );

		?>
		<tr class="field_option field_option_<?php echo $this->name; ?> field_option_<?php echo $this->name; ?>_readonly" id="field_option_<?php echo $html_key; ?>_readonly">
			<td class="label"><label><?php _e( 'Read Only?', 'acf_vf' ); ?> </label>
			</td>
			<td><?php 
			do_action( 'acf/create_field', array(
				'type'	=> 'radio',
				'name'	=> 'fields['.$key.'][read_only]',
				'value'	=> ( false == $field['read_only'] || 'false' === $field['read_only'] )? 'false' : 'true',
				'choices' => array(
					'true'	=> __( 'Yes', 'acf_vf' ),
					'false' => __( 'No', 'acf_vf' ),
				),
				'class' => 'horizontal'
			));
			?>
			</td>
		</tr>
		<tr class="field_option field_option_<?php echo $this->name; ?> field_option_<?php echo $this->name; ?>_readonly" id="field_option_<?php echo $html_key; ?>_drafts">
			<td class="label"><label><?php _e( 'Validate Drafts/Preview?', 'acf_vf' ); ?> </label>
			</td>
			<td><?php 
			do_action( 'acf/create_field', array(
				'type'	=> 'radio',
				'name'	=> 'fields['.$key.'][drafts]',
				'value'	=> ( false == $field['drafts'] || 'false' === $field['drafts'] )? 'false' : 'true',
				'choices' => array(
					'true'	=> __( 'Yes', 'acf_vf' ),
					'false' => __( 'No', 'acf_vf' ),
				),
				'class' => 'horizontal'
			));

			if ( ! $this->drafts ){
				echo '<em>';
				_e( 'Warning', 'acf_vf' );
				echo ': <code>ACF_VF_DRAFTS</code> ';
				_e( 'has been set to <code>false</code> which overrides field level configurations', 'acf_vf' );
				echo '.</em>';
			}
			?>
			</td>
		</tr>
		<tr class="field_option field_option_<?php echo $this->name; ?>">
			<td class="label"><label><?php _e( 'Validated Field', 'acf_vf' ); ?> </label>
			<script type="text/javascript">
			</script>
			</td>
			<td>
				<div class="sub-field">
					<div class="fields">
						<div class="field sub_field acf-sub_field">
							<div class="field_form">
								<table class="acf_input widefat">
									<tbody>
										<tr class="field_type">
											<td class="label"><label><span class="required">*</span> <?php _e( 'Field Type', 'acf' ); ?>
											</label></td>
											<td><?php
											// Create the drop down of field types
											do_action( 'acf/create_field', array(
												'type'	=> 'select',
												'name'	=> 'fields[' . $key . '][sub_field][type]',
												'value'	=> $sub_field['type'],
												'class'	=> 'type',
												'choices' => $fields_names
											));

											// Create the default sub field settings
											do_action( 'acf/create_field_options', $sub_field );
											?>
											</td>
										</tr>
										<tr class="field_save">
											<td class="label">
											</td>
											<td></td>
										</tr>
									</tbody>
								</table>
							</div>
							<!-- End Form -->
						</div>
					</div>
				</div>
			</td>
		</tr>
		<tr class="field_option field_option_<?php echo $this->name; ?> non_read_only">
			<td class="label"><label><?php _e( 'Input Mask', 'acf_vf' ); ?></label></td>
			<td><?php _e( 'Use &#39;a&#39; to match A-Za-z, &#39;9&#39; to match 0-9, and &#39;*&#39; to match any alphanumeric.', 'acf_vf' ); ?> 
				<a href="http://digitalbush.com/projects/masked-input-plugin/" target="_new"><?php _e( 'More info', 'acf_vf' ); ?></a>.
				<?php 
				do_action( 'acf/create_field', 
					array(
						'type'	=> 'text',
						'name'	=> 'fields[' . $key . '][mask]',
						'value'	=> $field['mask'],
						'class'	=> 'input-mask'
					)
				);
				?><br />
				<label for="">Autoclear invalid values: </label>
				<?php 
				do_action( 'acf/create_field', 
					array(
						'type'	=> 'radio',
						'name'	=> 'fields[' . $key . '][mask_autoclear]',
						'value'	=> $field['mask_autoclear'],
						'layout'	=>	'horizontal',
						'choices' => array(
							true => 'Yes',
							false => 'No'
						),
						'class'	=> 'mask-settings'
					)
				);
				?><br />
				<label for="">Input mask placeholder: </label>
				<?php 
				do_action( 'acf/create_field', 
					array(
						'type'	=> 'text',
						'name'	=> 'fields[' . $key . '][mask_placeholder]',
						'value'	=> $field['mask_placeholder'],
						'class'	=> 'mask-settings'
					)
				);
				?><br />
				<strong><em><?php _e( 'Input masking is not compatible with the "number" field type!', 'acf_vf' ); ?><em></strong>
			</td>
		</tr>
		<tr class="field_option field_option_<?php echo $this->name; ?> non_read_only">
			<td class="label"><label><?php _e( 'Validation: Function', 'acf_vf' ); ?></label></td>
			<td><?php _e( "How should the field be server side validated?", 'acf_vf' ); ?><br />
				<?php 
				do_action( 'acf/create_field', 
					array(
						'type'	=> 'select',
						'name'	=> 'fields[' . $key . '][function]',
						'value'	=> $field['function'],
						'choices' => array(
							'none'	=> __( 'None', 'acf_vf' ),
							'regex' => __( 'Regular Expression', 'acf_vf' ),
							//'sql'	=> __( 'SQL Query', 'acf_vf' ),
							'php'	=> __( 'PHP Statement', 'acf_vf' ),
						),
						'optgroup' => true,
						'multiple' => '0',
						'class' => 'validated_select',
					)
				);
				?>
			</td>
		</tr>
		<tr class="field_option field_option_<?php echo $this->name; ?> field_option_<?php echo $this->name; ?>_validation non_read_only" id="field_option_<?php echo $html_key; ?>_validation">
			<td class="label"><label><?php _e( 'Validation: Pattern', 'acf_vf' ); ?></label>
			</td>
			<td>
				<div id="validated-<?php echo $html_key; ?>-info">
					<div class='validation-type regex'>
						<?php _e( 'Pattern match the input using', 'acf_vf' ); ?> <a href="http://php.net/manual/en/function.preg-match.php" target="_new">PHP preg_match()</a>.
						<br />
					</div>
					<div class='validation-type php'>
						<ul>
							<li><?php _e( "Use any PHP code and return true or false. If nothing is returned it will evaluate to true.", 'acf_vf' ); ?></li>
							<li><?php _e( 'Available variables', 'acf_vf' ); ?> - <b>$post_id</b>, <b>$post_type</b>, <b>$name</b>, <b>$value</b>, <b>$prev_value</b>, <b>&amp;$message</b> (<?php _e('returned to UI', 'acf_vf' ); ?>).</li>
							<li><?php _e( 'Example', 'acf_vf' ); ?>: <code>if ( empty( $value ) || $value == "xxx" ){  return "{$value} is not allowed"; }</code></li>
						</ul>
					</div>
					<div class='validation-type sql'>
						<?php _e( 'SQL', 'acf_vf' ); ?>.
						<br />
					</div>
				</div> 
				<?php
				do_action( 'acf/create_field', array(
					'type'	=> 'textarea',
					'name'	=> 'fields['.$key.'][pattern]',
					'value'	=> $field['pattern'],
					'class' => 'editor'					 
				)); 
				?>
				<div id="acf-field-<?php echo $html_key; ?>_editor" style="height:200px;"><?php echo $field['pattern']; ?></div>

			</td>
		</tr>
		<tr class="field_option field_option_<?php echo $this->name; ?> field_option_<?php echo $this->name; ?>_message non_read_only" id="field_option_<?php echo $html_key; ?>_message">
			<td class="label"><label><?php _e( 'Validation: Error Message', 'acf_vf' ); ?></label>
			</td>
			<td><?php 
			do_action( 'acf/create_field', 
				array(
					'type'	=> 'text',
					'name'	=> 'fields['.$key.'][message]',
					'value'	=> $field['message'],
				)
			); 
			?>
			</td>
		</tr>
		<tr class="field_option field_option_<?php echo $this->name; ?> non_read_only">
			<td class="label"><label><?php _e( 'Unique Value?', 'acf_vf' ); ?> </label>
			</td>
			<td>
			<div id="validated-<?php echo $html_key; ?>-unique">
			<p><?php _e( 'Make sure this value is unique for...', 'acf_vf' ); ?><br/>
			<?php 

			do_action( 'acf/create_field', 
				array(
					'type'	=> 'select',
					'name'	=> 'fields[' . $key . '][unique]',
					'value'	=> $field['unique'],
					'choices' => array(
						'non-unique'=> __( 'Non-Unique Value', 'acf_vf' ),
						'global'	=> __( 'Unique Globally', 'acf_vf' ),
						'post_type'	=> __( 'Unique For Post Type', 'acf_vf' ),
						'post_key'	=> __( 'Unique For Post Type', 'acf_vf' ) . ' + ' . __( 'Field/Meta Key', 'acf_vf' ),
					),
					'optgroup'	=> false,
					'multiple'	=> '0',
					'class'		=> 'validated-select',
				)
			);
			?>
			</p>
			<div class="unique_statuses">
			<p><?php _e( 'Unique Value: Apply to...?', 'acf_vf'); ?><br/>
			<?php
			$statuses = $this->get_post_statuses();
			$choices = array();
			foreach ( $statuses as $value => $status ) {
				$choices[$value] = $status->label;
			}

			do_action( 'acf/create_field', 
				array(
					'type'	=> 'checkbox',
					'name'	=> 'fields['.$key.'][unique_statuses]',
					'value'	=> $field['unique_statuses'],
					'choices' => $choices,
				)
			); 
			?></p>
			</div>
			</div>
			<script type="text/javascript">
			jQuery(document).ready(function(){
				jQuery("#acf-field-<?php echo $html_key; ?>_pattern").hide();

		    	ace.require("ace/ext/language_tools");
				ace.config.loadModule('ace/snippets/snippets');
				ace.config.loadModule('ace/snippets/php');
				ace.config.loadModule("ace/ext/searchbox");

				var editor = ace.edit("acf-field-<?php echo $html_key; ?>_editor");
				editor.setTheme("ace/theme/monokai");
				editor.getSession().setMode("ace/mode/text");
				editor.getSession().on('change', function(e){
					var val = editor.getValue();
					var func = jQuery('#acf-field-<?php echo $html_key; ?>_function').val();
					if (func=='php'){
						val = val.substr(val.indexOf('\n')+1);
					} else if (func=='regex'){
						if (val.indexOf('\n')>0){
							editor.setValue(val.trim().split('\n')[0]);
						}
					}
					jQuery("#acf-field-<?php echo $html_key; ?>_pattern").val(val);
				});
				jQuery("#acf-field-<?php echo $html_key; ?>_editor").data('editor', editor);

				jQuery('#acf-field-<?php echo $html_key; ?>_function').on('change',function(){
					jQuery('#validated-<?php echo $html_key; ?>-info div').hide(300);
					jQuery('#validated-<?php echo $html_key; ?>-info div.'+jQuery(this).val()).show(300);
					if (jQuery(this).val()!='none'){
						jQuery('#validated-<?php echo $html_key; ?>-info .field_option_<?php echo $this->name; ?>_validation').show();
					} else {
						jQuery('#validated-<?php echo $html_key; ?>-info .field_option_<?php echo $this->name; ?>_validation').hide();
					}
					var sPhp = '<'+'?'+'php';
					var editor = jQuery('#acf-field-<?php echo $html_key; ?>_editor').data('editor');
					var val = editor.getValue();
					if (jQuery(this).val()=='none'){
						jQuery('#field_option_<?php echo $html_key; ?>_validation, #field_option_<?php echo $html_key; ?>_message').hide(300);
					} else {
						if (jQuery(this).val()=='php'){
							if (val.indexOf(sPhp)!=0){
								editor.setValue(sPhp +'\n' + val);
							}
							editor.getSession().setMode("ace/mode/php");
							jQuery("#acf-field-<?php echo $html_key; ?>_editor").css('height','420px');

							editor.setOptions({
								enableBasicAutocompletion: true,
								enableSnippets: true,
								enableLiveAutocompletion: true
							});
						} else {
							if (val.indexOf(sPhp)==0){
								editor.setValue(val.substr(val.indexOf('\n')+1));
							}
							editor.getSession().setMode("ace/mode/text");
							jQuery("#acf-field-<?php echo $html_key; ?>_editor").css('height','18px');
							editor.setOptions({
								enableBasicAutocompletion: false,
								enableSnippets: false,
								enableLiveAutocompletion: false
							});
						}
						editor.resize();
						editor.gotoLine(1, 1, false);
						jQuery('#field_option_<?php echo $html_key; ?>_validation, #field_option_<?php echo $html_key; ?>_message').show(300);
					}
				});

				jQuery('#acf-field-<?php echo $html_key; ?>_unique').on('change',function(){
					var unqa = jQuery('#validated-<?php echo $html_key; ?>-unique .unique_statuses');
					var val = jQuery(this).val();
					if (val=='non-unique'||val=='') { unqa.hide(300); } else { unqa.show(300); }
				});

				// update ui
				jQuery('#acf-field-<?php echo $html_key; ?>_function').trigger('change');
				jQuery('#acf-field-<?php echo $html_key; ?>_unique').trigger('change');
				jQuery('#acf-field-<?php echo $html_key; ?>_sub_field_type').trigger('change');
			});
			</script>
			</td>
		</tr>
		<?php
	}

	/*
	*  create_field()
	*
	*  Create the HTML interface for your field
	*
	*  @param	$field - an array holding all the field's data
	*
	*  @type	action
	*  @since	3.6
	*  @date	23/01/13
	*/
	function create_field( $field ){
		global $post, $pagenow;
		$is_new = $pagenow=='post-new.php';
		$field = $this->setup_field( $field );
		$sub_field = $this->setup_sub_field( $field );

		// filter to determine if this field should be rendered or not
		$render_field = apply_filters( 'acf_vf/render_field', true, $field, $is_new );

		// if it is not rendered, hide the label with CSS
		if ( ! $render_field ): ?>
			<style>div[data-key="<?php echo $sub_field['key']; ?>"] { display: none; }</style>
		<?php
		// if it is shown either render it normally or as read-only
		else : ?>
			<div class="validated-field">
				<?php
				if ( $field['read_only'] ){
					?>
					<p><?php 
					ob_start();
					do_action( 'acf/create_field', $sub_field ); 
					$contents = ob_get_contents();
					$contents = preg_replace("~<(input|textarea|select)~", "<\${1} disabled=true readonly", $contents );
					$contents = preg_replace("~acf-hidden~", "acf-hidden acf-vf-readonly", $contents );
					ob_end_clean();
					echo $contents;
					?></p>
					<?php
				} else {
					do_action( 'acf/create_field', $sub_field ); 
				}
				?>
			</div>
			<?php
			if ( ! empty( $field['mask'] ) && ( $is_new || ( isset( $field['read_only'] ) && ! $field['read_only'] ) ) ) { ?>
				<script type="text/javascript">
					jQuery(function($){
						$('div[data-field_key="<?php echo $field['key']; ?>"] input').each( function(){
							$(this).mask("<?php echo $field['mask']?>", {
								autoclear: <?php echo isset( $field['mask_autoclear'] ) && empty( $field['mask_autoclear'] )? 'false' : 'true'; ?>,
								placeholder: '<?php echo isset( $field['mask_placeholder'] )? $field['mask_placeholder'] : '_'; ?>'
							});
						});
					});
				</script>
			<?php
			}
		endif;
	}

	/*
	*  input_admin_enqueue_scripts()
	*
	*  This action is called in the admin_enqueue_scripts action on the edit screen where your field is created.
	*  Use this action to add css + javascript to assist your create_field() action.
	*
	*  $info	http://codex.wordpress.org/Plugin_API/Action_Reference/admin_enqueue_scripts
	*  @type	action
	*  @since	3.6
	*  @date	23/01/13
	*/
	function input_admin_enqueue_scripts(){
		// register acf scripts
		$min = ( ! $this->debug )? '.min' : '';

		wp_register_script( 'acf-validated-field', plugins_url( "js/input{$min}.js", __FILE__ ), array( 'jquery' ), $this->settings['version'] );
		wp_register_script( 'jquery-masking', plugins_url( "js/jquery.maskedinput{$min}.js", __FILE__ ), array( 'jquery' ), $this->settings['version']);
		wp_register_script( 'sh-core', plugins_url( 'js/shCore.js', __FILE__ ), array( 'acf-input' ), $this->settings['version'] );
		wp_register_script( 'sh-autoloader', plugins_url( 'js/shAutoloader.js', __FILE__ ), array( 'sh-core' ), $this->settings['version']);
		
		// enqueue scripts
		wp_enqueue_script( array(
			'jquery',
			'jquery-ui-core',
			'jquery-ui-tabs',
			'jquery-masking',
			'acf-validated-field',
		));
		if ( $this->debug ){ 
			add_action( $this->frontend? 'wp_head' : 'admin_head', array( &$this, 'debug_head' ), 20 );
		}
		if ( ! $this->drafts ){ 
			add_action( $this->frontend? 'wp_head' : 'admin_head', array( &$this, 'drafts_head' ), 20 );
		}
		if ( $this->frontend && ! is_admin() ){
			add_action( 'wp_head', array( &$this, 'frontend_head' ), 20 );
		}
	}

	function debug_head(){
		// set debugging for javascript
		echo '<script type="text/javascript">vf.debug=true;</script>';
	}

	function drafts_head(){
		// don't validate drafts for anything
		echo '<script type="text/javascript">vf.drafts=false;</script>';
	}

	function frontend_head(){
		// indicate that this is validating the front end
		echo '<script type="text/javascript">vf.frontend=true;</script>';
	}

	/*
	*  input_admin_head()
	*
	*  This action is called in the admin_head action on the edit screen where your field is created.
	*  Use this action to add css and javascript to assist your create_field() action.
	*
	*  @info	http://codex.wordpress.org/Plugin_API/Action_Reference/admin_head
	*  @type	action
	*  @since	3.6
	*  @date	23/01/13
	*/
	function input_admin_head(){
		wp_enqueue_style( 'font-awesome', plugins_url( 'css/font-awesome/css/font-awesome.min.css', __FILE__ ), array(), '4.2.0' ); 
		wp_enqueue_style( 'acf-validated_field', plugins_url( 'css/input.css', __FILE__ ), array( 'acf-input' ), ACF_VF_VERSION ); 

	}
	/*
	*  field_group_admin_enqueue_scripts()
	*
	*  This action is called in the admin_enqueue_scripts action on the edit screen where your field is edited.
	*  Use this action to add css + javascript to assist your create_field_options() action.
	*
	*  $info	http://codex.wordpress.org/Plugin_API/Action_Reference/admin_enqueue_scripts
	*  @type	action
	*  @since	3.6
	*  @date	23/01/13
	*/
	function field_group_admin_enqueue_scripts(){
		wp_enqueue_script( 'ace-editor', plugins_url( 'js/ace/ace.js', __FILE__ ), array(), '1.1.7' );
		wp_enqueue_script( 'ace-ext-language_tools', plugins_url( 'js/ace/ext-language_tools.js', __FILE__ ), array(), '1.1.7' );
	}

	/*
	*  field_group_admin_head()
	*
	*  This action is called in the admin_head action on the edit screen where your field is edited.
	*  Use this action to add css and javascript to assist your create_field_options() action.
	*
	*  @info	http://codex.wordpress.org/Plugin_API/Action_Reference/admin_head
	*  @type	action
	*  @since	3.6
	*  @date	23/01/13
	*/
	function field_group_admin_head(){ }

	/*
	*  load_value()
	*
	*  This filter is appied to the $value after it is loaded from the db
	*
	*  @type	filter
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	$value - the value found in the database
	*  @param	$post_id - the $post_id from which the value was loaded from
	*  @param	$field - the field array holding all the field options
	*
	*  @return	$value - the value to be saved in te database
	*/
	function load_value( $value, $post_id, $field ){
		$sub_field = $this->setup_sub_field( $this->setup_field( $field ) );
		return apply_filters( 'acf/load_value/type='.$sub_field['type'], $value, $post_id, $sub_field );
	}

	/*
	*  update_value()
	*
	*  This filter is appied to the $value before it is updated in the db
	*
	*  @type	filter
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	$value - the value which will be saved in the database
	*  @param	$post_id - the $post_id of which the value will be saved
	*  @param	$field - the field array holding all the field options
	*
	*  @return	$value - the modified value
	*/
	function update_value( $value, $post_id, $field ){
		$sub_field = $this->setup_sub_field( $this->setup_field( $field ) );
		return apply_filters( 'acf/update_value/type='.$sub_field['type'], $value, $post_id, $sub_field );
	}

	/*
	*  format_value()
	*
	*  This filter is appied to the $value after it is loaded from the db and before it is passed to the create_field action
	*
	*  @type	filter
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	$value	- the value which was loaded from the database
	*  @param	$post_id - the $post_id from which the value was loaded
	*  @param	$field	- the field array holding all the field options
	*
	*  @return	$value	- the modified value
	*/
	function format_value( $value, $post_id, $field ){
		$sub_field = $this->setup_sub_field( $this->setup_field( $field ) );
		return apply_filters( 'acf/format_value/type='.$sub_field['type'], $value, $post_id, $sub_field );
	}

	/*
	*  format_value_for_api()
	*
	*  This filter is appied to the $value after it is loaded from the db and before it is passed back to the api functions such as the_field
	*
	*  @type	filter
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	$value	- the value which was loaded from the database
	*  @param	$post_id - the $post_id from which the value was loaded
	*  @param	$field	- the field array holding all the field options
	*
	*  @return	$value	- the modified value
	*/
	function format_value_for_api( $value, $post_id, $field ){
		$sub_field = $this->setup_sub_field( $this->setup_field( $field ) );
		return apply_filters( 'acf/format_value_for_api/type='.$sub_field['type'], $value, $post_id, $sub_field );
	}

	/*
	*  load_field()
	*
	*  This filter is appied to the $field after it is loaded from the database
	*
	*  @type	filter
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	$field - the field array holding all the field options
	*
	*  @return	$field - the field array holding all the field options
	*/
	function load_field( $field ){
		global $currentpage;
		$field = $this->setup_field( $field );
		$sub_field = $this->setup_sub_field( $field );
		$sub_field = apply_filters( 'acf/load_field/type='.$sub_field['type'], $sub_field );

		// The relationship field gets settings from the sub_field so we need to return it since it effectively displays through this method.
		if ( isset( $_POST['action'] ) && $_POST['action'] == 'acf/fields/relationship/query_posts' ){
			// Bug fix, if the taxonomy is "all" just omit it from the filter.
			if ( $sub_field['taxonomy'][0] == 'all' ){
				unset( $sub_field['taxonomy']);
			}
			return $sub_field;
		}

		$field['sub_field'] = $sub_field;
		if ( $field['read_only'] && $currentpage == 'edit.php' ){
			$field['label'] .= ' <i class="fa fa-ban" style="color:red;" title="'. __( 'Read only', 'acf_vf' ) . '"></i>';
		}
		return $field;
	}

	/*
	*  update_field()
	*
	*  This filter is appied to the $field before it is saved to the database
	*
	*  @type	filter
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	$field - the field array holding all the field options
	*  @param	$post_id - the field group ID (post_type = acf)
	*
	*  @return	$field - the modified field
	*/
	function update_field( $field, $post_id ){
		$sub_field = $this->setup_sub_field( $this->setup_field( $field ) );
		$sub_field = apply_filters( 'acf/update_field/type='.$sub_field['type'], $sub_field, $post_id );
		$field['sub_field'] = $sub_field;
		return $field;
	}
}

new acf_field_validated_field();
endif;

validated_field_v5.php

<?php
if ( class_exists( 'acf_Field' ) && ! class_exists( 'acf_field_validated_field' ) ):
class acf_field_validated_field extends acf_field {
	//static final NL = "\n";
	// vars
	var $slug,
		$config,
		$settings,					// will hold info such as dir / path
		$defaults,					// will hold default field options
		$sub_defaults,				// will hold default sub field options
		$debug,						// if true, don't use minified and confirm form submit					
		$drafts,
		$frontend;

	/*
	*  __construct
	*
	*  Set name / label needed for actions / filters
	*
	*  @since	3.6
	*  @date	23/01/13
	*/
	function __construct(){
		// vars
		$this->slug 	= 'acf-validated-field';
		$this->strbool 	= array( 'true' => true, 'false' => false );
		$this->config 	= array(
			'acf_vf_debug' => array(
				'type' 		=> 'checkbox',
				'default' 	=> 'false',
				'label'  	=> __( 'Enable Debug', 'acf_vf' ),
				'help' 		=> __( 'Check this box to turn on debugging for Validated Fields.', 'acf_vf' ),
			),
			'acf_vf_drafts' => array(
				'type' 		=> 'checkbox',
				'default' 	=> 'true',
				'label'  	=> __( 'Enable Draft Validation', 'acf_vf' ),
				'help' 		=> __( 'Check this box to enable Draft validation globally, or uncheck to allow it to be set per field.', 'acf_vf' ),
			),
			'acf_vf_frontend' => array(
				'type' 		=> 'checkbox',
				'default' 	=> 'true',
				'label'  	=> __( 'Enable Front-End Validation', 'acf_vf' ),
				'help'		=> __( 'Check this box to turn on validation for front-end forms created with', 'acf_vf' ) . ' <code>acf_form()</code>.',
			),
			'acf_vf_frontend_css' => array(
				'type' 		=> 'checkbox',
				'default' 	=> 'true',
				'label'  	=> __( 'Enqueue Admin CSS on Front-End', 'acf_vf' ),
				'help' 		=> __( 'Uncheck this box to turn off "colors-fresh" admin theme enqueued by', 'acf_vf' ) . ' <code>acf_form_head()</code>.',
			),
		);
		$this->name		= 'validated_field';
		$this->label 	= __( 'Validated Field', 'acf_vf' );
		$this->category	= __( 'Basic', 'acf' );
		$this->drafts	= $this->option_value( 'acf_vf_drafts' );
		$this->frontend = $this->option_value( 'acf_vf_frontend' );
		$this->frontend_css = $this->option_value( 'acf_vf_frontend_css' );
		$this->debug 	= $this->option_value( 'acf_vf_debug' );

		$this->defaults = array(
			'read_only' => false,
			'mask'		=> '',
			'mask_autoclear' => true,
			'mask_placeholder' => '_',
			'function'	=> 'none',
			'pattern'	=> '',
			'message'	=>  __( 'Validation failed.', 'acf_vf' ),
			'unique'	=> 'non-unique',
			'unique_statuses' => apply_filters( 'acf_vf/unique_statuses', array( 'publish', 'future' ) ),
			'drafts'	=> true,
		);

		$this->sub_defaults = array(
			'type'		=> '',
			'key'		=> '',
			'name'		=> '',
			'_name'		=> '',
			'id'		=> '',
			'value'		=> '',
			'field_group' => '',
			'readonly' => '',
			'disabled' => '',
		);

		$this->input_defaults = array(
			'id'		=> '',
			'value'		=> '',
		);

		// do not delete!
		parent::__construct();

		// settings
		$this->settings = array(
			'path'		=> apply_filters( 'acf/helpers/get_path', __FILE__ ),
			'dir'		=> apply_filters( 'acf/helpers/get_dir', __FILE__ ),
			'version'	=> ACF_VF_VERSION,
		);

		if ( is_admin() || $this->frontend ){ // admin actions

			// bug fix for acf with backslashes in the content.
			add_filter( 'content_save_pre', array( $this, 'fix_post_content' ) );
			add_filter( 'acf/get_valid_field', array( $this, 'fix_upgrade' ) );

			// override the default ajax actions to provide our own messages since they aren't filtered
			add_action( 'init', array( $this, 'add_acf_ajax_validation' ) );

			if ( ! is_admin() && $this->frontend ){
				if ( ! $this->frontend_css ){
					add_action( 'acf/input/admin_enqueue_scripts',  array( $this, 'remove_acf_form_style' ) );
				}

				add_action( 'wp_head', array( $this, 'set_post_id_to_acf_form' ) );
				add_action( 'wp_head', array( $this, 'input_admin_enqueue_scripts' ), 1 );
			}
			if ( is_admin() ){
				add_action( 'admin_init', array( $this, 'admin_register_settings' ) );
				add_action( 'admin_menu', array( $this, 'admin_add_menu' ), 11 );
				add_action( 'admin_head', array( $this, 'admin_head' ) );
				// add the post_ID to the acf[] form
				add_action( 'edit_form_after_editor', array( $this, 'edit_form_after_editor' ) );
			}

			if ( is_admin() || $this->frontend ){
				// validate validated_fields
				add_filter( "acf/validate_value/type=validated_field", array( $this, 'validate_field' ), 10, 4 );
			}
		}
	}

	function fix_upgrade( $field ){
		// the $_POST will tell us if this is an upgrade
		$is_5_upgrade = 
			isset( $_POST['action'] ) && $_POST['action'] == 'acf/admin/data_upgrade' && 
			isset( $_POST['version'] ) && $_POST['version'] == '5.0.0';

		// if it is an upgrade recursively fix the field values
		if ( $is_5_upgrade ){
			$field = $this->do_recursive_slash_fix( $field );
		}

		return $field;
	}

	function fix_post_content( $content ){
		global $post;

		// are we saving a field group?
		$is_field_group = get_post_type() == 'acf-field-group';

		// are we saving a field group?
		$is_field = get_post_type() == 'acf-field';

		// are we upgrading to ACF 5?
		$is_5_upgrade = 
			isset( $_POST['action'] ) && $_POST['action'] == 'acf/admin/data_upgrade' && 
			isset( $_POST['version'] ) && $_POST['version'] == '5.0.0';

		// if we are, we need to check the values for single, but not double, backslashes and make them double
		if ( $is_field || $is_field_group || $is_5_upgrade ){
			$content = $this->do_slash_fix( $content );
		}

		return $content;
	}

	function do_slash_fix( $string ){
		if ( preg_match( '~(?<!\\\\)\\\\(?!\\\\)~', $string ) ){
			$string = str_replace('\\', '\\\\', $string );
		}
		if ( preg_match( '~\\\\\\\\"~', $string ) ){
			$string = str_replace('\\\\"', '\\"', $string );
		}
		return $string;
	}

	function do_recursive_slash_fix( $array ){
		// loop through all levels of the array
		foreach( $array as $key => &$value ){
			if ( is_array( $value ) ){
				// div deeper
				$value = $this->do_recursive_slash_fix( $value );
			} elseif ( is_string( $value ) ){
				// fix single backslashes to double
				$value = $this->do_slash_fix( $value );
			}
		}

		return $array;
	}

	function add_acf_ajax_validation(){
		global $acf;
		if ( version_compare( $acf->settings['version'], '5.2.6', '<' ) ){
			remove_all_actions( 'wp_ajax_acf/validate_save_post' );
			remove_all_actions( 'wp_ajax_nopriv_acf/validate_save_post' );
		}
		add_action( 'wp_ajax_acf/validate_save_post',			array( $this, 'ajax_validate_save_post') );
		add_action( 'wp_ajax_nopriv_acf/validate_save_post',	array( $this, 'ajax_validate_save_post') );
	}

	function set_post_id_to_acf_form(){
		global $post;
		?>

		<script type="text/javascript">
		jQuery(document).ready(function(){
			jQuery('form.acf-form').append('<input type="hidden" name="acf[post_ID]" value="<?php echo $post->ID; ?>"/>');
			jQuery('form.acf-form').append('<input type="hidden" name="acf[frontend]" value="true"/>');
		});
		</script>

		<?php
	}

	function edit_form_after_editor( $post ){
		echo "<input type='hidden' name='acf[post_ID]' value='{$post->ID}'/>";
	}

	function option_value( $key ){
		return ( false !== $option = get_option( $key ) )?
			$option == $this->config[$key]['default'] :
			$this->strbool[$this->config[$key]['default']];
	}

	function admin_head(){
		global $typenow, $acf;

		$min = ( ! $this->debug )? '.min' : '';
		if ( $this->is_edit_page() && "acf-field-group" == $typenow ){
			wp_register_script( 'acf-validated-field-admin', plugins_url( "js/admin{$min}.js", __FILE__ ), array( 'jquery', 'acf-field-group' ), ACF_VF_VERSION );	
		}
		if ( version_compare( $acf->settings['version'], '5.2.6', '<' ) ){
			wp_register_script( 'acf-validated-field-group', plugins_url( "js/field-group{$min}.js", __FILE__ ), array( 'jquery', 'acf-field-group' ), ACF_VF_VERSION );
		}
		wp_enqueue_script( array(
			'jquery',
			'acf-validated-field-admin',
			'acf-validated-field-group',
		));	
	}

	function admin_add_menu(){
		$page = add_submenu_page( 'edit.php?post_type=acf-field-group', __( 'Validated Field Settings', 'acf_vf' ), __( 'Validated Field Settings', 'acf_vf' ), 'manage_options', $this->slug, array( &$this,'admin_settings_page' ) );		
	}

	function admin_register_settings(){
		foreach ( $this->config as $key => $value ) {
			register_setting( $this->slug, $key );
		}
	}

	function admin_settings_page(){
		?>
		<div class="wrap">
		<h2>Validated Field Settings</h2>
		<form method="post" action="options.php">
		    <?php settings_fields( $this->slug ); ?>
		    <?php do_settings_sections( $this->slug ); ?>
			<table class="form-table">
			<?php foreach ( $this->config as $key => $value ) { ?>
				<tr valign="top">
					<th scope="row"><?php echo $value['label']; ?></th>
					<td>
						<input type="checkbox" id="<?php echo $key; ?>" name="<?php echo $key; ?>" value="<?php echo $value['default']; ?>" <?php if ( $this->option_value( $key ) ) echo 'checked'; ?>/>
						<small><em><?php echo $value['help']; ?></em></small>
					</td>
				</tr>
			<?php } ?>
			</table>
		    <?php submit_button(); ?>
		</form>
		</div>
    	<?php
	}

	function remove_acf_form_style(){
		wp_dequeue_style( array( 'colors-fresh' ) );
	}

	function setup_field( $field ){
		// setup booleans, for compatibility
		$field = acf_prepare_field( array_merge( $this->defaults, $field ) );

		// set up the sub_field
		$sub_field = isset( $field['sub_field'] )? 
			$field['sub_field'] :	// already set up
			array();				// create it

		// mask the sub field as the parent by giving it the same key values
		foreach( $field as $key => $value ){
			if ( in_array( $key, array( 'sub_field', 'type' ) ) )
				continue;
			$sub_field[$key] = $value;
		}

		// these fields need some special formatting
		$sub_field['_input'] = $field['prefix'].'['.$sub_field['key'].']';
		$sub_field['name'] = $sub_field['_input'];
		$sub_field['id'] = str_replace( '-acfcloneindex', '', str_replace( ']', '', str_replace( '[', '-', $sub_field['_input'] ) ) );

		// make sure all the defaults are set
		$field['sub_field'] = array_merge( $this->sub_defaults, $sub_field );

		return $field;
	}

	function setup_sub_field( $field ){
		return $field['sub_field'];	
	}

	/*
	*  get_post_statuses()
	*
	*  Get the various post statuses that have been registered
	*
	*  @type		function
	*
	*/
	function get_post_statuses() {
		global $wp_post_statuses;
		return $wp_post_statuses;
	}

	/*
	*  ajax_validate_save_post()
	*
	*  Override the default acf_input()->ajax_validate_save_post() to return a custom validation message
	*
	*  @type		function
	*
	*/
	function ajax_validate_save_post() {
		
		// validate
		if ( ! isset( $_POST['_acfnonce'] ) ) {
			// ignore validation, this form $_POST was not correctly configured
			die();
		}
		
		// success
		if ( acf_validate_save_post() ) {
			$json = array(
				'result'	=> 1,
				'message'	=> __( 'Validation successful', 'acf' ),
				'errors'	=> 0
			);
			
			die( json_encode( $json ) );
		}
		
		// fail
		$json = array(
			'result'	=> 0,
			'message'	=> __( 'Validation failed', 'acf' ),
			'errors'	=> acf_get_validation_errors()
		);

		// update message
		$i = count( $json['errors'] );
		$json['message'] .= '. ' . sprintf( _n( '1 field below is invalid.', '%s fields below are invalid.', $i, 'acf_vf' ), $i ) . ' ' . __( 'Please check your values and submit again.', 'acf_vf' );
		
		die( json_encode( $json ) );
	}

	function validate_field( $valid, $value, $field, $input ) {
		if ( ! $valid )
			return $valid;

		// get ID of the submit post or cpt, allow null for options page
		$post_id = isset( $_POST['acf']['post_ID'] )? $_POST['acf']['post_ID'] : null;

		$post_type = get_post_type( $post_id );				// the type of the submitted post
		$frontend = isset( $_REQUEST['acf']['frontend'] )?
			$_REQUEST['acf']['frontend'] :
			false;

		if ( !empty( $field['parent'] ) ){
			$parent_field = acf_get_field( $field['parent'] );	
		}

		// if it's a repeater field, get the validated field so we can do meta queries...
		if ( $is_repeater = ( isset( $parent_field ) && 'repeater' == $parent_field['type'] ) ){
			$index = explode( '][', $input );
			$index = $index[1];
		}
		
		// the wrapped field
		$field = $this->setup_field( $field );
		$sub_field = $this->setup_sub_field( $field );
		
		//$value = $input['value'];							// the submitted value
		if ( $field['required'] && empty( $value ) ){
			return $valid;									// let the required field handle it
		}

		if ( !$field['drafts'] ){
			return $valid;									// we aren't publishing and we don't want to validate drafts
		}
		
		if ( is_array( $value ) ){
			$value = implode( ',', $value );
		}

		$function = $field['function'];						// what type of validation?
		$pattern = $field['pattern'];						// string to use for validation
		$message = $field['message'];						// failure message to return to the UI
		if ( ! empty( $function ) && ! empty( $pattern ) ){
			switch ( $function ){							// only run these checks if we have a pattern
				case 'regex':								// check for any matches to the regular expression
					$pattern_fltr = '/' . str_replace( "/", "\/", $pattern ) . '/';
					if ( ! preg_match( $pattern_fltr, $value ) ){
						$valid = false;						// return false if there are no matches
					}
					break;
				case 'sql':									// todo: sql checks?
					break;
				case 'php':									// this code is a little tricky, one bad eval() can break the lot. needs a nonce.
					$this_key = $field['name'];
					if ( $is_repeater ) $this_key .= '_' . $index . '_' . $sub_sub_field['name'];

					// get the fields based on the keys and then index by the meta value for easy of use
					$input_fields = array();
					foreach ( $_POST['acf'] as $key => $val ){
						if ( false !== ( $input_field = get_field_object( $key, $post_id ) ) ){
							$meta_key = $input_field['name'];
							$input_fields[$meta_key] = array(
								'field'=>$input_field,
								'value'=>$val,
								'prev_val'=>get_post_meta( $post_id, $meta_key, true )
							);
						}
					}

					$message = $field['message'];			// the default message

					// not yet saved to the database, so this is the previous value still
					$prev_value = addslashes( get_post_meta( $post_id, $this_key, true ) );

					// unique function for this key
					$function_name = 'validate_' . $field['key'] . '_function';
					
					// it gets tricky but we are trying to account for an capture bad php code where possible
					$pattern = addcslashes( trim( $pattern ), '$' );
					if ( substr( $pattern, -1 ) != ';' ) $pattern.= ';';

					$value = addslashes( $value );

					// this must be left aligned as it contains an inner HEREDOC
					$php = <<<PHP
if ( ! function_exists( '$function_name' ) ):
function $function_name( \$args, &\$message ){
	extract( \$args );
	try {
		\$code = <<<INNERPHP
		$pattern return true;
INNERPHP;
		return @eval( \$code );
	} catch ( Exception \$e ){
		\$message = "Error: ".\$e->getMessage(); return false;
	}
}
endif; // function_exists
\$valid = $function_name( array( 'post_id'=>'$post_id', 'post_type'=>'$post_type', 'this_key'=>'$this_key', 'value'=>'$value', 'prev_value'=>'$prev_value', 'inputs'=>\$input_fields ), \$message );
PHP;

					if ( true !== eval( $php ) ){			// run the eval() in the eval()
						$error = error_get_last();			// get the error from the eval() on failure
						// check to see if this is our error or not.
						if ( strpos( $error['file'], basename( __FILE__ ) ) && strpos( $error['file'], "eval()'d code" ) ){
							preg_match( '/eval\\(\\)\'d code\\((\d+)\\)/', $error['file'], $matches );
							$message = __( 'PHP Error', 'acf_vf' ) . ': ' . $error['message'] . ', line ' . $matches[1] . '.';
							$valid = false;
						} 
					}
					// if a string is returned, return it as the error.
					if ( is_string( $valid ) ){
						$message = $valid;
						$valid = false;
					}
					$message = stripslashes( $message );
					break;
			}
		} elseif ( ! empty( $function ) && $function != 'none' ) {
			$message = __( 'This field\'s validation is not properly configured.', 'acf_vf' );
			$valid = false;
		}
			
		$unique = $field['unique'];
		$field_is_unique = ! empty( $value ) && ! empty( $unique ) && $unique != 'non-unique';
		
		// validate the submitted values since there might be dupes in the form submit that aren't yet in the database
		if ( $valid && $field_is_unique ){
			$value_instances = 0;
			switch ( $unique ){
				case 'global';
				case 'post_type':
				case 'this_post':
					// no duplicates at all allowed
					foreach ( $_REQUEST['acf'] as $submitted ){
						if ( is_array( $submitted ) ){
							foreach ( $submitted as $row ){
								// there is only one, but we don't know the key
								foreach ( $row as $submitted2 ){
									if ( $submitted2 == $value ){
										$value_instances++;
									}
									break;
								}
							}
						} else {
							if ( $submitted == $value ){
								$value_instances++;
							}
						}
					}
					break;
				case 'post_key':
				case 'this_post_key':
					// only check the key for a repeater
					if ( $is_repeater ){
						foreach ( $_REQUEST['acf'] as $key => $submitted ){
							if ( $key == $parent_field['key'] ){	
								foreach ( $submitted as $row ){
									// there is only one, but we don't know the key
									foreach ( $row as $submitted2 ){
										if ( $submitted2 == $value ){
											$value_instances++;
										}
										break;
									}
								}
							}
						}
					}
					break;
			}

			// this value came up more than once, so we need to mark it as an error
			if ( $value_instances > 1 ){
				$message = __( 'The value', 'acf_vf' ) . " '$value' " . __( 'was submitted multiple times and should be unique for', 'acf_vf' ) . " {$field['label']}.";
				$valid = false;
			}
		}

		if ( $valid && $field_is_unique ){
			global $wpdb;
			$status_in = "'" . implode( "','", $field['unique_statuses'] ) . "'";

			// WPML compatibility, get code list of active languages
			if ( function_exists( 'icl_object_id' ) ){
				$languages = $wpdb->get_results( "SELECT code FROM {$wpdb->prefix}icl_languages WHERE active = 1", ARRAY_A );
				$wpml_ids = array();
				foreach( $languages as $lang ){
					$wpml_ids[] = (int) icl_object_id( $post_id, $post_type, true, $lang['code'] );
				}
				$post_ids = array_unique( $wpml_ids );
			} else {
				$post_ids = array( (int) $post_id );
			}

			$sql_prefix = "SELECT pm.meta_id AS meta_id, pm.post_id AS post_id, p.post_title AS post_title FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id AND p.post_status IN ($status_in)";
			switch ( $unique ){
				case 'global': 
					// check to see if this value exists anywhere in the postmeta table
					$sql = $wpdb->prepare( 
						"{$sql_prefix} AND post_id NOT IN ([IN_NOT_IN]) WHERE ( meta_value = %s OR meta_value LIKE %s )",
						$value,
						'%"' . $wpdb->esc_like( $value ) . '"%'
					);
					break;
				case 'post_type':
					// check to see if this value exists in the postmeta table with this $post_id
					$sql = $wpdb->prepare( 
						"{$sql_prefix} AND p.post_type = %s WHERE ( ( post_id IN ([IN_NOT_IN]) AND meta_key != %s ) OR post_id NOT IN ([IN_NOT_IN]) ) AND ( meta_value = %s OR meta_value LIKE %s )", 
						$post_type,
						$field['name'],
						$value,
						'%"' . $wpdb->esc_like( $value ) . '"%'
					);
					break;
				case 'this_post':
					// check to see if this value exists in the postmeta table with this $post_id
					$this_key = $is_repeater ? 
						$parent_field['name'] . '_' . $index . '_' . $field['name'] :
						$field['name'];
					$sql = $wpdb->prepare( 
						"{$sql_prefix} AND post_id IN ([IN_NOT_IN]) AND meta_key != %s AND ( meta_value = %s OR meta_value LIKE %s )",
						$this_key,
						$value,
						'%"' . $wpdb->esc_like( $value ) . '"%'
					);
					break;
				case 'post_key':
				case 'this_post_key':
					// check to see if this value exists in the postmeta table with both this $post_id and $meta_key
					if ( $is_repeater ){
						$this_key = $parent_field['name'] . '_' . $index . '_' . $field['name'];
						$meta_key = $parent_field['name'] . '_%_' . $field['name'];
						if ( 'post_key' == $unique ){
							$sql = $wpdb->prepare(
								"{$sql_prefix} AND p.post_type = %s WHERE ( ( post_id IN ([IN_NOT_IN]) AND meta_key != %s AND meta_key LIKE %s ) OR ( post_id NOT IN ([IN_NOT_IN]) AND meta_key LIKE %s ) ) AND ( meta_value = %s OR meta_value LIKE %s )", 
								$post_type,
								$this_key,
								$meta_key,
								$meta_key,
								$value,
								'%"' . $wpdb->esc_like( $value ) . '"%'
							);
						} else {
							$sql = $wpdb->prepare(
								"{$sql_prefix} WHERE post_id IN ([IN_NOT_IN]) AND meta_key != %s AND meta_key LIKE %s AND ( meta_value = %s OR meta_value LIKE %s )", 
								$this_key,
								$meta_key,
								$meta_key,
								$value,
								'%"' . $wpdb->esc_like( $value ) . '"%'
							);
						}
					} else {
						if ( 'post_key' == $unique ){
							$sql = $wpdb->prepare( 
								"{$sql_prefix} AND p.post_type = %s AND post_id NOT IN ([IN_NOT_IN]) WHERE meta_key = %s AND ( meta_value = %s OR meta_value LIKE %s )", 
								$post_type,
								$field['name'],
								$value,
								'%"' . $wpdb->esc_like( $value ) . '"%'
							);
						} else {
							$sql = $wpdb->prepare( 
								"{$sql_prefix} AND post_id IN ([IN_NOT_IN]) WHERE meta_key = %s AND ( meta_value = %s OR meta_value LIKE %s )", 
								$field['name'],
								$value,
								'%"' . $wpdb->esc_like( $value ) . '"%'
							);
						}
					}
					break;
				case 'this_post_key':
					// check to see if this value exists in the postmeta table with this $post_id
					$sql = $wpdb->prepare( 
						"{$sql_prefix} AND p.post_type = %s WHERE ( ( post_id IN ([IN_NOT_IN]) AND meta_key = %s ) ) AND ( meta_value = %s OR meta_value LIKE %s )", 
						$post_type,
						$field['name'],
						$value,
						'%"' . $wpdb->esc_like( $value ) . '"%'
					);
					break;
				default:
					// no dice, set $sql to null
					$sql = null;
					break;
			}

			// Only run if we hit a condition above
			if ( ! empty( $sql ) ){

				// Update the [IN_NOT_IN] values
				$sql = $this->prepare_in_and_not_in( $sql, $post_ids );

				// Execute the SQL
				$rows = $wpdb->get_results( $sql );
				if ( count( $rows ) ){
					// We got some matches, but there might be more than one so we need to concatenate the collisions
					$conflicts = "";
					foreach ( $rows as $row ){
						$permalink = ( $frontend )? get_permalink( $row->post_id ) : "/wp-admin/post.php?post={$row->post_id}&action=edit";
						$conflicts.= "<a href='{$permalink}' style='color:inherit;text-decoration:underline;'>{$row->post_title}</a>";
						if ( $row !== end( $rows ) ) $conflicts.= ', ';
					}
					$message = __( 'The value', 'acf_vf' ) . " '$value' " . __( 'is already in use by', 'acf_vf' ) . " {$conflicts}.";
					$valid = false;
				}
			}
		}
		
		// ACF will use any message as an error
		if ( ! $valid ) $valid = $message;

		return $valid;
	}

	private function prepare_in_and_not_in( $sql, $post_ids ){
		global $wpdb;
		$not_in_count = substr_count( $sql, '[IN_NOT_IN]' );
		if ( $not_in_count > 0 ){
			$args = array( str_replace( '[IN_NOT_IN]', implode( ', ', array_fill( 0, count( $post_ids ), '%d' ) ), str_replace( '%', '%%', $sql ) ) );
			for ( $i=0; $i < substr_count( $sql, '[IN_NOT_IN]' ); $i++ ) { 
				$args = array_merge( $args, $post_ids );
			}
			$sql = call_user_func_array( array( $wpdb, 'prepare' ), $args );
		}
		return $sql;
	}

	/*
	*  render_field_settings()
	*
	*  Create extra settings for your field. These are visible when editing a field
	*
	*  @type	action
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	$field (array) the $field being edited
	*  @return	n/a
	*/
	
	function render_field_settings( $field ) {
		//return;
		// defaults?
		$field = $this->setup_field( $field );

		// key is needed in the field names to correctly save the data
		$key = $field['key'];
		$html_key = 'acf_fields-'.$field['ID'];

		$sub_field = $this->setup_sub_field( $field );
		$sub_field['prefix'] = "{$field['prefix']}[sub_field]";

		// remove types that don't jive well with this one
		$fields_names = apply_filters( 'acf/get_field_types', array() );
		unset( $fields_names[__( 'Layout', 'acf' )] );
		unset( $fields_names[__( 'Basic', 'acf' )][ 'validated_field' ] );

		$field_id = str_replace("-temp", "", $field['id'] );
		$field_key = $field['key'];

		// layout
		acf_render_field_setting( $field, array(
			'label'			=> __( 'Read Only?', 'acf_vf' ),
			'instructions'	=> __( 'When a field is marked read only, it will be visible but uneditable. Read only fields are marked with ', 'acf_vf' ). '<i class="fa fa-ban" style="color:red;" title="'. __( 'Read only', 'acf_vf' ) . '"></i>.',
			'type'			=> 'radio',
			'name'			=> 'read_only',
			'layout'		=> 'horizontal', 
			'prefix'		=> $field['prefix'],
			'choices'		=> array(
				false 	=> __( 'No', 'acf_vf' ),
				true	=> __( 'Yes', 'acf_vf' ),
			)
		));

		// Validate Drafts
		acf_render_field_setting( $field, array(
			'label'			=> __( 'Validate Drafts/Preview?', 'acf_vf' ),
			'instructions'	=> '',
			'type'			=> 'radio',
			'name'			=> 'drafts',
			'prefix'		=> $field['prefix'],
			'choices' 		=> array(
				true  	=> __( 'Yes', 'acf_vf' ),
				false 	=> __( 'No', 'acf_vf' ),
			),
			'layout'		=> 'horizontal',
		));

		if ( false && ! $this->drafts ){
			echo '<em>';
			_e( 'Warning', 'acf_vf' );
			echo ': <code>ACF_VF_DRAFTS</code> ';
			_e( 'has been set to <code>false</code> which overrides field level configurations', 'acf_vf' );
			echo '.</em>';
		}

		?>
		<tr class="acf-field acf-sub_field" data-setting="validated_field" data-name="sub_field">
			<td class="acf-label">
				<label><?php _e( 'Validated Field', 'acf_vf' ); ?></label>
				<p class="description"></p>		
			</td>
			<td class="acf-input">
				<?php
				$atts = array(
					'id' => 'acfcloneindex',
					'class' => "field field_type-{$sub_field['type']}",
					'data-id'	=> $sub_field['id'],
					'data-key'	=> $sub_field['key'],
					'data-type'	=> $sub_field['type'],
				);

				$metas = array(
					'id'			=> $sub_field['id'],
					'key'			=> $sub_field['key'],
					'parent'		=> $sub_field['parent'],
					'save'			=> '',
				);

				?>
				<div <?php echo acf_esc_attr( $atts ); ?>>
					<div class="field-meta acf-hidden">
						<?php 

						// meta		
						foreach( $metas as $k => $v ) {
							acf_hidden_input(array( 'class' => "input-{$k}", 'name' => "{$sub_field['prefix']}[{$k}]", 'value' => $v ));
						}

						?>
					</div>

					<div class="sub-field-settings">			
						<table class="acf-table">
							<tbody>
							<?php 

							if ( ! isset( $sub_field['function'] ) || empty( $sub_field['function'] ) ){
								$sub_field['function'] = 'none';
							}

							// Validated Field Type
							acf_render_field_setting( $sub_field, array(
								'label'			=> __( 'Field Type', 'acf_vf' ),
								'instructions'	=> __( 'The underlying field type that you would like to validate.', 'acf_vf' ),
								'type'			=> 'select',
								'name'			=> 'type',
								'prefix'		=> $sub_field['prefix'],
								'choices' 		=> $fields_names,
								'required'		=> true
							), 'tr' );			

							// Render the Sub Field
							do_action( "acf/render_field_settings/type={$sub_field['type']}", $sub_field );

							?>
							<tr class="field_save acf-field" data-name="conditional_logic" style="display:none;">
								<td class="acf-label"></td>
								<td class="acf-input"></td>
							</tr>
							</tbody>
						</table>
					</div>
				</div>
			</td>
		</tr>
		<?php

		if ( !empty( $field['mask'] ) && $sub_field['type'] == 'number' ){

		}

		$mask_error = ( !empty( $field['mask'] ) && $sub_field['type'] == 'number' )? 
			'color:red;' : '';

		// Input Mask
		acf_render_field_setting( $field, array(
			'label'			=> __( 'Input mask', 'acf_vf' ),
			'instructions'	=> __( 'Use &#39;a&#39; to match A-Za-z, &#39;9&#39; to match 0-9, and &#39;*&#39; to match any alphanumeric.', 'acf_vf' ) . 
								' <a href="http://digitalbush.com/projects/masked-input-plugin/" target="_new">' . 
								__( 'More info', 'acf_vf' ) . 
								'</a>.<br/><br/><strong style="' . $mask_error . '"><em>' . 
								__( 'Input masking is not compatible with the "number" field type!', 'acf_vf' ) .
								'</em></strong>',
			'type'			=> 'text',
			'name'			=> 'mask',
			'prefix'		=> $field['prefix'],
			'value'			=> $field['mask'],
			'layout'		=> 'horizontal',
			'class'			=> 'input-mask'
		));

		// Input Mask
		acf_render_field_setting( $field, array(
			'label'			=> __('Input Mask: Autoclear', 'acf_vf' ),
			'instructions'	=> __( 'Clear values that do match the input mask, if provided.', 'acf_vf' ),
			'type'			=> 'radio',
			'name'			=> 'mask_autoclear',
			'prefix'		=> $field['prefix'],
			'value'			=> $field['mask_autoclear'],
			'layout'		=> 'horizontal',
			'choices' => array(
				true  	=> __( 'Yes', 'acf_vf' ),
				false 	=> __( 'No', 'acf_vf' ),
			),
			'class'			=> 'mask-settings'
		));

		// Input Mask
		acf_render_field_setting( $field, array(
			'label'			=> __('Input Mask: Placeholder', 'acf_vf' ),
			'instructions'	=> __( 'Use this string or character as a placeholder for the input mask.', 'acf_vf' ),
			'type'			=> 'text',
			'name'			=> 'mask_placeholder',
			'prefix'		=> $field['prefix'],
			'value'			=> $field['mask_placeholder'],
			'class'			=> 'mask-settings'
		));

		// Validation Function
		acf_render_field_setting( $field, array(
			'label'			=> __( 'Validation: Function', 'acf_vf' ),
			'instructions'	=> __( 'How should the field be server side validated?', 'acf_vf' ),
			'type'			=> 'select',
			'name'			=> 'function',
			'prefix'		=> $field['prefix'],
			'value'			=> $field['function'],
			'choices' => array(
				'none'	=> __( 'None', 'acf_vf' ),
				'regex' => __( 'Regular Expression', 'acf_vf' ),
				//'sql'	=> __( 'SQL Query', 'acf_vf' ),
				'php'	=> __( 'PHP Statement', 'acf_vf' ),
			),
			'layout'		=> 'horizontal',
			'optgroup' => true,
			'multiple' => '0',
			'class'			=> 'validated_select validation-function',
		));

		?>
		<tr class="acf-field validation-settings" data-setting="validated_field" data-name="pattern" id="field_option_<?php echo $html_key; ?>_validation">
			<td class="acf-label">
				<label><?php _e( 'Validation: Pattern', 'acf_vf' ); ?></label>
				<p class="description">	
				<small>
				<div class="validation-info">
					<div class='validation-type regex'>
						<?php _e( 'Pattern match the input using', 'acf_vf' ); ?> <a href="http://php.net/manual/en/function.preg-match.php" target="_new">PHP preg_match()</a>.
						<br />
					</div>
					<div class='validation-type php'>
						<ul>
							<li><?php _e( 'Use any PHP code and return true for success or false for failure. If nothing is returned it will evaluate to true.', 'acf_vf' ); ?></li>
							<li><?php _e( 'Available variables', 'acf_vf' ); ?>:
							<ul>
								<li><code>$post_id = $post->ID</code></li>
								<li><code>$post_type = $post->post_type</code></li>
								<li><code>$name = meta_key</code></li>
								<li><code>$value = form value</code></li>
								<li><code>$prev_value = db value</code></li>
								<li><code>$inputs = array(<blockquote>'field'=>?,<br/>'value'=>?,<br/>'prev_value'=>?<br/></blockquote>)</code></li>
								<li><code>&amp;$message = error message</code></li>
							</ul>
							</li>
							<li><?php _e( 'Example', 'acf_vf' ); ?>: 
							<small><code><pre>if ( $value == "123" ){
  return '123 is not valid!';
}</pre></code></small></li>
						</ul>
					</div>
					<div class='validation-type sql'>
						<?php _e( 'SQL', 'acf_vf' ); ?>.
						<br />
					</div>
				</div> 
				</small>
				</p>		
			</td>
			<td class="acf-input">
				<?php

				// Pattern
				acf_render_field( array(
					'label'			=> __( 'Pattern', 'acf_vf' ),
					'instructions'	=> '',
					'type'			=> 'textarea',
					'name'			=> 'pattern',
					'prefix'		=> $field['prefix'],
					'value'			=> $field['pattern'],
					'layout'		=> 'horizontal',
					'class'			=> 'editor',
				));

				?>
				<div id="<?php echo $field_id; ?>-editor" class='ace-editor' style="height:200px;"><?php echo $field['pattern']; ?></div>
			</td>
		</tr>
		<?php

		// Error Message
		acf_render_field_setting( $field, array(
			'label'			=> __( 'Validation: Error Message', 'acf_vf' ),
			'instructions'	=> __( 'The default error message that is returned to the client.', 'acf_vf' ),
			'type'			=> 'text',
			'name'			=> 'message',
			'prefix'		=> $field['prefix'],
			'value'			=> $field['message'],
			'layout'		=> 'horizontal',
			'class'			=> 'validation-settings'
		));

		// Validation Function
		acf_render_field_setting( $field, array(
			'label'			=> __( 'Unique Value?', 'acf_vf' ),
			'instructions'	=> __( "Make sure this value is unique for...", 'acf_vf' ),
			'type'			=> 'select',
			'name'			=> 'unique',
			'prefix'		=> $field['prefix'],
			'value'			=> $field['unique'],
			'choices' 		=> array(
				'non-unique'	=> __( 'Non-Unique Value', 'acf_vf' ),
				'global'		=> __( 'Unique Globally', 'acf_vf' ),
				'post_type'		=> __( 'Unique For Post Type', 'acf_vf' ),
				'post_key'		=> __( 'Unique For Post Type', 'acf_vf' ) . ' + ' . __( 'Field/Meta Key', 'acf_vf' ),
				'this_post'		=> __( 'Unique For Post', 'acf_vf' ),
				'this_post_key'	=> __( 'Unique For Post', 'acf_vf' ) . ' + ' . __( 'Field/Meta Key', 'acf_vf' ),
			),
			'layout'		=> 'horizontal',
			'optgroup' 		=> false,
			'multiple' 		=> '0',
			'class'			=> 'validated_select validation-unique',
		));

		// Unique Status
		$statuses = $this->get_post_statuses();
		$choices = array();
		foreach ( $statuses as $value => $status ) {
			$choices[$value] = $status->label;
		}
		acf_render_field_setting( $field, array(
			'label'			=> __( 'Unique Value: Apply to...?', 'acf_vf' ),
			'instructions'	=> __( "Make sure this value is unique for the checked post statuses.", 'acf_vf' ),
			'type'			=> 'checkbox',
			'name'			=> 'unique_statuses',
			'prefix'		=> $field['prefix'],
			'value'			=> $field['unique_statuses'],
			'choices' 		=> $choices,
		));
	}

	/*
	*  render_field()
	*
	*  Create the HTML interface for your field
	*
	*  @param	$field (array) the $field being rendered
	*
	*  @type	action
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	$field (array) the $field being edited
	*  @return	n/a
	*/
	
	function render_field( $field ) {
		global $post, $pagenow;

		// set up field properties
		$field = $this->setup_field( $field );
		$sub_field = $this->setup_sub_field( $field );

		// determine if this is a new post or an edit
		$is_new = $pagenow=='post-new.php';

		// filter to determine if this field should be rendered or not
		$render_field = apply_filters( 'acf_vf/render_field', true, $field, $is_new );

		// if it is not rendered, hide the label with CSS
		if ( ! $render_field ): ?>
			<style>div[data-key="<?php echo $sub_field['key']; ?>"] { display: none; }</style>
		<?php
		// if it is shown either render it normally or as read-only
		else : ?>
			<div class="validated-field">
				<?php
				// for read only we need to buffer the output so that we can modify it
				if ( $field['read_only'] && $field['read_only'] != 'false' ){

					?>
					<p>
					<?php 

					// Buffer output
					ob_start();

					// Render the subfield
					echo apply_filters( 'acf/render_field/type='.$sub_field['type'], $sub_field );

					// Try to make the field readonly
					$contents = ob_get_contents();
					$contents = preg_replace("~<(input|textarea|select)~", "<\${1} disabled=true read_only", $contents );
					$contents = preg_replace("~acf-hidden~", "acf-hidden acf-vf-readonly", $contents );

					// Stop buffering
					ob_end_clean();

					// Return our (hopefully) readonly input.
					echo $contents;

					?>
					</p>
					<?php

				} else {
					// wrapper for other fields, especially relationship
					echo "<div class='acf-field acf-field-{$sub_field['type']} field_type-{$sub_field['type']}' data-type='{$sub_field['type']}' data-key='{$sub_field['key']}'><div class='acf-input'>";
					echo apply_filters( 'acf/render_field/type='.$sub_field['type'], $sub_field );
					echo "</div></div>";
				}
				?>
			</div>
			<?php
			// check to see if we need to mask the input
			if ( ! empty( $field['mask'] ) && ( $is_new || ( isset( $field['read_only'] ) && ( ! $field['read_only'] || $field['read_only'] == 'false' ) ) ) ) {
				// we have to use $sub_field['key'] since new repeater fields don't have a unique ID
				?>
				<script type="text/javascript">
					(function($){
						$("div[data-key='<?php echo $sub_field['key']; ?>'] input").each( function(){
							$(this).mask("<?php echo $field['mask']?>", {
								autoclear: <?php echo isset( $field['mask_autoclear'] ) && empty( $field['mask_autoclear'] )? 'false' : 'true'; ?>,
								placeholder: '<?php echo isset( $field['mask_placeholder'] )? $field['mask_placeholder'] : '_'; ?>'
							});
						});
					})(jQuery);
				</script>
				<?php
			}
		endif;
	}

	/*
	*  input_admin_enqueue_scripts()
	*
	*  This action is called in the admin_enqueue_scripts action on the edit screen where your field is created.
	*  Use this action to add css + javascript to assist your create_field() action.
	*
	*  $info	http://codex.wordpress.org/Plugin_API/Action_Reference/admin_enqueue_scripts
	*  @type	action
	*  @since	3.6
	*  @date	23/01/13
	*/
	function input_admin_enqueue_scripts(){
		// register acf scripts
		$min = ( ! $this->debug )? '.min' : '';
		wp_register_script( 'acf-validated-field-input', plugins_url( "js/input{$min}.js", __FILE__ ), array( 'acf-validated-field' ), ACF_VF_VERSION );
		wp_register_script( 'jquery-masking', plugins_url( "js/jquery.maskedinput{$min}.js", __FILE__ ), array( 'jquery' ), ACF_VF_VERSION);
		wp_register_script( 'sh-core', plugins_url( 'js/shCore.js', __FILE__ ), array( 'acf-input' ), ACF_VF_VERSION );
		wp_register_script( 'sh-autoloader', plugins_url( 'js/shAutoloader.js', __FILE__ ), array( 'sh-core' ), ACF_VF_VERSION);
		
		// enqueue scripts
		wp_enqueue_script( array(
			'jquery',
			'jquery-ui-core',
			'jquery-ui-tabs',
			'jquery-masking',
			'acf-validated-field',
			'acf-validated-field-input',
		));
	}

	/*
	*  input_admin_footer()
	*
	*  This action is called in the wp_head/admin_head action on the edit screen where your field is created.
	*  Use this action to add css and javascript to assist your create_field() action.
	*
	*  @type	action (admin_footer)
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	n/a
	*  @return	n/a
	*/
	function input_admin_footer(){
		wp_deregister_style('font-awesome');
		wp_enqueue_style( 'font-awesome', plugins_url( 'css/font-awesome/css/font-awesome.min.css', __FILE__ ), array(), '4.2.0' ); 
		wp_enqueue_style( 'acf-validated_field', plugins_url( 'css/input.css', __FILE__ ), array(), ACF_VF_VERSION ); 
	}

	/*
	*  field_group_admin_enqueue_scripts()
	*
	*  This action is called in the admin_enqueue_scripts action on the edit screen where your field is edited.
	*  Use this action to add css + javascript to assist your create_field_options() action.
	*
	*  $info	http://codex.wordpress.org/Plugin_API/Action_Reference/admin_enqueue_scripts
	*  @type	action
	*  @since	3.6
	*  @date	23/01/13
	*/
	function field_group_admin_enqueue_scripts(){
		wp_enqueue_script( 'ace-editor', plugins_url( 'js/ace/ace.js', __FILE__ ), array(), '1.1.7' );
		wp_enqueue_script( 'ace-ext-language_tools', plugins_url( 'js/ace/ext-language_tools.js', __FILE__ ), array(), '1.1.7' );
	}

	/*
	*  load_value()
	*
	*  This filter is appied to the $value after it is loaded from the db
	*
	*  @type	filter
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	$value - the value found in the database
	*  @param	$post_id - the $post_id from which the value was loaded from
	*  @param	$field - the field array holding all the field options
	*
	*  @return	$value - the value to be saved in te database
	*/
	function load_value( $value, $post_id, $field ){
		$sub_field = $this->setup_sub_field( $this->setup_field( $field ) );
		return apply_filters( 'acf/load_value/type='.$sub_field['type'], $value, $post_id, $sub_field );
	}

	/*
	*  update_value()
	*
	*  This filter is appied to the $value before it is updated in the db
	*
	*  @type	filter
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	$value - the value which will be saved in the database
	*  @param	$post_id - the $post_id of which the value will be saved
	*  @param	$field - the field array holding all the field options
	*
	*  @return	$value - the modified value
	*/
	function update_value( $value, $post_id, $field ){
		$sub_field = $this->setup_sub_field( $this->setup_field( $field ) );
		return apply_filters( 'acf/update_value/type='.$sub_field['type'], $value, $post_id, $sub_field );
	}

	/*
	*  format_value()
	*
	*  This filter is appied to the $value after it is loaded from the db and before it is passed to the create_field action
	*
	*  @type	filter
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	$value	- the value which was loaded from the database
	*  @param	$post_id - the $post_id from which the value was loaded
	*  @param	$field	- the field array holding all the field options
	*
	*  @return	$value	- the modified value
	*/
	function format_value( $value, $post_id, $field ){
		$sub_field = $this->setup_sub_field( $this->setup_field( $field ) );
		return apply_filters( 'acf/format_value/type='.$sub_field['type'], $value, $post_id, $sub_field );
	}

	/*
	*  format_value_for_api()
	*
	*  This filter is appied to the $value after it is loaded from the db and before it is passed back to the api functions such as the_field
	*
	*  @type	filter
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	$value	- the value which was loaded from the database
	*  @param	$post_id - the $post_id from which the value was loaded
	*  @param	$field	- the field array holding all the field options
	*
	*  @return	$value	- the modified value
	*/
	function format_value_for_api( $value, $post_id, $field ){
		$sub_field = $this->setup_sub_field( $this->setup_field( $field ) );
		return apply_filters( 'acf/format_value_for_api/type='.$sub_field['type'], $value, $post_id, $sub_field );
	}

	/*
	*  load_field()
	*
	*  This filter is appied to the $field after it is loaded from the database
	*
	*  @type	filter
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	$field - the field array holding all the field options
	*
	*  @return	$field - the field array holding all the field options
	*/
	function load_field( $field ){
		$field = $this->setup_field( $field );
		$sub_field = $this->setup_sub_field( $field );
		$sub_field = apply_filters( 'acf/load_field/type='.$sub_field['type'], $sub_field );

		// The relationship field gets settings from the sub_field so we need to return it since it effectively displays through this method.
		if ( 'relationship' == $sub_field['type'] && isset( $_POST['action'] ) && $_POST['action'] == 'acf/fields/relationship/query' ){
			// the name is the key, so use _name
			$sub_field['name'] = $sub_field['_name'];
			return $sub_field;
		}

		$field['sub_field'] = $sub_field;
		if ( $field['read_only'] && $field['read_only'] != 'false' && get_post_type() != 'acf-field-group' ){
			$field['label'] .= ' <i class="fa fa-ban" style="color:red;" title="'. __( 'Read only', 'acf_vf' ) . '"></i>';
		}

		// Just avoid using any type of quotes in the db values
		$field['pattern'] = str_replace( "%%squot%%", "'", $field['pattern'] );
		$field['pattern'] = str_replace( "%%dquot%%", '"', $field['pattern'] );

		return $field;
	}

	/*
	*  update_field()
	*
	*  This filter is appied to the $field before it is saved to the database
	*
	*  @type	filter
	*  @since	3.6
	*  @date	23/01/13
	*
	*  @param	$field - the field array holding all the field options
	*
	*  @return	$field - the modified field
	*/
	function update_field( $field ){
		$sub_field = $this->setup_sub_field( $this->setup_field( $field ) );
		$sub_field = apply_filters( 'acf/update_field/type='.$sub_field['type'], $sub_field );
		$field['sub_field'] = $sub_field;

		// Just avoid using any type of quotes in the db values
		$field['pattern'] = str_replace( "'", "%%squot%%", $field['pattern'] );
		$field['pattern'] = str_replace( '"', "%%dquot%%", $field['pattern'] );

		return $field;
	}

	/**
	* is_edit_page 
	* function to check if the current page is a post edit page
	* 
	* @author Ohad Raz <[email protected]>
	* 
	* @param  string  $new_edit what page to check for accepts new - new post page ,edit - edit post page, null for either
	* @return boolean
	*/
	function is_edit_page($new_edit = null){
		global $pagenow;
		//make sure we are on the backend
		if ( !is_admin() ) return false;

		if($new_edit == "edit")
			return in_array( $pagenow, array( 'post.php',  ) );
		elseif($new_edit == "new") //check for new post page
			return in_array( $pagenow, array( 'post-new.php' ) );
		else //check for either new or edit
			return in_array( $pagenow, array( 'post.php', 'post-new.php' ) );
	}
}

new acf_field_validated_field();
endif;

js/input.js

/*
	Advanced Custom Fields: Validated Field
	Justin Silver, http://justin.ag
	DoubleSharp, http://doublesharp.com
*/
var vf = {
	valid 		: false,
	lastclick 	: false,
	reclick		: false,
	debug 		: false,
	drafts		: true,
};

(function($){

	// DOM elements we need to validate the value of
	var inputSelector = 'input[type="text"], input[type="hidden"], textarea, select, input[type="checkbox"]:checked';

	// If a form value changes, mark the form as dirty
	$(document).on('change', inputSelector, function(){
		vf.valid = false;
	});

	// When a .button is clicked we need to track what was clicked
	$(document).on('click', 'form#post .button, form#post input[type=submit]', function(){
		vf.lastclick = $(this);
		// The default 'click' runs first and then calls 'submit' so we need to retrigger after we have tracked the 'lastclick'
		if (vf.reclick){
			vf.reclick = false;
			vf.lastclick.trigger('click');
		}
	});
	
	// Intercept the form submission
	$(document).on('submit', 'form#post', function(){
		// remove error messages since we are going to revalidate
		$('.field_type-validated_field').find('.acf-error-message').remove();

		if ( ! acf.validation.status ){
			$(this).siblings('#acfvf_message').remove();
			return false;
		} else {
			$(this).siblings('#acfvf_message, #message').remove();
		}

		// If we don't have a 'lastclick' this is probably a preview where WordPress calls 'click' first
		if (!vf.lastclick){
			// We need to let our click handler run, then start the whole thing over in our handler
			vf.reclick = true;
			return false;
		}

		// We mith have already checked the form and vf.valid is set and just want all the other 'submit' functions to run, otherwise check the validation
		return vf.valid || do_validation($(this), vf.lastclick);
	});

	// Validate the ACF Validated Fields
	function do_validation(formObj, clickObj){
		// default the form validation to false
		vf.valid = false;
		// we have to know what was clicked to retrigger
		if (!clickObj) return false;
		// validate non-"publish" clicks unless vf.drafts is set to false
		if (!vf.drafts&&clickObj.attr('id')!='publish') return true;
		// gather form fields and values to submit to the server
		var fields = [];

		// inspect each of the validated fields
		formObj.find('.field_type-validated_field').each( function(){
			div = $(this);

			// we want to show some of the hidden fields.
			var validate = true;
			if ( div.is(':hidden') ){
				validate = false;

				// if this field is hidden by a tab group, allow validation
				if ( div.hasClass('acf-tab_group-hide') ){
					validate = true;
					
					// vars
					var $tab_field = div.prevAll('.field_type-tab:first'),
						$tab_group = div.prevAll('.acf-tab-wrap:first');			
					
					// if the tab itself is hidden, bypass validation
					if ( $tab_field.hasClass('acf-conditional_logic-hide') ){
						validate = false;
					} else {
						// activate this tab as it holds hidden required field!
						$tab = $tab_group.find('.acf-tab-button[data-key="' + $tab_field.attr('data-field_key') + '"]');
					}
				}
			}
			
			// if is hidden by conditional logic, ignore
			if ( div.hasClass('acf-conditional_logic-hide') ){
				validate = false;
			}
			
			// if field group is hidden, ignore
			if ( div.closest('.postbox.acf-hidden').exists() ){
				validate = false;		
			}
			
			// we want to validate this field
			if ( validate ){
				if ( div.find('.acf_wysiwyg').exists() && typeof( tinyMCE ) == "object" ){
					// wysiwyg
					var id = div.find('.wp-editor-area').attr('id'),
						editor = tinyMCE.get( id );
					field = { 
						id: div.find('.wp-editor-area').attr('name'),
						value: editor.getContent()
					};
				} else if ( div.find('.acf_relationship, input[type="radio"], input[type="checkbox"]').exists() ) {
					// relationship / radio / checkbox
					sel = '.acf_relationship .relationship_right input, input[type="radio"]:checked, input[type="checkbox"]:checked';
					field = { id: div.find('input[type="hidden"], ' + sel ).attr('name'), value: [] };
					div.find( sel ).each( function(){
						field.value.push( $( this ).val() );
					});
				} else {
					// text / textarea / select
					var text = div.find('input[type="text"], input[type="email"], input[type="number"], input[type="hidden"], textarea, select');
					if ( text.exists() ){
						field = { 
								id: text.attr('name'),
								value: text.val()
						};
					}
				}

				// add to the array to send to the server
				fields.push( field );
			}
		});

		$('.acf_postbox:hidden').remove();

		// if there are no fields, don't make an ajax call.
		if ( ! fields.length ){
			vf.valid = true;
			return true;
		} else {
			// send everything to the server to validate
			var postEl = vf.frontend? 'input[name=post_id]' : '#post_ID';
			$.ajax({
				url: ajaxurl,
				data: {
					action: 'validate_fields',
					post_id: formObj.find(postEl).val(),
					click_id: clickObj.attr('id'),
					frontend: vf.frontend,
					fields: fields
				},
				type: 'POST',
				dataType: 'json',
				success: function(json){
					ajax_returned(json, formObj, clickObj);				
				}, error:function (xhr, ajaxOptions, thrownError){
					ajax_returned(fields, formObj, clickObj);
 				}
			});

			// return false to block the 'submit', we will handle as necessary once we get a response from the server
			return false;
		}
		
		// Process the data returned by the server side validation
		function ajax_returned(fields, formObj, clickObj){
			// now we default to true since the response says if something is invalid
			vf.valid = true;
			// if we got a good response, iterate each response and if it's not valid, set an error message on it
			if (fields){
				for (var i=0; i<fields.length; i++){
					var fld = fields[i];
					if (!fld.valid){
						vf.valid = false;
						msg = $('<div/>').html(fld.message).text();
						input = $('[name="'+fld.id.replace('[', '\\[').replace(']', '\\]')+'"]');
						input.parent().parent().append('<span class="acf-error-message"><i class="bit"></i>' + msg + '</span>');
						field = input.closest('.field');
						field.addClass('error');
						field.find('.widefat').css('width','100%');
					}
				}
			}
			
			// reset all the CSS
			$('#ajax-loading').attr('style','');
			$('.submitbox .spinner').hide();
			$('.submitbox .button').removeClass('button-primary-disabled').removeClass('disabled');
			if ( !vf.valid ){
				// if it wasn't valid, show all the errors
				formObj.before('<div id="acfvf_message" class="error"><p>Validation Failed. See errors below.</p></div>');
				formObj.find('.field_type-validated_field .acf-error-message').show();
			} else if ( vf.debug ){
				// it was valid, but we have debugging on which will confirm the submit
				vf.valid = confirm("The fields are valid, do you want to submit the form?");
			} 
			// if everything is good, reclick which will now bypass the validation
			if (vf.valid) {
				clickObj.click();
			}
		}
	}
})(jQuery);

css/input.css

.validated-select { width: 200px; }
.validation-type { display: none; }
.acf-vf-readonly { display: block !important; }

/* required for conditional logic when the default view is hidden */
.acf-field-validated-field>.acf-input>.validated-field>.hidden-by-conditional-logic { display: block !important; }

The post WordPress Plugin: validated-field-for-acf appeared first on Justin Silver.

]]>
https://www.justinsilver.com/technology/wordpress/wordpress-plugins/wordpress-plugin-validated-field-for-acf/feed/ 6
Remote Content Shortcode https://www.justinsilver.com/technology/wordpress/wordpress-plugin-remote-content-shortcode/?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-plugin-remote-content-shortcode https://www.justinsilver.com/technology/wordpress/wordpress-plugin-remote-content-shortcode/#comments Sun, 04 Nov 2012 05:06:44 +0000 http://justin.ag/?p=2779 Note: This plugin is now part of the WordPress Plugin Repository as Remote Content Shortcode. The Remote Content Shortcode plugin will enable a shortcode to display remote content embedded into a post or page....

The post Remote Content Shortcode appeared first on Justin Silver.

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

Note: This plugin is now part of the WordPress Plugin Repository as Remote Content Shortcode.

The Remote Content Shortcode plugin will enable a shortcode to display remote content embedded into a post or page. In fact, it is being used to embed the code below from my SVN server. To use it, just place the code in your page where you want to remote content to be placed.

[remote_content url="http://www.example.com"/]

By default this will just fetch the content and drop it straight into the page, which isn’t always ideal (like when you are using a code formatter like I am). Because of this, there are a few more parameters you can pass to Remote Content Shortcode:

  • url – the url that you want to request.
  • method=[ GET | POST ] – set the request type, defaults to GET.
  • timeout=[ 0-9... ] – set the request timeout in seconds, defaults to 10 seconds.
  • userpwd=[ username:password | post_meta | site_option | constant ] – the username and password to send for BASIC authentication. It is recommended to not set the username and password directly in the tag, as it will be visible on your site if this plugin is disabled, and instead use one of the other options. By order of priority, if the value matches a post meta_key the meta_value is used, if it matches a site_option the option_value is used, and if it matches a constant the constant value is used, otherwise the data is passed as is. The format is username:password.
  • htmlentities=[ false | true ] – if you want to HTML encode the content for display set to true, defaults to false.
  • strip_tags=[ false | true ] – remove all tags from the remote content (after CSS selection).
  • decode_atts=[ false | true ] – the SyntaxHighlighter plugin will HTML encode your shortcode attributes, so attr="blah" becomes attr=&quot;blah&quot;. This fixes it to the intended value when set to true, defaults to false.
  • selector=[ CSS Selectors... ] – the CSS selector or comma separated list or selectors for the content you would like to display, for example div.main-content or div.this-class #this-id, defaults to the entire document.
  • remove=[ CSS Selectors... ] – the CSS selector or comma separated list or selectors for the content that you would like to remove from the content, for example h2.this-class or div#this-id, defaults to no replacement.
  • find=[ regex ] – use a PHP regular expression to find content and replace it based on the replaceattribute, for example ~http://([^\.]*?)\.example\.com~, defaults to disabled.
  • replace=[ regex ] – the replacement text to use with the results of the find regular expression, for example https://\\1.new-domain.com, defaults to empty string replacement.
  • cache=[true| false ] – set to false to prevent the contents from being cached in the WP-Cache/WordPress transients, defaults to true for performance.
  • cache_ttl=[ 0-9... 3600 ] – the cache expiration in seconds or 0 for as long as possible, defaults to 3600 seconds.
If you specify any content for Remote Content Shortcode, it will be sent to the remote server as the request data.
[remote_content url="http://www.example.com" method="POST"]
{ json: { example: request } }
[/remote_content]

This source files for Remote Content Shortcode are always up to date based on the trunk of the WordPress Plugin SVN repository.

remote-content-shortcode.php

<?php
/*
Plugin Name: Remote Content Shortcode
Plugin URI: http://www.doublesharp.com
Description: Embed remote content with a shortcode
Author: Justin Silver
Version: 1.5
Author URI: http://doublesharp.com
License: GPL2
*/

if ( ! class_exists( 'RemoteContentShortcode' ) ):

class RemoteContentShortcode {

	private static $instance;

	private function __construct() { }

	public static function init() {
		if ( ! is_admin() && ! self::$instance ) {
			self::$instance = new RemoteContentShortcode();
			self::$instance->add_shortcode();
		}
	}

	public function add_shortcode(){
		add_shortcode( 'remote_content', array( &$this, 'remote_content_shortcode' ) );
	}

	public function remote_content_shortcode( $atts, $content=null ) {
		// decode and remove quotes, if we wanted to (for use with SyntaxHighlighter)
		if ( isset( $atts['decode_atts'] ) ) {
			switch ( strtolower( html_entity_decode( $atts['decode_atts'] ) ) ) {
			 	case 'true': case '"true"':
					foreach ( $atts as $key => &$value ) {
						$value = html_entity_decode( $value );
						if ( strpos( $value, '"' ) === 0 ) $value = substr( $value, 1, strlen( $value ) - 2 );
					}
			 		break;
			 	default:
			 		break;
			}
		}

		$atts = shortcode_atts( 
			array(
				'userpwd' => false,
				'method' => 'GET',
				'timeout' => 10,
				'url' => '',
				'selector' => false,
				'remove' => false,
				'find' => false,
				'replace' => false,
				'htmlentities' => false,
				'params' => false,
				'strip_tags' => false,
				'cache' => true,
				'cache_ttl' => 3600,
			), 
			$atts
		);

		// convert %QUOT% to "
		$atts['find'] = $this->quote_replace( $atts['find'] );
		$atts['replace'] = $this->quote_replace( $atts['replace'] );

		// extract attributes
		extract( $atts );

		// normalize parameters
		$is_cache = strtolower( $cache ) != 'false';
		$is_htmlentities = strtolower( $htmlentities ) == 'true';
		$is_strip_tags = strtolower( $strip_tags ) == 'true';
		$method = strtoupper( $method );

		$group = 'remote_content_cache';
		$key = implode( $atts );
		$error = false;
		if ( ! $is_cache || false === ( $response = wp_cache_get( $key, $group ) ) ){

			// if we don't have a url, don't bother
			if ( empty( $url ) ) return;
			
			// change ampersands back since WP will encode them between the visual/text editor
			if ( strpos( $url, '&amp;' ) !== false ) {
				$url = str_replace( '&amp;', '&', $url );
			}

			// inherit params from the parent page query string
			if ( ! empty($params) ) {
				if (strpos($url, '?') === false) {
					$url .= '?';
				}

				$qs = explode( ',', trim( $params ) );

				foreach ( $qs as $q ) {
					$q = trim( $q );
					if ( strpos( $q, '=') !== false ) {
						$p = explode('=', $q );
						$q = trim( $p[0] );
						$v = trim( $p[1] );
					}
					if ( isset( $_REQUEST[$q] ) ) {
						$v = $_REQUEST[$q];
					}
					$url .= "&{$q}={$v}";
				}
			}

			// apply filters to the arguments
			$url = apply_filters( 'remote_content_shortcode_url', $url );
			$content = apply_filters( 'remote_content_shortcode_postfields', $content, $url );
			$ssl_verifyhost = apply_filters( 'remote_content_shortcode_ssl_verifyhost', false, $url );
			$ssl_verifypeer = apply_filters( 'remote_content_shortcode_ssl_verifypeer', false, $url );

			// get the user:password BASIC AUTH
			if ( ! empty( $userpwd ) ){
				global $post;
				if ( false!==( $meta_userpwd = get_post_meta( $userpwd, $post->ID, true ) ) ) {
					// if the userpwd is a post meta, use that
					$userpwd = $meta_userpwd;
				} elseif ( false !== ( $option_userpwd = get_option( $userpwd ) ) ) {
					// if the userpwd is a site option, use that
					$userpwd = $option_userpwd;
				} elseif ( defined( $userpwd ) ) {
					// if the userpwd is a constant, use that
					$userpwd = constant( $userpwd );
				} 
				/* lastly assume the userpwd is plaintext, this is not safe as it will be
				 displayed in the browser if this plugin is disabled */
			}

			// set up curl
			$ch = curl_init();
			// the url to request
			curl_setopt( $ch, CURLOPT_URL, $url );
			// follow redirects
			curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
			// set a timeout
			curl_setopt( $ch, CURLOPT_TIMEOUT, $timeout );
			// return to variable
			curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
			// (don't) verify host ssl cert
			curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, $ssl_verifyhost );
			// (don't) verify peer ssl cert	
			curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, $ssl_verifypeer );
			// send a user:password
			if ( ! empty( $userpwd ) ) {
				curl_setopt( $ch, CURLOPT_USERPWD, $userpwd );
			}
			// optionally POST
			if ( $method == 'POST' ) {
				curl_setopt( $ch, CURLOPT_POST, true );
			}
			// send content of tag
			if ( ! empty( $content ) ) {
				curl_setopt( $ch, CURLOPT_POSTFIELDS, $content );
			}
			// fetch remote contents
			if ( false === ( $response = curl_exec( $ch ) ) )	{
				// if we get an error, use that
				$error = curl_error( $ch );						
			}
			// close the resource
			curl_close( $ch );

			if ( $response ){
				if ( $selector || $remove ){
					// include phpQuery	
					include_once( 'inc/phpQuery.php' );
					// filter the content
					$response = apply_filters( 'remote_content_shortcode_phpQuery', $response, $url, $selector, $remove );
					// load the response HTML DOM
					phpQuery::newDocument( $response );
					// $remove defaults to false
					if ( $remove ) {
						// remove() the elements
						pq( $remove )->remove();
					}
					// use a CSS selector or default to everything
					$response = pq( $selector );
				}

				// perform a regex find and replace
				if ( $find ) {
					$response = preg_replace( $find, $replace | '', $response );
				}

				// strip the tags
				if ( $is_strip_tags ) {
					$response = strip_tags( $response );
				}

				// HTML encode the response
				if ( $is_htmlentities ) {
					$response = htmlentities( $response );
				}
			} else {
				// send back the error unmodified so we can debug
				$response = $error;
			}

			// Cache the result based on the TTL
			if ( $is_cache ) {
				wp_cache_set( $key, $response, $group, $cache_ttl );
			}
		}
		
		// filter the response
		return apply_filters( 'remote_content_shortcode_return', $response, $url );
	}

	private function quote_replace( $input ){
		if ( ! $input ) return false;
		return str_replace( '%QUOT%', '"', strval( $input ) );
	}
}

// init the class/shortcode
RemoteContentShortcode::init();

endif; //class exists

The post Remote Content Shortcode appeared first on Justin Silver.

]]>
https://www.justinsilver.com/technology/wordpress/wordpress-plugin-remote-content-shortcode/feed/ 3
WordPress Plugin: wp-slimstat-permalinks https://www.justinsilver.com/technology/wordpress/wordpress-plugin-wp-slimstat-permalinks/?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-plugin-wp-slimstat-permalinks https://www.justinsilver.com/technology/wordpress/wordpress-plugin-wp-slimstat-permalinks/#respond Sun, 04 Nov 2012 04:56:28 +0000 http://justin.ag/?p=2772 This plugin will maintain your WP SlimStat stats history for pages and posts when permalinks change for your page(s) or post(s). It does this by updating your {$wpdb->prefix)slim_stats table replacing the old permalink with...

The post WordPress Plugin: wp-slimstat-permalinks appeared first on Justin Silver.

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

This plugin will maintain your WP SlimStat stats history for pages and posts when permalinks change for your page(s) or post(s). It does this by updating your {$wpdb->prefix)slim_stats table replacing the old permalink with the new permalink in the resources column. Your permalinks can change when you update a post slug, date, etc or your permalink structure sitewide.

This file is always up to date based on the trunk of my SVN repository.

wp-slimstat-permalinks.php


The post WordPress Plugin: wp-slimstat-permalinks appeared first on Justin Silver.

]]>
https://www.justinsilver.com/technology/wordpress/wordpress-plugin-wp-slimstat-permalinks/feed/ 0
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
WP_HOME and WP_SITEURL for WordPress Multisite Development & Migration https://www.justinsilver.com/technology/wordpress/wp_home-and-wp_siteurl-for-wordpress-multisite-development-migration/?utm_source=rss&utm_medium=rss&utm_campaign=wp_home-and-wp_siteurl-for-wordpress-multisite-development-migration Fri, 19 Oct 2012 15:10:35 +0000 http://justin.ag/?p=2677 I needed to be able to copy the wp_options table(s) to my development environment as they contain configurations that are needed. To overcome the issue of not being able to set the WP_SITEURL and...

The post WP_HOME and WP_SITEURL for WordPress Multisite Development & Migration appeared first on Justin Silver.

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

I needed to be able to copy the wp_options table(s) to my development environment as they contain configurations that are needed. To overcome the issue of not being able to set the WP_SITEURL and WP_HOME values in WordPress MultiSite, I wrote a custom filter to replace the _config_wp_siteurl() and _config_wp_home() functions that are available for non-multisite installs that is included in a plugin that is available network-wide and is configured in wp-config.php. I am then able to copy all of the database tables except wp_site and wp_blogs to a local database.

I highly recommend the URL Token Replacement Techniques for WordPress 3.0 article by Chris Murphy to help handle URLs in your content.

This example assumes a subdomain multisite install, with a domain of example.com and two subdomains, www.example.com and second.example.com. The local development URLs will be www.example.local and second.example.local respectively.

WordPress Multisite Updates

Database Changes:

Update the domain value in wp_site:

UPDATE wp_site
SET domain = 'example.local'
WHERE domain = 'example.com';

Update the domain value(s) in wp_blogs:

UPDATE wp_blogs
SET domain = 'www.example.local'
WHERE domain = 'www.example.com';
UPDATE wp_blogs
SET domain = 'second.example.local'
WHERE domain = 'second.example.com';

Plugin Code:

The following plugin should be installed network-wide.

<?php
/*
Plugin Name: MultiSite WP_HOME and WP_SITEURL
Plugin URI: http://doublesharp.com/
Description: Allows wp_options values to be overwritten in wp-config.php for MultiSite
Author: Justin Silver
Version: 1.0
Author URI: http://doublesharp.com
License: GPL2
*/

/**
 * 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;
}
add_filter( 'option_siteurl', '_ms_config_wp_siteurl' );

/**
 * 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;
}
add_filter( 'option_home',    '_ms_config_wp_home'    );
?>

Configure wp-config.php:

Add new constants to wp-config.php. The primary site should use the standard WP_HOME and WP_SITEURL and the tertiary URLs should use WP_{$blog_id}_HOME and WP_{$blog_id}_SITEURL where {$blog_id} is the numeric blog ID value that you want to replace from the wp_blogs table.

define('WP_HOME',      'http://www.example.local');
define('WP_SITEURL',   'http://www.example.local');
define('WP_2_HOME',    'http://secondary.example.local');
define('WP_2_SITEURL', 'http://secondary.example.local');

This post is the result of a question that I originally asked on StackOverflow – Override WP_SITEURL and WP_HOME for WordPress Multisite – and ultimately answered with the help of Chris Murphy.

The post WP_HOME and WP_SITEURL for WordPress Multisite Development & Migration appeared first on Justin Silver.

]]>