__( 'Text', 'frontglut' ),
'textarea' => __( 'Textarea', 'frontglut' ),
'editor' => __( 'Rich editor', 'frontglut' ),
'number' => __( 'Number', 'frontglut' ),
'email' => __( 'Email', 'frontglut' ),
'url' => __( 'URL', 'frontglut' ),
'tel' => __( 'Phone', 'frontglut' ),
'password' => __( 'Password', 'frontglut' ),
'date' => __( 'Date', 'frontglut' ),
'color' => __( 'Colour', 'frontglut' ),
'select' => __( 'Select', 'frontglut' ),
'multiselect' => __( 'Multi select', 'frontglut' ),
'radio' => __( 'Radio buttons', 'frontglut' ),
'checkbox' => __( 'Checkbox', 'frontglut' ),
'checkboxes' => __( 'Checkbox group', 'frontglut' ),
'file' => __( 'File upload', 'frontglut' ),
'image' => __( 'Image upload', 'frontglut' ),
'gallery' => __( 'Gallery', 'frontglut' ),
'table' => __( 'Table', 'frontglut' ),
'hidden' => __( 'Hidden', 'frontglut' ),
'heading' => __( 'Heading', 'frontglut' ),
'label' => __( 'Info text', 'frontglut' ),
) );
}
/** Field types the form builder may offer for user-defined fields. */
public static function builder_types() {
$t = self::types();
return $t;
}
public static function defaults() {
return array( 'key' => '', 'type' => 'text', 'label' => '', 'required' => 0, 'placeholder' => '', 'help' => '', 'options' => '', 'default' => '', 'width' => '100', 'class' => '', 'storage' => 'meta', 'min' => '', 'max' => '', 'maxlength' => '', 'minlength' => '', 'logic' => array(), 'roles' => array(), 'multiple' => 0 );
}
/** Parse options: array(value=>label) or "value|Label" lines or "Label" lines. */
public static function options( array $f ) {
$o = isset( $f['options'] ) ? $f['options'] : array();
if ( is_array( $o ) ) {
return $o;
}
$out = array();
foreach ( preg_split( '/\r\n|\r|\n/', (string) $o ) as $line ) {
$line = trim( $line );
if ( '' === $line ) {
continue;
}
if ( false !== strpos( $line, '|' ) ) {
list( $v, $l ) = array_map( 'trim', explode( '|', $line, 2 ) );
} else {
$v = $l = $line;
}
$out[ $v ] = $l;
}
return $out;
}
/** Visibility under conditional logic given all current values. */
public static function is_visible( array $f, array $values ) {
$pre = apply_filters( 'frontglut_field_visible', null, $f, $values );
if ( null !== $pre ) {
return (bool) $pre; // add-ons implement richer logic (AND/OR groups).
}
if ( empty( $f['logic'] ) || empty( $f['logic']['field'] ) ) {
return true;
}
$l = $f['logic'];
$cur = isset( $values[ $l['field'] ] ) ? $values[ $l['field'] ] : '';
$cur = is_array( $cur ) ? $cur : array( $cur );
$val = isset( $l['value'] ) ? (string) $l['value'] : '';
switch ( isset( $l['op'] ) ? $l['op'] : 'eq' ) {
case 'neq':
return ! in_array( $val, array_map( 'strval', $cur ), true );
case 'contains':
foreach ( $cur as $c ) {
if ( false !== stripos( (string) $c, $val ) ) {
return true;
}
}
return false;
case 'empty':
return '' === implode( '', array_map( 'strval', $cur ) );
case 'notempty':
return '' !== implode( '', array_map( 'strval', $cur ) );
default:
return in_array( $val, array_map( 'strval', $cur ), true );
}
}
/** Full field with wrapper, label, help. */
public static function render( array $f, $value, $name, array $ctx = array() ) {
$f = array_merge( self::defaults(), $f );
$type = $f['type'];
if ( 'hidden' === $type ) {
return '';
}
$id = 'frontglut-f-' . preg_replace( '/[^a-z0-9_-]/i', '-', $name ) . '-' . wp_rand( 100, 999 );
$attrs = ' data-frontglut-key="' . esc_attr( $f['key'] ) . '"';
if ( ! empty( $f['logic']['field'] ) ) {
$lg = $f['logic'];
$lg['name'] = ! empty( $ctx['prefix'] ) ? $ctx['prefix'] . '[' . $lg['field'] . ']' : $lg['field'];
$attrs .= " data-frontglut-logic='" . esc_attr( wp_json_encode( $lg ) ) . "'";
}
$width = in_array( (string) $f['width'], array( '25', '33', '50', '66', '75', '100' ), true ) ? $f['width'] : '100';
$cls = 'frontglut-field frontglut-w-' . $width . ' frontglut-t-' . sanitize_html_class( $type ) . ( $f['class'] ? ' ' . esc_attr( $f['class'] ) : '' );
if ( ! empty( $f['required'] ) ) {
$cls .= ' is-required';
}
$out = '
';
if ( 'heading' === $type ) {
return $out . '
' . esc_html( $f['label'] ) . '
';
}
if ( 'label' === $type ) {
return $out . '' . wp_kses_post( wpautop( $f['help'] ? $f['help'] : $f['label'] ) ) . '
';
}
$bare = in_array( $type, array( 'checkbox' ), true );
if ( $f['label'] && ! $bare ) {
$out .= '';
}
$out .= self::control( $f, $value, $name, $id );
if ( $f['help'] ) {
$out .= '' . wp_kses_post( $f['help'] ) . '
';
}
return $out . '';
}
/** The bare input control. */
public static function control( array $f, $value, $name, $id = '' ) {
$f = array_merge( self::defaults(), $f );
$type = $f['type'];
$id = $id ? $id : 'frontglut-f-' . wp_rand( 1000, 9999 );
$n = esc_attr( $name );
$req = $f['required'] ? ' required' : '';
$ph = $f['placeholder'] ? ' placeholder="' . esc_attr( $f['placeholder'] ) . '"' : '';
$ml = $f['maxlength'] ? ' maxlength="' . (int) $f['maxlength'] . '"' : '';
$val = is_array( $value ) ? '' : (string) $value;
$custom = apply_filters( 'frontglut_field_render_' . $type, null, $f, $value, $name, $id );
if ( null !== $custom ) {
return $custom;
}
switch ( $type ) {
case 'textarea':
return '';
case 'editor':
$mode = isset( $f['editor'] ) ? $f['editor'] : 'classic';
if ( 'lite' === $mode ) {
return '' . self::lite_toolbar() . '
' . wp_kses_post( $val ) . '
';
}
if ( 'plain' === $mode ) {
return '';
}
ob_start();
wp_editor( $val, preg_replace( '/[^a-z0-9_]/', '_', strtolower( $id ) ), array( 'textarea_name' => $name, 'media_buttons' => false, 'textarea_rows' => 12, 'teeny' => true, 'quicktags' => true ) );
return '' . ob_get_clean() . '
';
case 'select':
$o = '';
case 'multiselect':
$cur = (array) $value;
$o = '';
case 'radio':
$o = '';
foreach ( self::options( $f ) as $k => $l ) {
$o .= '';
}
return $o . '
';
case 'checkboxes':
$cur = array_map( 'strval', (array) $value );
$o = '';
foreach ( self::options( $f ) as $k => $l ) {
$o .= '';
}
return $o . '
';
case 'checkbox':
return '';
case 'file':
case 'image':
case 'gallery':
return self::uploader( $f, $value, $name, $id );
case 'table':
return self::table_control( $f, $value, $name );
case 'number':
$a = '';
foreach ( array( 'min', 'max' ) as $k ) {
if ( '' !== (string) $f[ $k ] ) {
$a .= ' ' . $k . '="' . esc_attr( $f[ $k ] ) . '"';
}
}
return '';
case 'color':
return '';
case 'text':
case 'email':
case 'url':
case 'tel':
case 'password':
case 'date':
default:
$t = in_array( $type, array( 'email', 'url', 'tel', 'password', 'date' ), true ) ? $type : 'text';
$ac = 'password' === $t ? ' autocomplete="new-password"' : '';
return '';
}
}
private static function lite_toolbar() {
$b = array( 'bold' => 'B', 'italic' => 'I', 'formatBlock:h3' => 'H', 'formatBlock:blockquote' => '❝', 'insertUnorderedList' => '•', 'insertOrderedList' => '1.', 'createLink' => '🔗', 'removeFormat' => '✕' );
$o = '';
foreach ( $b as $cmd => $lbl ) {
$o .= '';
}
return $o;
}
private static function uploader( array $f, $value, $name, $id ) {
$multi = 'gallery' === $f['type'] || ! empty( $f['multiple'] );
$ids = array_filter( array_map( 'absint', is_array( $value ) ? $value : explode( ',', (string) $value ) ) );
$accept = 'image' === $f['type'] || 'gallery' === $f['type'] ? 'image/*' : '';
$o = '';
$o .= '
';
foreach ( $ids as $aid ) {
$o .= self::uploader_item( $aid, $name, $multi );
}
$o .= '
';
$o .= '
';
return $o;
}
public static function uploader_item( $aid, $name, $multi ) {
$aid = absint( $aid );
if ( ! $aid || ! get_post( $aid ) ) {
return '';
}
$thumb = wp_attachment_is_image( $aid ) ? wp_get_attachment_image( $aid, 'thumbnail' ) : '';
return '' . $thumb . '' . esc_html( wp_basename( (string) get_attached_file( $aid ) ) ) . '
';
}
private static function table_control( array $f, $value, $name ) {
$cols = array_filter( array_map( 'trim', explode( ',', isset( $f['columns'] ) ? $f['columns'] : 'Column 1,Column 2' ) ) );
$rows = is_array( $value ) && $value ? array_values( $value ) : array( array() );
$o = '';
}
/** Sanitize a raw submitted value for a field. */
public static function sanitize( array $f, $raw ) {
$f = array_merge( self::defaults(), $f );
$type = $f['type'];
$pre = apply_filters( 'frontglut_field_sanitize_' . $type, null, $f, $raw );
if ( null !== $pre ) {
return $pre;
}
switch ( $type ) {
case 'textarea':
return sanitize_textarea_field( is_array( $raw ) ? '' : $raw );
case 'editor':
return wp_kses_post( is_array( $raw ) ? '' : $raw );
case 'email':
return sanitize_email( is_array( $raw ) ? '' : $raw );
case 'url':
$u = is_array( $raw ) ? '' : trim( $raw );
return '' === $u ? '' : esc_url_raw( $u );
case 'number':
$n = is_array( $raw ) ? '' : trim( $raw );
if ( '' === $n || ! is_numeric( $n ) ) {
return '';
}
return $n + 0;
case 'date':
$d = is_array( $raw ) ? '' : trim( $raw );
return preg_match( '/^\d{4}-\d{2}-\d{2}$/', $d ) ? $d : '';
case 'color':
return (string) sanitize_hex_color( is_array( $raw ) ? '' : $raw );
case 'checkbox':
return ! empty( $raw ) ? '1' : '';
case 'select':
case 'radio':
$v = is_array( $raw ) ? '' : (string) $raw;
return array_key_exists( $v, self::options( $f ) ) || '' === $v ? $v : '';
case 'multiselect':
case 'checkboxes':
$ok = array_map( 'strval', array_keys( self::options( $f ) ) );
return array_values( array_filter( array_map( 'sanitize_text_field', array_filter( (array) $raw, 'is_scalar' ) ), function ( $v ) use ( $ok ) {
return in_array( (string) $v, $ok, true );
} ) );
case 'file':
case 'image':
case 'gallery':
$ids = is_array( $raw ) ? $raw : explode( ',', (string) $raw );
$ids = array_values( array_unique( array_filter( array_map( 'absint', $ids ) ) ) );
if ( 'gallery' !== $type && empty( $f['multiple'] ) ) {
$ids = array_slice( $ids, 0, 1 );
}
return $ids;
case 'table':
$out = array();
foreach ( (array) $raw as $row ) {
$row = array_map( function ( $c ) {
return sanitize_text_field( is_scalar( $c ) ? $c : '' );
}, (array) $row );
if ( '' !== implode( '', $row ) ) {
$out[] = array_values( $row );
}
}
return $out;
case 'password':
return is_array( $raw ) ? '' : (string) $raw; // never altered.
case 'heading':
case 'label':
return '';
default:
return sanitize_text_field( is_array( $raw ) ? '' : $raw );
}
}
public static function is_empty( $v ) {
if ( is_array( $v ) ) {
return 0 === count( array_filter( $v, function ( $x ) {
return '' !== $x && null !== $x;
} ) );
}
return '' === trim( (string) $v );
}
/** @return string|null error message */
public static function validate( array $f, $clean ) {
$f = array_merge( self::defaults(), $f );
$label = $f['label'] ? $f['label'] : $f['key'];
if ( ! empty( $f['required'] ) && self::is_empty( $clean ) ) {
/* translators: %s: field label */
return sprintf( __( '%s is required.', 'frontglut' ), $label );
}
if ( self::is_empty( $clean ) ) {
return null;
}
if ( 'email' === $f['type'] && ! is_email( $clean ) ) {
/* translators: %s: value. */
return sprintf( __( '%s must be a valid email.', 'frontglut' ), $label );
}
if ( 'url' === $f['type'] && ! filter_var( $clean, FILTER_VALIDATE_URL ) ) {
/* translators: %s: value. */
return sprintf( __( '%s must be a valid URL.', 'frontglut' ), $label );
}
if ( 'number' === $f['type'] ) {
if ( '' !== (string) $f['min'] && $clean < $f['min'] ) {
/* translators: 1: value, 2: value. */
return sprintf( __( '%1$s must be at least %2$s.', 'frontglut' ), $label, $f['min'] );
}
if ( '' !== (string) $f['max'] && $clean > $f['max'] ) {
/* translators: 1: value, 2: value. */
return sprintf( __( '%1$s must be at most %2$s.', 'frontglut' ), $label, $f['max'] );
}
}
if ( is_string( $clean ) ) {
$len = function_exists( 'mb_strlen' ) ? mb_strlen( $clean ) : strlen( $clean );
if ( $f['maxlength'] && $len > (int) $f['maxlength'] ) {
/* translators: 1: value, 2: value. */
return sprintf( __( '%1$s must be at most %2$d characters.', 'frontglut' ), $label, (int) $f['maxlength'] );
}
if ( $f['minlength'] && $len < (int) $f['minlength'] ) {
/* translators: 1: value, 2: value. */
return sprintf( __( '%1$s must be at least %2$d characters.', 'frontglut' ), $label, (int) $f['minlength'] );
}
}
return apply_filters( 'frontglut_field_validate', null, $f, $clean );
}
/**
* Process a set of fields from posted data.
* @return array [ values, errors ]
*/
public static function process( array $fields, array $post, array $ctx = array() ) {
$raw_all = array();
foreach ( $fields as $f ) {
if ( ! empty( $f['key'] ) ) {
$raw_all[ $f['key'] ] = isset( $post[ $f['key'] ] ) ? $post[ $f['key'] ] : '';
}
}
$values = array();
$errors = array();
foreach ( $fields as $f ) {
if ( empty( $f['key'] ) || in_array( isset( $f['type'] ) ? $f['type'] : '', array( 'heading', 'label' ), true ) ) {
continue;
}
if ( ! self::is_visible( $f, $raw_all ) ) {
continue; // hidden by logic: neither validated nor stored.
}
$clean = self::sanitize( $f, $raw_all[ $f['key'] ] );
$err = self::validate( $f, $clean );
if ( $err ) {
$errors[ $f['key'] ] = $err;
}
$values[ $f['key'] ] = $clean;
}
return array( $values, $errors );
}
}