: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
'<input type="email" %s %s>',
wpforms_html_attributes( $primary['id'], $primary['class'], $primary['data'], $primary['attr'] ),
esc_attr( $primary['required'] )
$this->field_display_error( 'primary', $field );
// Confirmation email field configuration.
echo '<div class="wpforms-field-row wpforms-field-' . sanitize_html_class( $field['size'] ) . '">';
echo '<div ' . wpforms_html_attributes( false, $primary['block'] ) . '>';
$this->field_display_sublabel( 'primary', 'before', $field );
'<input type="email" %s %s>',
wpforms_html_attributes( $primary['id'], $primary['class'], $primary['data'], $primary['attr'] ),
$this->field_display_sublabel( 'primary', 'after', $field );
$this->field_display_error( 'primary', $field );
echo '<div ' . wpforms_html_attributes( false, $secondary['block'] ) . '>';
$this->field_display_sublabel( 'secondary', 'before', $field );
'<input type="email" %s %s>',
wpforms_html_attributes( $secondary['id'], $secondary['class'], $secondary['data'], $secondary['attr'] ),
$this->field_display_sublabel( 'secondary', 'after', $field );
$this->field_display_error( 'secondary', $field );
* Format and sanitize field.
* @param int $field_id Field ID.
* @param mixed $field_submit Field value that was submitted.
* @param array $form_data Form data and settings.
public function format( $field_id, $field_submit, $form_data ) {
if ( is_array( $field_submit ) ) {
$value = ! empty( $field_submit['primary'] ) ? $field_submit['primary'] : '';
$value = ! empty( $field_submit ) ? $field_submit : '';
if ( $value && ! wpforms_is_email( $value ) ) {
wpforms()->get( 'process' )->errors[ $form_data['id'] ][ $field_id ] = esc_html__( 'The provided email is not valid.', 'wpforms-lite' );
$name = ! empty( $form_data['fields'][ $field_id ] ['label'] ) ? $form_data['fields'][ $field_id ]['label'] : '';
// Set final field details.
wpforms()->get( 'process' )->fields[ $field_id ] = [
'name' => sanitize_text_field( $name ),
'value' => sanitize_text_field( $this->decode_punycode( $value ) ),
'id' => wpforms_validate_field_id( $field_id ),
* Validate field on form submit.
* @param int $field_id Field ID.
* @param mixed $field_submit Submitted field value (raw data).
* @param array $form_data Form data and settings.
public function validate( $field_id, $field_submit, $form_data ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
$form_id = (int) $form_data['id'];
parent::validate( $field_id, $field_submit, $form_data );
if ( ! is_array( $field_submit ) && ! empty( $field_submit ) ) {
'primary' => $field_submit,
if ( empty( $field_submit['primary'] ) ) {
$process = wpforms()->get( 'process' );
$field_submit['primary'] = $this->email_encode_punycode( $field_submit['primary'] );
if ( ! $field_submit['primary'] ) {
$process->errors[ $form_id ][ $field_id ] = esc_html__( 'The provided email is not valid.', 'wpforms-lite' );
// Validate email field with confirmation.
if ( isset( $form_data['fields'][ $field_id ]['confirmation'] ) && ! empty( $field_submit['secondary'] ) ) {
$field_submit['secondary'] = $this->email_encode_punycode( $field_submit['secondary'] );
if ( ! $field_submit['secondary'] ) {
$process->errors[ $form_id ][ $field_id ] = esc_html__( 'The provided email is not valid.', 'wpforms-lite' );
if ( $field_submit['primary'] !== $field_submit['secondary'] ) {
$process->errors[ $form_id ][ $field_id ] = esc_html__( 'The provided emails do not match.', 'wpforms-lite' );
if ( ! $this->is_restricted_email( $field_submit['primary'], $form_data['fields'][ $field_id ] ) ) {
$process->errors[ $form_id ][ $field_id ] = wpforms_setting( 'validation-email-restricted', esc_html__( 'This email address is not allowed.', 'wpforms-lite' ) );
// Validate regular email field, without confirmation.
if ( ! isset( $form_data['fields'][ $field_id ]['confirmation'] ) && ! $this->is_restricted_email( $field_submit['primary'], $form_data['fields'][ $field_id ] ) ) {
$process->errors[ $form_id ][ $field_id ] = wpforms_setting( 'validation-email-restricted', esc_html__( 'This email address is not allowed.', 'wpforms-lite' ) );
* Ajax handler to detect restricted email.
public function ajax_check_restricted_email() {
$form_id = filter_input( INPUT_POST, 'form_id', FILTER_SANITIZE_NUMBER_INT );
$field_id = filter_input( INPUT_POST, 'field_id', FILTER_SANITIZE_NUMBER_INT );
$email = filter_input( INPUT_POST, 'email', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES );
// The valid email can contain such characters: !#$%&'*+/=?^_`{|}~-.
// After filtering the email, we need to decode the `&`, otherwise the email with `&` couldn't be properly recognized.
$email = str_replace( '&', '&', $email );
if ( ! $form_id || ! $field_id || ! $email ) {
$form_data = wpforms()->get( 'form' )->get(
[ 'content_only' => true ]
if ( empty( $form_data['fields'][ $field_id ] ) ) {
$this->is_restricted_email( $email, $form_data['fields'][ $field_id ] )
* Sanitize restricted rules.
public function ajax_sanitize_restricted_rules() {
$this->ajax_sanitize( self::RULES );
* Sanitize default email.
public function ajax_sanitize_default_email() {
$this->ajax_sanitize( self::EMAIL );
* Sanitize email options input.
* @param string $type Type of sanitization.
private function ajax_sanitize( $type ) {
check_ajax_referer( 'wpforms-builder', 'nonce' );
$content = filter_input( INPUT_GET, 'content', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$content = wpforms_json_decode( $content, true );
$current = $content['current'];
$other = $current === 'allow' ? 'deny' : 'allow';
$current_rules = $this->sanitize_restricted_rules( $content[ $current ] );
$other_rules = $this->sanitize_restricted_rules( $content[ $other ] );
$intersect_rules = array_intersect( $current_rules, $other_rules );
$current_rules = array_diff( $current_rules, $intersect_rules );
'currentField' => $this->decode_email_patterns_rules_array( $current_rules ),
'intersect' => str_replace(
$this->decode_email_patterns_rules_array( $intersect_rules )
list( $local, $domain ) = $this->parse_email_pattern( $content );
$local = $this->sanitize_local_pattern( $local );
$domain = $this->sanitize_domain_pattern( $domain );
$content = (string) wpforms_is_email( $this->get_pattern( $local, $domain ) );
wp_send_json_success( $content );
* Verify that an email pattern is valid.
* @param string $pattern Email pattern.
private function is_email_pattern( $pattern ) {
// Empty pattern is not valid.
list( $local, $domain ) = $this->parse_email_pattern( $pattern );
$local = $this->sanitize_local_pattern( $local );
$domain = $this->sanitize_domain_pattern( $domain );
if ( mb_strpos( $pattern, '@' ) === false ) {
return $this->is_email_pattern_without_at( $local );
$domain_check = str_replace( '*', '', $domain );
$domain_check = $this->maybe_adjust_domain( $domain_check );
$pattern_check = $this->get_pattern( $local, $domain_check );
if ( wpforms_is_email( $pattern_check ) ) {
return $this->get_pattern( $local, $domain );
* Sanitize the local or domain part of the email pattern.
* @param string $part Local or domain part of the email pattern.
* @param string $pattern Sanitization pattern.
private function sanitize_part_pattern( $part, $pattern ) {
* Smart tag placeholder. Should contain allowed chars only.
* See patterns in sanitize_local_pattern(), sanitize_domain_pattern().
$smart_tag_placeholder = '-wpforms-smart-tag-';
$smart_tag_pattern = '/{.+?}/';
if ( preg_match_all( $smart_tag_pattern, $part, $m ) ) {
foreach ( $smart_tags as $smart_tag ) {
'/' . preg_quote( $smart_tag, '/' ) . '/',
// Sanitize part by pattern.
$part = preg_replace( $pattern, '', $part );
foreach ( $smart_tags as $smart_tag ) {
'/' . preg_quote( $smart_tag_placeholder, '/' ) . '/',
* Sanitize the local part of the email pattern.
* @param string $local Local part of the email pattern.
private function sanitize_local_pattern( $local ) {
* This regexp is from is_email() WP core function
* with added international characters and
* asterisk [*] for patterns.
return $this->sanitize_part_pattern( $local, '/[^a-zA-Z0-9\x{0080}-\x{0FFF}!#$%&\'*+\/=?^_`{|}~.-]/u' );
* Sanitize the domain part of the email pattern.
* @param string $domain Domain part of the email pattern.
private function sanitize_domain_pattern( $domain ) {
* This regexp is from is_email() WP core function
* with added international characters,
* dot [.] for the whole domain part and
* asterisk [*] for patterns.
return $this->sanitize_part_pattern( $domain, '/[^a-z0-9\x{0080}-\x{FFFF}-.*]/u' );
* Maybe replace empty subdomains with templates.
* @param string $domain Email domain.
private function maybe_adjust_domain( $domain ) {
$domain_subs = array_pad( explode( '.', $domain ), 2, '' );
$domain_template_subs = [ 'a', 'me' ];
foreach ( $domain_template_subs as $index => $domain_template_sub ) {
$domain_subs[ $index ] = trim( $domain_subs[ $index ] );
if ( ! $domain_subs[ $index ] ) {
$domain_subs[ $index ] = $domain_template_sub;
return implode( '.', $domain_subs );
* Get pattern from local and domain parts.
* @param string $local Local part.
* @param string $domain Domain part.
private function get_pattern( $local, $domain = '' ) {
return implode( '@', array_filter( [ $local, $domain ] ) );
* Sanitize restricted rules.
* @param string $content Content.
private function sanitize_restricted_rules( $content ) {
$patterns = array_filter( preg_split( '/\r\n|\r|\n|,/', $content ) );
foreach ( $patterns as $key => $pattern ) {
$pattern = mb_strtolower( trim( $pattern ) );
$email_pattern = $this->is_email_pattern( $pattern );
if ( ! $email_pattern ) {
unset( $patterns[ $key ] );
$patterns[ $key ] = $this->encode_punycode( $email_pattern );
return array_unique( $patterns );
* The check is a restricted email.
* @param string $email Email string.
* @param array $field Field data.
private function is_restricted_email( $email, $field ) {
if ( empty( $field['filter_type'] ) || empty( $field[ $field['filter_type'] ] ) ) {
$email = mb_strtolower( trim( $email ) );
if ( ! wpforms_is_email( $email ) ) {
// Chrome and Edge encode <input type="email"> to punycode, but domain part only.
// Firefox sends intl email as is.
if ( $this->is_encoded_punycode( $email ) ) {
$email = $this->decode_punycode( $email );
$patterns = $this->sanitize_restricted_rules( $field[ $field['filter_type'] ] );
$patterns = array_map( [ $this, 'decode_punycode' ], $patterns );
$patterns = array_map( [ $this, 'sanitize_email_pattern' ], $patterns );
$check = $field['filter_type'] === 'allowlist';
foreach ( $patterns as $pattern ) {
if ( preg_match( '/' . $pattern . '/', $email ) ) {
* Sanitize from email patter a REGEX pattern.
* @param string $pattern Pattern line.