__( 'Contact form', 'alpeg-forms' ),
'submitLabel' => __( 'Send', 'alpeg-forms' ),
'successMessage' => __( 'Thank you! Your message has been received. We will get back to you as soon as possible.', 'alpeg-forms' ),
'confirmationSubject' => __( 'We have received your message', 'alpeg-forms' ),
'confirmationMessage' => __( "Hello,\n\nthank you for your message. We have received it and will get back to you as soon as possible.\n\nHere is a copy of your details:\n{fields}", 'alpeg-forms' ),
'firstStepTitle' => __( 'Your details', 'alpeg-forms' ),
);
}
/**
* Block render callback.
*
* @param array $attributes Attributes.
* @param string $content Content (empty).
* @param \WP_Block $block Block instance.
* @return string
*/
public static function render( $attributes, $content = '', $block = null ) {
$fields = Fields::normalize( isset( $attributes['fields'] ) ? $attributes['fields'] : array() );
$inputs = array_filter( $fields, array( Fields::class, 'is_input' ) );
if ( ! $inputs ) {
return '';
}
wp_enqueue_style( 'alpeg-forms-frontend' );
$d = self::defaults();
$form_id = isset( $attributes['formId'] ) && '' !== $attributes['formId'] ? substr( sanitize_key( $attributes['formId'] ), 0, 64 ) : 'af_' . substr( md5( wp_json_encode( $fields ) ), 0, 10 );
$title = self::attr( $attributes, 'formTitle', $d['formTitle'] );
$post_id = ( $block instanceof \WP_Block && isset( $block->context['postId'] ) ) ? (int) $block->context['postId'] : (int) get_queried_object_id();
$steps = Fields::steps( $fields, self::attr( $attributes, 'firstStepTitle', $d['firstStepTitle'] ) );
$multi = count( $steps ) > 1;
$files = (bool) wp_list_filter( $fields, array( 'type' => 'file' ) );
$token = Token::encode(
array(
'v' => 2,
'fid' => $form_id,
'title' => $title,
'fields' => $fields,
'notify' => sanitize_text_field( self::attr( $attributes, 'notifyEmail', '' ) ),
'subject' => sanitize_text_field( self::attr( $attributes, 'notifySubject', '' ) ),
'attach' => $files && ! empty( $attributes['attachFiles'] ),
'confirm' => array(
'on' => ! empty( $attributes['sendConfirmation'] ),
'subject' => sanitize_text_field( self::attr( $attributes, 'confirmationSubject', $d['confirmationSubject'] ) ),
'message' => sanitize_textarea_field( self::attr( $attributes, 'confirmationMessage', $d['confirmationMessage'] ) ),
),
'success' => sanitize_textarea_field( self::attr( $attributes, 'successMessage', $d['successMessage'] ) ),
'pid' => $post_id,
'blog' => get_current_blog_id(),
'ts' => time(),
)
);
++self::$instance;
$uid = $form_id . '-' . self::$instance;
// Result of a submission without JavaScript (redirect back).
$state = self::no_js_state( $form_id );
$style = '';
$accent = sanitize_hex_color( self::attr( $attributes, 'accentColor', '' ) );
if ( $accent ) {
$style = '--af-accent:' . $accent . ';';
}
$wrapper = get_block_wrapper_attributes(
array(
'class' => 'alpeg-form' . ( $multi ? ' alpeg-form--steps' : '' ),
'style' => $style,
)
);
$config = array(
'i18n' => array_merge(
Fields::messages(),
array(
/* translators: 1: current step, 2: number of steps, 3: step title */
'step' => __( 'Step %1$d of %2$d: %3$s', 'alpeg-forms' ),
'error' => __( 'Something went wrong while sending. Please try again.', 'alpeg-forms' ),
'network' => __( 'No connection to the server. Please check your internet connection and try again.', 'alpeg-forms' ),
'too_large' => __( 'The uploaded files are too large in total. Please send smaller files.', 'alpeg-forms' ),
'units' => array( 'B', 'KB', 'MB', 'GB' ),
)
),
'steps' => count( $steps ),
);
ob_start();
?>
1 ) {
/* translators: %d: max number of files */
$info .= ' · ' . sprintf( __( 'up to %d files', 'alpeg-forms' ), $f['max_files'] );
}
$help = trim( $help . ( $help ? ' – ' : '' ) . $info );
}
if ( $help ) {
$describe[] = $help_id;
}
if ( $error ) {
$describe[] = $err_id;
}
$describe = $describe ? ' aria-describedby="' . esc_attr( implode( ' ', $describe ) ) . '"' : '';
$classes = 'alpeg-form__field alpeg-form__field--' . $f['type'] . ( 'half' === $f['width'] ? ' alpeg-form__field--half' : '' ) . ( $error ? ' has-error' : '' );
$star = $f['required'] ? ' *' : '';
$data = ' data-af-field="' . esc_attr( $f['id'] ) . '" data-af-type="' . esc_attr( $f['type'] ) . '" data-af-label="' . esc_attr( wp_strip_all_tags( $f['label'] ) ) . '"' . ( $f['required'] ? ' data-af-required' : '' );
if ( $f['condition'] ) {
$data .= " data-af-cond='" . esc_attr( wp_json_encode( $f['condition'] ) ) . "'";
}
echo ''; // phpcs:ignore WordPress.Security.EscapeOutput
switch ( $f['type'] ) {
case 'textarea':
echo '
'; // phpcs:ignore WordPress.Security.EscapeOutput
echo '
'; // phpcs:ignore WordPress.Security.EscapeOutput
break;
case 'select':
echo '
'; // phpcs:ignore WordPress.Security.EscapeOutput
echo '
';
break;
case 'radio':
case 'checkbox':
$input_type = 'radio' === $f['type'] ? 'radio' : 'checkbox';
$input_name = 'checkbox' === $f['type'] ? $name . '[]' : $name;
$checked = (array) $value;
// Radios form a radiogroup, which supports aria-required/aria-invalid. A checkbox group is a plain
// group: it gets no ARIA state, but announces "required" through hidden legend text.
if ( 'radio' === $f['type'] ) {
$group = ' role="radiogroup"' . ( $f['required'] ? ' aria-required="true"' : '' ) . $invalid;
$sr = '';
} else {
$group = '';
$sr = $f['required'] ? '
' . esc_html__( '(required)', 'alpeg-forms' ) . '' : '';
}
echo '
';
break;
case 'consent':
$policy = get_privacy_policy_url();
$text = str_replace( '{datenschutz}', esc_url( $policy ? $policy : home_url( '/' ) ), $f['label'] );
$text = str_replace( '{privacy}', esc_url( $policy ? $policy : home_url( '/' ) ), $text );
$text = preg_replace( '/
]*\btarget=)/i', ' ' . wp_kses( $text, Fields::consent_kses() ) . $star . ''; // phpcs:ignore WordPress.Security.EscapeOutput
break;
case 'file':
$exts = Files::allowed_extensions( $f );
$multiple = $f['max_files'] > 1;
$accept = implode( ',', array_map( static function ( $e ) { return '.' . $e; }, $exts ) );
echo ''; // phpcs:ignore WordPress.Security.EscapeOutput
echo ''; // phpcs:ignore WordPress.Security.EscapeOutput
break;
default:
$auto = array(
'email' => 'email',
'tel' => 'tel',
'url' => 'url',
);
$ac = isset( $auto[ $f['type'] ] ) ? ' autocomplete="' . $auto[ $f['type'] ] . '"' : '';
if ( 'text' === $f['type'] && preg_match( '/\bname\b|vorname|nachname/i', $f['id'] . ' ' . $f['label'] ) ) {
$ac = ' autocomplete="name"';
}
$extra = 'number' === $f['type'] ? ' inputmode="decimal" step="any"' : '';
echo ''; // phpcs:ignore WordPress.Security.EscapeOutput
echo ''; // phpcs:ignore WordPress.Security.EscapeOutput
}
if ( $help ) {
echo '' . esc_html( $help ) . '
';
}
echo '' . esc_html( $error ) . '
';
echo ' ';
}
/**
* State after a redirect (submission without JavaScript).
*
* @param string $form_id Form ID.
* @return array
*/
private static function no_js_state( $form_id ) {
$state = array(
'status' => '',
'errors' => array(),
'field_errors' => array(),
'values' => array(),
);
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- display only, no action.
if ( empty( $_GET['alpeg_form'] ) || sanitize_key( wp_unslash( $_GET['alpeg_form'] ) ) !== $form_id ) {
return $state;
}
if ( isset( $_GET['alpeg_form_status'] ) && 'ok' === $_GET['alpeg_form_status'] ) {
$state['status'] = 'success';
return $state;
}
if ( ! empty( $_GET['alpeg_form_key'] ) ) {
$key = 'alpeg_forms_state_' . sanitize_key( wp_unslash( $_GET['alpeg_form_key'] ) );
$data = get_transient( $key );
if ( is_array( $data ) ) {
delete_transient( $key );
$state = array_merge( $state, $data );
}
}
// phpcs:enable
return $state;
}
/**
* Read an attribute with fallback.
*
* @param array $a Attributes.
* @param string $key Key.
* @param string $default Fallback when empty.
* @return string
*/
private static function attr( $a, $key, $default ) {
return isset( $a[ $key ] ) && '' !== trim( (string) $a[ $key ] ) ? (string) $a[ $key ] : $default;
}
}