|null $layout Defaults to FormLayout::current(). */ public static function fields( ?array $layout = null ): string { $out = ''; foreach ( $layout ?? FormLayout::current() as $entry ) { if ( FormLayout::HIDDEN !== $entry['answer'] ) { $out .= self::field( $entry ); } } return '
' . $out . '
'; } /** * The guest's part of the form, split the way the booking flow asks it * once a time is picked (the date, guests and time are asked before): * * - `contact`: the name, phone and email, and the owner's required fields * - `more`: the other optional fields, each folded under its label * - `consent`: the owner's checkboxes, such as "Send me offers by email" * * @param list|null $layout Defaults to FormLayout::current(). * @return array{contact:string,more:string,consent:string} */ public static function flow( ?array $layout = null ): array { $out = [ 'contact' => '', 'more' => '', 'consent' => '', ]; foreach ( $layout ?? FormLayout::current() as $entry ) { if ( FormLayout::HIDDEN === $entry['answer'] || ( ! $entry['custom'] && in_array( $entry['id'], [ 'date', 'party_size', 'time' ], true ) ) ) { continue; } if ( $entry['custom'] && 'checkbox' === $entry['type'] ) { $out['consent'] .= self::field( $entry ); } elseif ( FormLayout::REQUIRED === $entry['answer'] || ( ! $entry['custom'] && 'notes' !== $entry['id'] ) ) { $out['contact'] .= self::field( $entry ); } else { $out['more'] .= sprintf( '
%1$s
%2$s
', esc_html( FormLayout::label( $entry ) ), self::field( array_merge( $entry, [ 'width' => 'full' ] ) ) ); } } foreach ( [ 'contact', 'consent' ] as $part ) { if ( '' !== $out[ $part ] ) { $out[ $part ] = '
' . $out[ $part ] . '
'; } } return $out; } /** * The tags and attributes the fields are built from, for wp_kses() where * the form prints them. * * @return array> */ public static function allowed_html(): array { $common = [ 'class' => true, 'id' => true, 'hidden' => true, 'aria-describedby' => true, ]; $input = $common + [ 'type' => true, 'name' => true, 'value' => true, 'min' => true, 'max' => true, 'step' => true, 'maxlength' => true, 'inputmode' => true, 'autocomplete' => true, 'placeholder' => true, 'required' => true, 'disabled' => true, 'data-fxtr-date' => true, 'data-disabled-weekdays' => true, 'data-disabled-dates' => true, ]; return [ 'div' => $common, 'p' => $common, 'span' => $common, 'label' => $common, 'fieldset' => $common, 'legend' => $common, 'details' => $common, 'summary' => $common, 'input' => $input, 'textarea' => $input + [ 'rows' => true ], 'select' => $input, 'option' => [ 'value' => true, 'selected' => true, ], ]; } /** * @param Entry $entry */ private static function field( array $entry ): string { $required = FormLayout::REQUIRED === $entry['answer']; $label = esc_html( FormLayout::label( $entry ) ) . ( $required ? ' *' : '' ); $help = ''; $describe = ''; if ( '' !== $entry['help'] ) { $id = 'fxtr-help-' . ( ++self::$help ); $help = sprintf( '%s', esc_attr( $id ), esc_html( $entry['help'] ) ); $describe = sprintf( ' aria-describedby="%s"', esc_attr( $id ) ); } $row = 'fxtr-booking-form__row' . ( 'half' === $entry['width'] ? ' fxtr-booking-form__row--half' : '' ); if ( 'radio' === $entry['type'] ) { return sprintf( '
%s%s%s
', esc_attr( $row ), $label, self::radios( $entry, $required, $describe ), $help ); } if ( 'checkbox' === $entry['type'] ) { return sprintf( '

%6$s

', esc_attr( $row ), esc_attr( self::name( $entry ) ), $required ? ' required' : '', $describe, $label, $help ); } return sprintf( '

', esc_attr( $row ), $label, self::control( $entry, $required, $describe ), $help ); } /** * @param Entry $entry */ private static function control( array $entry, bool $required, string $describe ): string { $name = esc_attr( self::name( $entry ) ); $req = $required ? ' required' : ''; $extra = $req . $describe . ( '' !== $entry['placeholder'] ? sprintf( ' placeholder="%s"', esc_attr( $entry['placeholder'] ) ) : '' ); switch ( $entry['custom'] ? 'custom-' . $entry['type'] : $entry['id'] ) { case 'date': return sprintf( '', esc_attr( current_time( 'Y-m-d' ) ), BlockRegistry::closed_day_attributes(), $describe ); case 'party_size': return sprintf( '', (int) Policy::party_max(), $describe ); case 'time': return sprintf( '', $describe, esc_html__( 'Pick a date first', 'flexatech-table-reservations' ) ); case 'customer_name': return sprintf( '', $extra ); case 'phone': return sprintf( '', $extra ); case 'email': return sprintf( '', $extra ); case 'notes': return sprintf( '', $extra ); case 'custom-textarea': return sprintf( '', $name, $extra ); case 'custom-select': $options = sprintf( '', esc_html( '' !== $entry['placeholder'] ? $entry['placeholder'] : __( 'Choose…', 'flexatech-table-reservations' ) ) ); foreach ( $entry['options'] as $choice ) { $options .= sprintf( '', esc_attr( $choice['value'] ), esc_html( $choice['label'] ) ); } return sprintf( '', $name, $req, $describe, $options ); case 'custom-date': return sprintf( '', $name, $req, $describe ); case 'custom-number': return sprintf( '', $name, $extra ); case 'custom-email': return sprintf( '', $name, $extra ); case 'custom-tel': return sprintf( '', $name, $extra ); default: return sprintf( '', $name, $extra ); } } /** * @param Entry $entry */ private static function radios( array $entry, bool $required, string $describe ): string { $out = ''; foreach ( $entry['options'] as $i => $choice ) { $out .= sprintf( '', esc_attr( self::name( $entry ) ), esc_attr( $choice['value'] ), $required && 0 === $i ? ' required' : '', 0 === $i ? $describe : '', esc_html( $choice['label'] ) ); } return $out; } /** * @param Entry $entry */ private static function name( array $entry ): string { return $entry['custom'] ? 'fields[' . $entry['id'] . ']' : $entry['id']; } }