', '<' );
public static function boot() {
add_filter( 'customfields_validate_field_schema', array( __CLASS__, 'validate_field' ), 5, 3 );
add_filter( 'customfields_validate_group_schema', array( __CLASS__, 'validate_group' ), 5, 2 );
add_filter( 'customfields_field_is_active', array( __CLASS__, 'is_active' ), 10, 4 );
add_action( 'customfields_after_render_field', array( __CLASS__, 'render' ), 10, 3 );
add_action( 'customfields_field_editor_settings', array( __CLASS__, 'editor' ), 5, 2 );
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'assets' ) );
}
public static function assets() {
$screen = get_current_screen();
if ( ! $screen || ! ( in_array( $screen->base, array( 'post', 'profile', 'user-edit', 'edit-tags', 'term' ), true ) || false !== strpos( $screen->id, 'customfields' ) ) ) { return; }
wp_enqueue_script( 'customfields-conditions', CUSTOMFIELDS_URL . 'assets/conditions.js', array('wp-i18n'), CUSTOMFIELDS_VERSION, true );
wp_set_script_translations('customfields-conditions', 'customfields-com', CUSTOMFIELDS_PATH . 'languages');
if ( current_user_can( 'upload_files' ) ) { wp_enqueue_media(); }
}
public static function validate_field( $field, $raw, $depth = 0 ) {
if ( is_wp_error( $field ) ) { return $field; }
$rules = $raw['conditional_logic'] ?? array();
if ( is_string( $rules ) ) { $rules = '' === trim( $rules ) ? array() : json_decode( $rules, true ); }
if ( ! is_array( $rules ) || count( $rules ) > 20 ) { return new \WP_Error( 'conditions', __( 'Use at most 20 visibility rules.', 'customfields-com' ) ); }
$field['conditional_logic'] = array();
foreach ( $rules as $rule ) {
if ( ! is_array( $rule ) || ! isset( $rule['field'], $rule['operator'] ) || ! is_string( $rule['field'] ) || ! preg_match( '/^[a-z][a-z0-9_]{0,63}$/D', $rule['field'] ) || ! in_array( $rule['operator'], self::OPERATORS, true ) || ! is_scalar( $rule['value'] ?? '' ) || strlen( (string) ( $rule['value'] ?? '' ) ) > 1000 ) { return new \WP_Error( 'conditions', __( 'Each visibility rule needs a field name, a supported operator, and a simple value.', 'customfields-com' ) ); }
if ( $rule['field'] === $field['name'] ) { return new \WP_Error( 'conditions_self', __( 'A field cannot control its own visibility.', 'customfields-com' ) ); }
if ( in_array( $rule['operator'], array( '>', '<' ), true ) && ( ! is_numeric( $rule['value'] ?? '' ) || ! is_finite( (float) $rule['value'] ) ) ) { return new \WP_Error( 'conditions_number', __( 'Greater/less than rules require a finite numeric value.', 'customfields-com' ) ); }
$field['conditional_logic'][] = array( 'field' => $rule['field'], 'operator' => $rule['operator'], 'value' => (string) ( $rule['value'] ?? '' ) );
}
$relation = $raw['condition_relation'] ?? 'all';
if ( ! in_array( $relation, array( 'all', 'any' ), true ) ) { return new \WP_Error( 'conditions_relation', __( 'Visibility must match all or any rules.', 'customfields-com' ) ); }
$field['condition_relation'] = $relation;
return $field;
}
public static function validate_group( $group, $raw ) {
if ( is_wp_error( $group ) ) { return $group; }
$valid = self::validate_fields( $group['fields'] );
return is_wp_error( $valid ) ? $valid : $group;
}
public static function validate_fields( $fields ) {
$by_name = array_column( $fields, null, 'name' );
foreach ( $fields as $field ) {
foreach ( $field['conditional_logic'] ?? array() as $rule ) {
if ( ! isset( $by_name[$rule['field']] ) || ! in_array( $by_name[$rule['field']]['type'], Fields::CORE_TYPES, true ) ) { return new \WP_Error( 'conditions_reference', __( 'Visibility rules must reference a basic field in the same group.', 'customfields-com' ) ); }
}
}
$visited = array();
foreach ( $fields as $field ) { if ( self::cycle( $field['name'], $by_name, $visited ) ) { return new \WP_Error( 'conditions_cycle', __( 'Visibility rules cannot form a dependency cycle.', 'customfields-com' ) ); } }
return true;
}
private static function cycle( $name, $fields, &$visited ) {
if ( isset( $visited[$name] ) ) { return 1 === $visited[$name]; }
$visited[$name] = 1;
foreach ( $fields[$name]['conditional_logic'] ?? array() as $rule ) { if ( self::cycle( $rule['field'], $fields, $visited ) ) { return true; } }
$visited[$name] = 2;
return false;
}
/** Resolve controllers first so visibility follows sanitized or retained values, never discarded input. */
public static function resolve_values( $fields, $submitted, $stored, $validator, $object_id = 0 ) {
$by_name = array_column( $fields, null, 'name' );
$values = $submitted; $updates = array(); $resolved = array(); $visiting = array(); $errors = new \WP_Error();
$resolve = function ( $name ) use ( &$resolve, &$values, &$updates, &$resolved, &$visiting, $by_name, $stored, $validator, $object_id, $errors ) {
if ( isset( $resolved[$name] ) ) { return; }
if ( ! isset( $by_name[$name] ) ) { unset( $values[$name] ); return; }
if ( isset( $visiting[$name] ) ) {
$errors->add( $name, __( 'Visibility rules contain an invalid dependency.', 'customfields-com' ) );
unset( $values[$name] ); return;
}
$visiting[$name] = true;
$field = $by_name[$name];
foreach ( $field['conditional_logic'] ?? array() as $rule ) { $resolve( $rule['field'] ); }
$condition_field = $field;
$condition_field['_customfields_controller_types'] = array_column( $by_name, 'type', 'name' );
if ( ! apply_filters( 'customfields_field_is_active', true, $condition_field, $values, $object_id ) ) {
$values[$name] = array_key_exists( $name, $stored ) ? $stored[$name] : ( $field['default'] ?? '' );
} else {
$clean = $validator( $field );
if ( is_wp_error( $clean ) ) {
$errors->add( $name, $field['label'] . ': ' . $clean->get_error_message() );
unset( $values[$name] );
} else { $updates[$name] = $clean; $values[$name] = $clean; }
}
unset( $visiting[$name] ); $resolved[$name] = true;
};
foreach ( array_keys( $by_name ) as $name ) { $resolve( $name ); }
if ( $errors->has_errors() ) { return $errors; }
$ordered = array();
foreach ( array_keys( $by_name ) as $name ) { if ( array_key_exists( $name, $updates ) ) { $ordered[$name] = $updates[$name]; } }
return $ordered;
}
public static function is_active( $active, $field, $values, $object_id = 0, $controller_types = array() ) {
if ( ! $active || empty( $field['conditional_logic'] ) || ! is_array( $values ) ) { return $active; }
$controller_types = $field['_customfields_controller_types'] ?? $controller_types;
// Missing or malformed controller values leave fields visible: uncertainty never suppresses validation.
foreach ( $field['conditional_logic'] as $rule ) {
if ( ! array_key_exists( $rule['field'], $values ) || ( ! is_scalar( $values[$rule['field']] ) && ! is_array( $values[$rule['field']] ) ) ) { return true; }
if ( is_array( $values[$rule['field']] ) ) { foreach ( $values[$rule['field']] as $key => $v ) { if ( '__present' !== $key && ( ! ctype_digit( (string) $key ) || ! is_scalar( $v ) ) ) { return true; } } }
}
$any = 'any' === ( $field['condition_relation'] ?? 'all' );
foreach ( $field['conditional_logic'] as $rule ) {
$value = $values[$rule['field']];
$numeric = 'number' === ( $controller_types[$rule['field']] ?? '' ) || is_float( $value );
$number = $numeric && is_scalar( $value ) && '' !== (string) $value && is_numeric( $value ) && is_finite( (float) $value ) ? (float) $value : null;
if ( null !== $number ) { $value = $number; }
if ( is_array( $value ) ) { unset( $value['__present'] ); $value = array_map( 'strval', array_values( $value ) ); }
else { $value = is_bool( $value ) ? ( $value ? '1' : '0' ) : (string) $value; }
$empty = array() === $value || '' === $value;
$equal = is_array( $value ) ? in_array( $rule['value'], $value, true ) : $value === $rule['value'];
if ( null !== $number && is_numeric( $rule['value'] ) && is_finite( (float) $rule['value'] ) ) { $equal = $number === (float) $rule['value']; }
switch ( $rule['operator'] ) {
case '!=': $match = ! $equal; break;
case 'empty': $match = $empty; break;
case '!empty': $match = ! $empty; break;
case 'contains': $match = is_array( $value ) ? $equal : false !== strpos( $value, $rule['value'] ); break;
case '>': case '<':
$match = ! is_array( $value ) && is_numeric( $value ) && is_finite( (float) $value ) && ( '>' === $rule['operator'] ? (float) $value > (float) $rule['value'] : (float) $value < (float) $rule['value'] ); break;
default: $match = $equal;
}
if ( $any && $match ) { return true; }
if ( ! $any && ! $match ) { return false; }
}
return ! $any;
}
public static function render( $field, $value, $input_name ) {
if ( in_array( $field['type'], Fields::CORE_TYPES, true ) ) {
// Options recovery can render unsaved input; its condition baseline must remain stored data.
if ( 'customfields_pro_values[' . $field['name'] . ']' === $input_name ) {
$options = get_option( 'customfields_pro_values', array() );
$value = is_array( $options ) && array_key_exists( $field['name'], $options ) ? $options[$field['name']] : ( $field['default'] ?? '' );
}
echo '';
}
if ( ! empty( $field['conditional_logic'] ) ) { echo ''; }
}
/** Return only the permitted basic-field values from this completed options write. */
public static function option_values( $groups, $values ) {
$saved = array();
foreach ( $groups as $group ) {
foreach ( $group['fields'] as $field ) {
if ( in_array( $field['type'], Fields::CORE_TYPES, true ) && array_key_exists( $field['name'], $values ) ) { $saved[$field['name']] = $values[$field['name']]; }
}
}
return $saved;
}
public static function editor( $field, $prefix ) {
echo ' ' . esc_html__( 'Use field names from this group. Hidden fields retain stored values. Visibility does not control access.', 'customfields-com' ) . '' . esc_html__( 'Conditional visibility', 'customfields-com' ) . '