The post WordPress Plugin: validated-field-for-acf appeared first on Justin Silver.
]]>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
This file is always up to date based on the trunk of the WordPress.org SVN repository.
<?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;
<?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 'a' to match A-Za-z, '9' to match 0-9, and '*' 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>&$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;
<?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 'a' to match A-Za-z, '9' to match 0-9, and '*' 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>&$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;
/*
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);
.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.
]]>