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.
]]>