, values?: array} $state */ public function render( int $form_id, array $state, int $now, string $return_url ): string { $form = $this->forms->find( $form_id ); if ( null === $form ) { return ''; } $definition = $form['definition']; $status = (string) ( $state['status'] ?? '' ); $message = (string) ( $state['message'] ?? '' ); $errors = (array) ( $state['errors'] ?? array() ); $values = (array) ( $state['values'] ?? array() ); $prefix = 'fxd-' . $form_id; if ( 'sent' === $status ) { /** * Filters markup shown under the thank-you message, for example poll results. The * add-on is responsible for escaping. * * @param string $markup Markup, empty by default. * @param int $form_id Form id. * @param FormDefinition $definition Definition. */ $after = (string) apply_filters( 'forensix_form_sent_markup', '', $form_id, $definition ); return '

' . esc_html( $message ) . '

' . $after . '
'; } /** * Lets an add-on show something else in place of the form, for example while the form is * closed or behind a password. Return markup (the add-on escapes it), or an empty string to * show the form. * * @param string $markup Markup, empty by default. * @param int $form_id Form id. * @param FormDefinition $definition Definition. * @param int $now Unix time. */ $locked = (string) apply_filters( 'forensix_form_locked_markup', '', $form_id, $definition, $now ); if ( '' !== $locked ) { return '
' . $locked . '
'; } $html = '
'; if ( 'invalid' === $status || 'rejected' === $status ) { $html .= ''; } /** * Fires when a form is about to be rendered, e.g. to load an add-on's script. * * @param int $form_id Form id. * @param FormDefinition $definition Definition. */ do_action( 'forensix_form_rendering', $form_id, $definition ); /** * Filters extra attributes of the form element (name => value, values are escaped here). * * @param array $attributes Attributes. * @param int $form_id Form id. * @param FormDefinition $definition Definition. */ $html .= '
'; $html .= ''; $html .= ''; $html .= ''; $html .= ''; // Hidden from people and screen readers; bots fill it. $html .= ''; /** * Filters markup placed before the fields. The add-on is responsible for escaping. * * @param string $markup Markup, empty by default. * @param int $form_id Form id. * @param FormDefinition $definition Definition. */ $html .= (string) apply_filters( 'forensix_form_before_fields', '', $form_id, $definition ); foreach ( $definition->fields as $field ) { $html .= $this->field( $prefix, $field, (string) ( $values[ $field['id'] ] ?? '' ), $errors[ $field['id'] ] ?? '' ); } /** * Filters markup placed after the fields. The add-on is responsible for escaping. * * @param string $markup Markup, empty by default. * @param int $form_id Form id. * @param FormDefinition $definition Definition. */ $html .= (string) apply_filters( 'forensix_form_after_fields', '', $form_id, $definition ); if ( $definition->settings['captcha'] ) { $html .= Captcha::markup(); } $html .= '

'; $html .= '
'; return $html; } /** * Markup of one field. * * @param string $prefix Id prefix of the form. * @param array $field Field. * @param string $value Entered value. * @param string $error Error message, empty if none. * @phpstan-param array{id: string, type: string, label: string, required: bool, options: string[], help: string, placeholder: string, default: string, min: string, max: string, step: string, css: string, ext: array} $field */ private function field( string $prefix, array $field, string $value, string $error ): string { /** * Lets an add-on draw a field of its own type or replace the markup of a field. Return a * string to use it; the add-on is responsible for escaping and for the labels. * * @param string|null $markup Null to use the normal markup. * @param string $prefix Id prefix of the form. * @param array $field Field. * @param string $value Entered value. * @param string $error Error message, empty if none. */ $custom = apply_filters( 'forensix_form_render_field', null, $prefix, $field, $value, $error ); if ( is_string( $custom ) ) { return $custom; } $id = $prefix . '-' . $field['id']; $name = $field['id']; $type = $field['type']; $describes = array(); if ( '' !== $field['help'] ) { $describes[] = $id . '-help'; } if ( '' !== $error ) { $describes[] = $id . '-error'; } $attrs = ( array() !== $describes ? ' aria-describedby="' . esc_attr( implode( ' ', $describes ) ) . '"' : '' ) . ( '' !== $error ? ' aria-invalid="true"' : '' ) . ( $field['required'] ? ' required' : '' ); $mark = $field['required'] ? ' ' : ''; $help = '' !== $field['help'] ? '

' . esc_html( $field['help'] ) . '

' : ''; $fault = '' !== $error ? '

' . esc_html( $error ) . '

' : ''; /** * Filters extra attributes of the element around a field (name => value, escaped here). * * @param array $attributes Attributes. * @param array $field Field. */ $open = '
'; if ( 'heading' === $type ) { return $open . '

' . esc_html( $field['label'] ) . '

' . ( '' !== $field['help'] ? '

' . esc_html( $field['help'] ) . '

' : '' ) . '
'; } if ( 'divider' === $type ) { return $open . '
'; } if ( 'hidden' === $type ) { return ''; } if ( '' === $value && '' !== $field['default'] && ! in_array( $type, array( 'checkbox', 'consent' ), true ) ) { $value = $field['default']; } if ( 'name' === $type ) { $parts = explode( ' ', $value, 2 ); $html = $open . '' . esc_html( $field['label'] ) . $mark . '' . $help . '
'; $names = array( 'first' => array( __( 'First name', 'forensix-digital' ), 'given-name', $parts[0] ), 'last' => array( __( 'Last name', 'forensix-digital' ), 'family-name', $parts[1] ?? '' ), ); foreach ( $names as $key => $part ) { $part_id = 'first' === $key ? $id : $id . '-' . $key; $html .= '

'; } return $html . '
' . $fault . ''; } if ( in_array( $type, array( 'radio', 'checkbox' ), true ) ) { $chosen = 'checkbox' === $type ? array_map( 'trim', explode( ',', $value ) ) : array( $value ); $html = $open . '' . esc_html( $field['label'] ) . $mark . '' . $help; foreach ( $field['options'] as $index => $option ) { $option_id = $id . '-' . $index; $html .= '

'; } return $html . $fault . ''; } if ( 'consent' === $type ) { return $open . '

' . $help . $fault . ''; } $html = $open . '' . $help; if ( 'textarea' === $type ) { $html .= ''; } elseif ( 'select' === $type ) { $html .= ''; } elseif ( 'range' === $type ) { $min = '' !== $field['min'] ? $field['min'] : '0'; $max = '' !== $field['max'] ? $field['max'] : '10'; $step = '' !== $field['step'] ? $field['step'] : '1'; $start = '' !== $value ? $value : $min; $html .= ''; } else { $input_type = in_array( $type, array( 'number', 'url' ), true ) ? 'text' : $type; $autocomplete = array( 'email' => 'email', 'tel' => 'tel', 'url' => 'url', ); $html .= ''; } return $html . $fault . ''; } /** * Attributes as markup. Names are limited to lowercase letters, digits and dashes. * * @param mixed $attributes Name => value. */ private static function attributes( $attributes ): string { $html = ''; foreach ( is_array( $attributes ) ? $attributes : array() as $name => $value ) { // Event handlers (on...) are never accepted; scripts belong in enqueued files. if ( 1 === preg_match( '/^[a-z][a-z0-9-]*$/', (string) $name ) && 0 !== strpos( (string) $name, 'on' ) && is_scalar( $value ) ) { $html .= ' ' . $name . '="' . esc_attr( (string) $value ) . '"'; } } return $html; } }