PluginProbe ʕ •ᴥ•ʔ
WPForms – Easy Form Builder for WordPress – Contact Forms, Payment Forms, Surveys, & More / 1.9.9.4
WPForms – Easy Form Builder for WordPress – Contact Forms, Payment Forms, Surveys, & More v1.9.9.4
1.10.1.1 1.10.1 1.10.0.5 trunk 1.1.4 1.1.4.2 1.1.5 1.1.5.1 1.1.6 1.1.6.1 1.1.7 1.1.7.1 1.1.7.2 1.1.8 1.1.8.1 1.1.8.2 1.1.8.3 1.1.8.4 1.10.0.1 1.10.0.2 1.10.0.3 1.10.0.4 1.2.0 1.2.0.1 1.2.1 1.2.2 1.2.2.1 1.2.2.2 1.2.3 1.2.3.1 1.2.3.2 1.2.4 1.2.4.1 1.2.5 1.2.5.1 1.2.6 1.2.7 1.2.8 1.2.8.1 1.2.9 1.3.0 1.3.1 1.3.1.1 1.3.1.2 1.3.2 1.3.3 1.3.5 1.3.6 1.3.6.1 1.3.6.2 1.3.7.2 1.3.7.3 1.3.7.4 1.3.8 1.3.9.1 1.4.0.1 1.4.1.1 1.4.2 1.4.2.1 1.4.2.2 1.4.3 1.4.4 1.4.4.1 1.4.5 1.4.5.1 1.4.5.2 1.4.5.3 1.4.6 1.4.7.1 1.4.7.2 1.4.8.1 1.4.9 1.5.0.1 1.5.0.3 1.5.0.4 1.5.1 1.5.1.1 1.5.1.3 1.5.2.1 1.5.2.2 1.5.2.3 1.5.3 1.5.3.1 1.5.4.1 1.5.4.2 1.5.5 1.5.5.1 1.5.6 1.5.6.2 1.5.7 1.5.8.2 1.5.9.1 1.5.9.4 1.5.9.5 1.6.0.1 1.6.0.2 1.6.1 1.6.2.2 1.6.2.3 1.6.3.1 1.6.4 1.6.4.1 1.6.5 1.6.6 1.6.7 1.6.7.1 1.6.7.2 1.6.7.3 1.6.8 1.6.8.1 1.6.9 1.7.0 1.7.1.1 1.7.1.2 1.7.2 1.7.2.1 1.7.3 1.7.4 1.7.4.1 1.7.4.2 1.7.5.1 1.7.5.2 1.7.5.3 1.7.5.5 1.7.6 1.7.7 1.7.7.1 1.7.7.2 1.7.8 1.7.9 1.7.9.1 1.8.0.1 1.8.0.2 1.8.1.1 1.8.1.2 1.8.1.3 1.8.2.1 1.8.2.2 1.8.2.3 1.8.3 1.8.3.1 1.8.4 1.8.4.1 1.8.5.2 1.8.5.3 1.8.5.4 1.8.6.2 1.8.6.3 1.8.6.4 1.8.7.2 1.8.8.2 1.8.8.3 1.8.9.1 1.8.9.2 1.8.9.4 1.8.9.5 1.8.9.6 1.9.0.1 1.9.0.2 1.9.0.3 1.9.0.4 1.9.1.1 1.9.1.2 1.9.1.3 1.9.1.4 1.9.1.5 1.9.1.6 1.9.2.1 1.9.2.2 1.9.2.3 1.9.3.1 1.9.3.2 1.9.4.1 1.9.4.2 1.9.5 1.9.5.1 1.9.5.2 1.9.6 1.9.6.1 1.9.6.2 1.9.7.1 1.9.7.2 1.9.7.3 1.9.8.1 1.9.8.2 1.9.8.4 1.9.8.7 1.9.9.2 1.9.9.3 1.9.9.4
wpforms-lite / includes / fields / class-email.php
wpforms-lite / includes / fields Last commit date
class-base.php 4 months ago class-checkbox.php 6 months ago class-email.php 8 months ago class-gdpr-checkbox.php 8 months ago class-internal-information.php 10 months ago class-name.php 10 months ago class-number-slider.php 1 year ago class-number.php 1 year ago class-radio.php 6 months ago class-select.php 5 months ago class-text.php 1 year ago class-textarea.php 1 year ago
class-email.php
1398 lines
1 <?php
2
3 use WPForms\Vendor\TrueBV\Punycode;
4
5 /**
6 * Email text field.
7 *
8 * @since 1.0.0
9 */
10 class WPForms_Field_Email extends WPForms_Field {
11
12 /**
13 * Encoding.
14 *
15 * @since 1.6.9
16 */
17 const ENCODING = 'UTF-8';
18
19 /**
20 * Email type of sanitization.
21 *
22 * @since 1.7.5
23 */
24 const EMAIL = 'email';
25
26 /**
27 * Rules type of sanitization.
28 *
29 * @since 1.7.5
30 */
31 const RULES = 'rules';
32
33 /**
34 * Restricted rules.
35 *
36 * @since 1.8.9
37 *
38 * @var array
39 */
40 private $restricted_rules = [];
41
42 /**
43 * Primary class constructor.
44 *
45 * @since 1.0.0
46 */
47 public function init() {
48
49 // Define field type information.
50 $this->name = esc_html__( 'Email', 'wpforms-lite' );
51 $this->keywords = esc_html__( 'user', 'wpforms-lite' );
52 $this->type = 'email';
53 $this->icon = 'fa-envelope-o';
54 $this->order = 170;
55
56 $this->hooks();
57 }
58
59 /**
60 * Hooks.
61 *
62 * @since 1.8.1
63 */
64 private function hooks() {
65
66 // Define additional field properties.
67 add_filter( 'wpforms_field_properties_email', [ $this, 'field_properties' ], 5, 3 );
68
69 // Set field to default to required.
70 add_filter( 'wpforms_field_new_required', [ $this, 'default_required' ], 10, 2 );
71
72 // Set confirmation status to option wrapper class.
73 add_filter( 'wpforms_builder_field_option_class', [ $this, 'field_option_class' ], 10, 2 );
74
75 add_action( 'wp_ajax_wpforms_restricted_email', [ $this, 'ajax_check_restricted_email' ] );
76 add_action( 'wp_ajax_nopriv_wpforms_restricted_email', [ $this, 'ajax_check_restricted_email' ] );
77
78 add_action( 'wp_ajax_wpforms_sanitize_restricted_rules', [ $this, 'ajax_sanitize_restricted_rules' ] );
79 add_action( 'wp_ajax_wpforms_sanitize_default_email', [ $this, 'ajax_sanitize_default_email' ] );
80
81 add_filter( 'wpforms_save_form_args', [ $this, 'save_form_args' ], 11, 3 );
82
83 add_filter( 'wpforms_builder_strings', [ $this, 'add_builder_strings' ], 10, 2 );
84
85 // This field requires fieldset+legend instead of the field label.
86 add_filter( "wpforms_frontend_modern_is_field_requires_fieldset_{$this->type}", [ $this, 'is_field_requires_fieldset' ], PHP_INT_MAX, 2 );
87 }
88
89 /**
90 * Define additional field properties.
91 *
92 * @since 1.3.7
93 *
94 * @param array $properties List field properties.
95 * @param array $field Field data and settings.
96 * @param array $form_data Form data and settings.
97 *
98 * @return array
99 */
100 public function field_properties( $properties, $field, $form_data ) {
101
102 // Prevent "spell-jacking" of email addresses.
103 $properties['inputs']['primary']['attr']['spellcheck'] = 'false';
104
105 if ( ! empty( $field['confirmation'] ) ) {
106 $properties = $this->confirmation_field_properties( $properties, $field, $form_data );
107 }
108 if ( ! empty( $field['filter_type'] ) ) {
109 $properties = $this->filter_type_field_properties( $properties, $field, $form_data );
110 }
111
112 return $properties;
113 }
114
115 /**
116 * Define the confirmation field properties.
117 *
118 * @since 1.6.3
119 *
120 * @param array $properties List field properties.
121 * @param array $field Field data and settings.
122 * @param array $form_data Form data and settings.
123 *
124 * @return array
125 */
126 public function confirmation_field_properties( $properties, $field, $form_data ) {
127 $form_id = absint( $form_data['id'] );
128 $field_id = wpforms_validate_field_id( $field['id'] );
129
130 // Email confirmation setting enabled.
131 $props = [
132 'inputs' => [
133 'primary' => [
134 'block' => [
135 'wpforms-field-row-block',
136 'wpforms-one-half',
137 'wpforms-first',
138 ],
139 'class' => [
140 'wpforms-field-email-primary',
141 ],
142 'sublabel' => [
143 'hidden' => ! empty( $field['sublabel_hide'] ),
144 'value' => esc_html__( 'Email', 'wpforms-lite' ),
145 ],
146 ],
147 'secondary' => [
148 'attr' => [
149 'name' => "wpforms[fields][{$field_id}][secondary]",
150 'value' => '',
151 'placeholder' => ! empty( $field['confirmation_placeholder'] ) ? $field['confirmation_placeholder'] : '',
152 'spellcheck' => 'false',
153 ],
154 'block' => [
155 'wpforms-field-row-block',
156 'wpforms-one-half',
157 ],
158 'class' => [
159 'wpforms-field-email-secondary',
160 ],
161 'data' => [
162 'rule-confirm' => '#' . $properties['inputs']['primary']['id'],
163 ],
164 'id' => "wpforms-{$form_id}-field_{$field_id}-secondary",
165 'required' => ! empty( $field['required'] ) ? 'required' : '',
166 'sublabel' => [
167 'hidden' => ! empty( $field['sublabel_hide'] ),
168 'value' => esc_html__( 'Confirm Email', 'wpforms-lite' ),
169 ],
170 'value' => '',
171 ],
172 ],
173 ];
174
175 $properties = array_merge_recursive( $properties, $props );
176
177 // Input Primary: adjust name.
178 $properties['inputs']['primary']['attr']['name'] = "wpforms[fields][{$field_id}][primary]";
179
180 // Input Primary: remove size and error classes.
181 $properties['inputs']['primary']['class'] = array_diff(
182 $properties['inputs']['primary']['class'],
183 [
184 'wpforms-field-' . sanitize_html_class( $field['size'] ),
185 'wpforms-error',
186 ]
187 );
188
189 // Input Primary: add error class if needed.
190 if ( ! empty( $properties['error']['value']['primary'] ) ) {
191 $properties['inputs']['primary']['class'][] = 'wpforms-error';
192 }
193
194 // Input Secondary: add error class if needed.
195 if ( ! empty( $properties['error']['value']['secondary'] ) ) {
196 $properties['inputs']['secondary']['class'][] = 'wpforms-error';
197 }
198
199 // Input Secondary: add required class if needed.
200 if ( ! empty( $field['required'] ) ) {
201 $properties['inputs']['secondary']['class'][] = 'wpforms-field-required';
202 }
203
204 // Remove reference to an input element to prevent duplication.
205 if ( empty( $field['sublabel_hide'] ) ) {
206 unset( $properties['label']['attr']['for'] );
207 }
208
209 return $properties;
210 }
211
212 /**
213 * Define the filter field properties.
214 *
215 * @since 1.6.3
216 *
217 * @param array $properties List field properties.
218 * @param array $field Field data and settings.
219 * @param array $form_data Form data and settings.
220 *
221 * @return array
222 */
223 public function filter_type_field_properties( $properties, $field, $form_data ) {
224
225 if ( ! empty( $field['filter_type'] ) && ! empty( $field[ $field['filter_type'] ] ) ) {
226 $properties['inputs']['primary']['data']['rule-restricted-email'] = true;
227 }
228
229 return $properties;
230 }
231
232 /**
233 * Field should default to being required.
234 *
235 * @since 1.0.9
236 * @param bool $required
237 * @param array $field
238 * @return bool
239 */
240 public function default_required( $required, $field ) {
241
242 if ( 'email' === $field['type'] ) {
243 return true;
244 }
245 return $required;
246 }
247
248 /**
249 * Add class to field options wrapper to indicate if field confirmation is
250 * enabled.
251 *
252 * @since 1.3.0
253 *
254 * @param string $class Class strings.
255 * @param array $field Current field.
256 *
257 * @return string
258 */
259 public function field_option_class( $class, $field ) {
260
261 if ( 'email' !== $field['type'] ) {
262 return $class;
263 }
264
265 $class .= isset( $field['confirmation'] ) ? ' wpforms-confirm-enabled' : ' wpforms-confirm-disabled';
266 if ( ! empty( $field['filter_type'] ) ) {
267 $class .= ' wpforms-filter-' . $field['filter_type'];
268 }
269
270 return $class;
271 }
272
273 /**
274 * Field options panel inside the builder.
275 *
276 * @since 1.0.0
277 *
278 * @param array $field
279 */
280 public function field_options( $field ) {
281 /*
282 * Basic field options.
283 */
284
285 // Options open markup.
286 $args = [
287 'markup' => 'open',
288 ];
289
290 $this->field_option( 'basic-options', $field, $args );
291
292 // Label.
293 $this->field_option( 'label', $field );
294
295 // Description.
296 $this->field_option( 'description', $field );
297
298 // Required toggle.
299 $this->field_option( 'required', $field );
300
301 // Confirmation toggle.
302 $fld = $this->field_element(
303 'toggle',
304 $field,
305 [
306 'slug' => 'confirmation',
307 'value' => isset( $field['confirmation'] ) ? '1' : '0',
308 'desc' => esc_html__( 'Enable Email Confirmation', 'wpforms-lite' ),
309 'tooltip' => esc_html__( 'Check this option to ask users to provide an email address twice.', 'wpforms-lite' ),
310 ],
311 false
312 );
313
314 $args = [
315 'slug' => 'confirmation',
316 'content' => $fld,
317 ];
318
319 $this->field_element( 'row', $field, $args );
320
321 // Options close markup.
322 $args = [
323 'markup' => 'close',
324 ];
325
326 $this->field_option( 'basic-options', $field, $args );
327
328 /*
329 * Advanced field options.
330 */
331
332 // Options open markup.
333 $args = [
334 'markup' => 'open',
335 ];
336
337 $this->field_option( 'advanced-options', $field, $args );
338
339 // Size.
340 $this->field_option( 'size', $field );
341
342 // Placeholder.
343 $this->field_option( 'placeholder', $field );
344
345 // Confirmation Placeholder.
346 $lbl = $this->field_element(
347 'label',
348 $field,
349 [
350 'slug' => 'confirmation_placeholder',
351 'value' => esc_html__( 'Confirmation Placeholder Text', 'wpforms-lite' ),
352 'tooltip' => esc_html__( 'Enter text for the confirmation field placeholder.', 'wpforms-lite' ),
353 ],
354 false
355 );
356
357 $fld = $this->field_element(
358 'text',
359 $field,
360 [
361 'slug' => 'confirmation_placeholder',
362 'value' => ! empty( $field['confirmation_placeholder'] ) ? esc_attr( $field['confirmation_placeholder'] ) : '',
363 ],
364 false
365 );
366
367 $args = [
368 'slug' => 'confirmation_placeholder',
369 'content' => $lbl . $fld,
370 ];
371
372 $this->field_element( 'row', $field, $args );
373
374 // Default value.
375 $this->field_option( 'default_value', $field );
376
377 $filter_type_label = $this->field_element(
378 'label',
379 $field,
380 [
381 'slug' => 'filter_type',
382 'value' => esc_html__( 'Allowlist / Denylist', 'wpforms-lite' ),
383 'tooltip' => esc_html__( 'Restrict which email addresses are allowed. Be sure to separate each email address with a comma.', 'wpforms-lite' ),
384 ],
385 false
386 );
387
388 $filter_type_field = $this->field_element(
389 'select',
390 $field,
391 [
392 'slug' => 'filter_type',
393 'value' => ! empty( $field['filter_type'] ) ? esc_attr( $field['filter_type'] ) : '',
394 'options' => [
395 '' => esc_html__( 'None', 'wpforms-lite' ),
396 'allowlist' => esc_html__( 'Allowlist', 'wpforms-lite' ),
397 'denylist' => esc_html__( 'Denylist', 'wpforms-lite' ),
398 ],
399 ],
400 false
401 );
402
403 $this->field_element(
404 'row',
405 $field,
406 [
407 'slug' => 'filter_type',
408 'content' => $filter_type_label . $filter_type_field,
409 ]
410 );
411
412 $this->field_element(
413 'row',
414 $field,
415 [
416 'slug' => 'allowlist',
417 'content' => $this->field_element(
418 'textarea',
419 $field,
420 [
421 'slug' => 'allowlist',
422 'value' => ! empty( $field['allowlist'] ) ? esc_attr( $this->decode_email_patterns_rules_list( $field['allowlist'] ) ) : '',
423 ],
424 false
425 ),
426 ]
427 );
428
429 $this->field_element(
430 'row',
431 $field,
432 [
433 'slug' => 'denylist',
434 'content' => $this->field_element(
435 'textarea',
436 $field,
437 [
438 'slug' => 'denylist',
439 'value' => ! empty( $field['denylist'] ) ? esc_attr( $this->decode_email_patterns_rules_list( $field['denylist'] ) ) : '',
440 ],
441 false
442 ),
443 ]
444 );
445
446 // Custom CSS classes.
447 $this->field_option( 'css', $field );
448
449 // Hide Label.
450 $this->field_option( 'label_hide', $field );
451
452 // Hide sublabels.
453 $this->field_option( 'sublabel_hide', $field );
454
455 // Disable email suggestions.
456 $this->field_element(
457 'row',
458 $field,
459 [
460 'slug' => 'disable_suggestions',
461 'content' => $this->field_element(
462 'toggle',
463 $field,
464 [
465 'slug' => 'disable_suggestions',
466 'value' => isset( $field['disable_suggestions'] ) ? '1' : '0',
467 'desc' => esc_html__( 'Disable Suggestions', 'wpforms-lite' ),
468 'tooltip' => esc_html__( 'Prevent email suggestions for common typos. Enable this if you find the suggestions distracting.', 'wpforms-lite' ),
469 ],
470 false
471 ),
472 ]
473 );
474
475 // Options close markup.
476 $args = [
477 'markup' => 'close',
478 ];
479
480 $this->field_option( 'advanced-options', $field, $args );
481 }
482
483 /**
484 * Field preview inside the builder.
485 *
486 * @since 1.0.0
487 * @param array $field
488 */
489 public function field_preview( $field ) {
490
491 // Define data.
492 $placeholder = ! empty( $field['placeholder'] ) ? $field['placeholder'] : '';
493 $confirm_placeholder = ! empty( $field['confirmation_placeholder'] ) ? $field['confirmation_placeholder'] : '';
494 $default_value = ! empty( $field['default_value'] ) ? $field['default_value'] : '';
495 $confirm = ! empty( $field['confirmation'] ) ? 'enabled' : 'disabled';
496
497 // Label.
498 $this->field_preview_option( 'label', $field );
499 ?>
500
501 <div class="wpforms-confirm wpforms-confirm-<?php echo sanitize_html_class( $confirm ); ?>">
502
503 <div class="wpforms-confirm-primary">
504 <input type="email" placeholder="<?php echo esc_attr( $placeholder ); ?>" value="<?php echo esc_attr( $default_value ); ?>" class="primary-input" readonly>
505 <label class="wpforms-sub-label"><?php esc_html_e( 'Email', 'wpforms-lite' ); ?></label>
506 </div>
507
508 <div class="wpforms-confirm-confirmation">
509 <input type="email" placeholder="<?php echo esc_attr( $confirm_placeholder ); ?>" class="secondary-input" readonly>
510 <label class="wpforms-sub-label"><?php esc_html_e( 'Confirm Email', 'wpforms-lite' ); ?></label>
511 </div>
512
513 </div>
514
515 <?php
516 // Description.
517 $this->field_preview_option( 'description', $field );
518 }
519
520 /**
521 * Field display on the form front-end.
522 *
523 * @since 1.0.0
524 *
525 * @param array $field Field data and settings.
526 * @param array $deprecated Deprecated field attributes. Use field properties.
527 * @param array $form_data Form data and settings.
528 */
529 public function field_display( $field, $deprecated, $form_data ) {
530
531 // Define data.
532 $primary = $field['properties']['inputs']['primary'];
533 $secondary = ! empty( $field['properties']['inputs']['secondary'] ) ? $field['properties']['inputs']['secondary'] : [];
534
535 $is_confirmation_enabled = ! empty( $field['confirmation'] );
536 $is_suggestions_disabled = ! empty( $field['disable_suggestions'] );
537
538 // Set data attributes if suggestions are disabled.
539 if ( $is_suggestions_disabled ) {
540 $primary['data']['disable-suggestions'] = true;
541
542 if ( ! empty( $secondary ) ) {
543 $secondary['data']['disable-suggestions'] = true;
544 }
545 }
546
547 // Standard email field.
548 if ( ! $is_confirmation_enabled ) {
549
550 // Primary field.
551 printf(
552 '<input type="email" %s %s>',
553 wpforms_html_attributes( $primary['id'], $primary['class'], $primary['data'], $primary['attr'] ),
554 esc_attr( $primary['required'] )
555 );
556 $this->field_display_error( 'primary', $field );
557
558 // Confirmation email field configuration.
559 } else {
560
561 // Row wrapper.
562 echo '<div class="wpforms-field-row wpforms-field-' . sanitize_html_class( $field['size'] ) . '">';
563
564 // Primary field.
565 echo '<div ' . wpforms_html_attributes( false, $primary['block'] ) . '>';
566 $this->field_display_sublabel( 'primary', 'before', $field );
567 printf(
568 '<input type="email" %s %s>',
569 wpforms_html_attributes( $primary['id'], $primary['class'], $primary['data'], $primary['attr'] ),
570 esc_attr( $primary['required'] )
571 );
572 $this->field_display_sublabel( 'primary', 'after', $field );
573 $this->field_display_error( 'primary', $field );
574 echo '</div>';
575
576 // Secondary field.
577 echo '<div ' . wpforms_html_attributes( false, $secondary['block'] ) . '>';
578 $this->field_display_sublabel( 'secondary', 'before', $field );
579 printf(
580 '<input type="email" %s %s>',
581 wpforms_html_attributes( $secondary['id'], $secondary['class'], $secondary['data'], $secondary['attr'] ),
582 esc_attr( $secondary['required'] )
583 );
584 $this->field_display_sublabel( 'secondary', 'after', $field );
585 $this->field_display_error( 'secondary', $field );
586 echo '</div>';
587
588 echo '</div>';
589
590 }
591 }
592
593 /**
594 * Format and sanitize field.
595 *
596 * @since 1.3.0
597 * @param int $field_id Field ID.
598 * @param mixed $field_submit Field value that was submitted.
599 * @param array $form_data Form data and settings.
600 */
601 public function format( $field_id, $field_submit, $form_data ) {
602
603 // Define data.
604 if ( is_array( $field_submit ) ) {
605 $value = ! empty( $field_submit['primary'] ) ? $field_submit['primary'] : '';
606 } else {
607 $value = ! empty( $field_submit ) ? $field_submit : '';
608 }
609
610 if ( $value && ! wpforms_is_email( $value ) ) {
611 wpforms()->obj( 'process' )->errors[ $form_data['id'] ][ $field_id ] = esc_html__( 'The provided email is not valid.', 'wpforms-lite' );
612
613 return;
614 }
615
616 $name = ! empty( $form_data['fields'][ $field_id ] ['label'] ) ? $form_data['fields'][ $field_id ]['label'] : '';
617
618 // Set final field details.
619 wpforms()->obj( 'process' )->fields[ $field_id ] = [
620 'name' => sanitize_text_field( $name ),
621 'value' => sanitize_text_field( $this->decode_punycode( $value ) ),
622 'id' => wpforms_validate_field_id( $field_id ),
623 'type' => $this->type,
624 ];
625 }
626
627 /**
628 * Validate field on form submit.
629 *
630 * @since 1.0.0
631 *
632 * @param int $field_id Field ID.
633 * @param mixed $field_submit Submitted field value (raw data).
634 * @param array $form_data Form data and settings.
635 */
636 public function validate( $field_id, $field_submit, $form_data ) {
637
638 $form_id = (int) $form_data['id'];
639
640 parent::validate( $field_id, $field_submit, $form_data );
641
642 if ( ! is_array( $field_submit ) && ! empty( $field_submit ) ) {
643 $field_submit = [
644 'primary' => $field_submit,
645 ];
646 }
647
648 if ( empty( $field_submit['primary'] ) ) {
649 return;
650 }
651
652 $process = wpforms()->obj( 'process' );
653
654 if ( ! $process ) {
655 return;
656 }
657
658 $field_submit['primary'] = $this->email_encode_punycode( $field_submit['primary'] );
659
660 if ( ! $field_submit['primary'] ) {
661 $process->errors[ $form_id ][ $field_id ] = esc_html__( 'The provided email is not valid.', 'wpforms-lite' );
662
663 return;
664 }
665
666 // Validate email field with confirmation.
667 if ( isset( $form_data['fields'][ $field_id ]['confirmation'] ) && ! empty( $field_submit['secondary'] ) ) {
668 $field_submit['secondary'] = $this->email_encode_punycode( $field_submit['secondary'] );
669
670 if ( ! $field_submit['secondary'] ) {
671 $process->errors[ $form_id ][ $field_id ] = esc_html__( 'The provided email is not valid.', 'wpforms-lite' );
672
673 return;
674 }
675
676 if ( $field_submit['primary'] !== $field_submit['secondary'] ) {
677 $process->errors[ $form_id ][ $field_id ] = esc_html__( 'The provided emails do not match.', 'wpforms-lite' );
678
679 return;
680 }
681
682 if ( ! $this->is_restricted_email( $field_submit['primary'], $form_data['fields'][ $field_id ] ) ) {
683 $process->errors[ $form_id ][ $field_id ] = wpforms_setting( 'validation-email-restricted', esc_html__( 'This email address is not allowed.', 'wpforms-lite' ) );
684
685 return;
686 }
687 }
688
689 // Validate regular email field, without confirmation.
690 if ( ! isset( $form_data['fields'][ $field_id ]['confirmation'] ) && ! $this->is_restricted_email( $field_submit['primary'], $form_data['fields'][ $field_id ] ) ) {
691 $process->errors[ $form_id ][ $field_id ] = wpforms_setting( 'validation-email-restricted', esc_html__( 'This email address is not allowed.', 'wpforms-lite' ) );
692 }
693 }
694
695 /**
696 * Ajax handler to detect restricted email.
697 *
698 * @since 1.6.3
699 * @since 1.9.1 Added repeater field compatibility.
700 */
701 public function ajax_check_restricted_email() {
702
703 $form_id = filter_input( INPUT_POST, 'form_id', FILTER_SANITIZE_NUMBER_INT );
704 $field_id = absint( filter_input( INPUT_POST, 'field_id', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) );
705 $email = filter_input( INPUT_POST, 'email', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES );
706
707 // The valid email can contain such characters: !#$%&'*+/=?^_`{|}~-.
708 // After filtering the email, we need to decode the `&amp;`, otherwise the email with `&` couldn't be properly recognized.
709 $email = str_replace( '&amp;', '&', $email );
710
711 if ( ! $form_id || ! $field_id || ! $email ) {
712 wp_send_json_error();
713 }
714
715 $form_data = wpforms()->obj( 'form' )->get(
716 $form_id,
717 [ 'content_only' => true ]
718 );
719
720 if ( empty( $form_data['fields'][ $field_id ] ) ) {
721 wp_send_json_error();
722 }
723
724 wp_send_json_success(
725 $this->is_restricted_email( $email, $form_data['fields'][ $field_id ] )
726 );
727 }
728
729 /**
730 * Sanitize restricted rules.
731 *
732 * @since 1.6.3
733 */
734 public function ajax_sanitize_restricted_rules() {
735
736 $this->ajax_sanitize( self::RULES );
737 }
738
739 /**
740 * Sanitize default email.
741 *
742 * @since 1.7.5
743 */
744 public function ajax_sanitize_default_email() {
745
746 $this->ajax_sanitize( self::EMAIL );
747 }
748
749 /**
750 * Sanitize email options input.
751 *
752 * @since 1.7.5
753 *
754 * @param string $type Type of sanitization.
755 *
756 * @return void
757 */
758 private function ajax_sanitize( $type ) {
759
760 // Run a security check.
761 check_ajax_referer( 'wpforms-builder', 'nonce' );
762
763 $content = filter_input( INPUT_GET, 'content', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
764 $content = wpforms_json_decode( $content, true );
765
766 if ( ! $content ) {
767 wp_send_json_error();
768 }
769
770 $this->restricted_rules = [];
771
772 switch ( $type ) {
773 case self::RULES:
774 $current = $content['current'];
775 $other = $current === 'allow' ? 'deny' : 'allow';
776 $current_rules = $this->sanitize_restricted_rules( $content[ $current ] );
777 $other_rules = $this->sanitize_restricted_rules( $content[ $other ] );
778 $intersect_rules = array_intersect( $current_rules, $other_rules );
779 $current_rules = array_diff( $current_rules, $intersect_rules );
780 $content = [
781 'currentField' => $this->decode_email_patterns_rules_array( $current_rules ),
782 'intersect' => str_replace(
783 PHP_EOL,
784 '<br>',
785 $this->decode_email_patterns_rules_array( $intersect_rules )
786 ),
787 ];
788 break;
789
790 case self::EMAIL:
791 list( $local, $domain ) = $this->parse_email_pattern( $content );
792
793 $local = $this->sanitize_local_pattern( $local );
794 $domain = $this->sanitize_domain_pattern( $domain );
795
796 $content = (string) wpforms_is_email( $this->get_pattern( $local, $domain ) );
797 break;
798
799 default:
800 break;
801 }
802
803 if ( ! empty( $this->restricted_rules ) ) {
804 $content['restricted'] = count( $this->restricted_rules );
805 }
806
807 wp_send_json_success( $content );
808 }
809
810 /**
811 * Verify that an email pattern is valid.
812 *
813 * @since 1.7.5
814 *
815 * @param string $pattern Email pattern.
816 *
817 * @return string|false
818 */
819 private function is_email_pattern( $pattern ) {
820
821 if ( ! $pattern ) {
822 // Empty pattern is not valid.
823 return false;
824 }
825
826 list( $local, $domain ) = $this->parse_email_pattern( $pattern );
827
828 $local = $this->sanitize_local_pattern( $local );
829 $domain = $this->sanitize_domain_pattern( $domain );
830
831 if ( mb_strpos( $pattern, '@' ) === false ) {
832 return $this->is_email_pattern_without_at( $local );
833 }
834
835 $domain_check = str_replace( '*', '', $domain );
836 $domain_check = $this->maybe_adjust_domain( $domain_check );
837 $pattern_check = $this->get_pattern( $local, $domain_check );
838
839 if ( wpforms_is_email( $pattern_check ) ) {
840 return $this->get_pattern( $local, $domain );
841 }
842
843 return false;
844 }
845
846 /**
847 * Sanitize the local or domain part of the email pattern.
848 *
849 * @since 1.7.5
850 *
851 * @param string $part Local or domain part of the email pattern.
852 * @param string $pattern Sanitization pattern.
853 *
854 * @return string
855 */
856 private function sanitize_part_pattern( $part, $pattern ) {
857
858 /**
859 * Smart tag placeholder. Should contain allowed chars only.
860 * See patterns in sanitize_local_pattern(), sanitize_domain_pattern().
861 */
862 $smart_tag_placeholder = '-wpforms-smart-tag-';
863
864 $smart_tag_pattern = '/{.+?}/';
865 $smart_tags = [];
866
867 if ( preg_match_all( $smart_tag_pattern, $part, $m ) ) {
868 $smart_tags = $m[0];
869
870 foreach ( $smart_tags as $smart_tag ) {
871 $part = preg_replace(
872 '/' . preg_quote( $smart_tag, '/' ) . '/',
873 $smart_tag_placeholder,
874 $part,
875 1
876 );
877 }
878 }
879
880 // Sanitize part by pattern.
881 $part = preg_replace( $pattern, '', $part );
882
883 foreach ( $smart_tags as $smart_tag ) {
884 $part = preg_replace(
885 '/' . preg_quote( $smart_tag_placeholder, '/' ) . '/',
886 $smart_tag,
887 $part,
888 1
889 );
890 }
891
892 return $part;
893 }
894
895 /**
896 * Sanitize the local part of the email pattern.
897 *
898 * @since 1.7.5
899 *
900 * @param string $local Local part of the email pattern.
901 *
902 * @return string
903 */
904 private function sanitize_local_pattern( $local ) {
905
906 /**
907 * This regexp is from is_email() WP core function
908 * with added international characters and
909 * asterisk [*] for patterns.
910 */
911 return $this->sanitize_part_pattern( $local, '/[^a-zA-Z0-9\x{0080}-\x{0FFF}!#$%&\'*+\/=?^_`{|}~.-]/u' );
912 }
913
914 /**
915 * Sanitize the domain part of the email pattern.
916 *
917 * @since 1.7.5
918 *
919 * @param string $domain Domain part of the email pattern.
920 *
921 * @return string
922 */
923 private function sanitize_domain_pattern( $domain ) {
924
925 /**
926 * This regexp is from is_email() WP core function
927 * with added international characters,
928 * dot [.] for the whole domain part and
929 * asterisk [*] for patterns.
930 */
931 return $this->sanitize_part_pattern( $domain, '/[^a-z0-9\x{0080}-\x{FFFF}-.*]/u' );
932 }
933
934 /**
935 * Maybe replace empty subdomains with templates.
936 *
937 * @since 1.7.5
938 *
939 * @param string $domain Email domain.
940 *
941 * @return string
942 */
943 private function maybe_adjust_domain( $domain ) {
944
945 $domain_subs = array_pad( explode( '.', $domain ), 2, '' );
946 $domain_template_subs = [ 'a', 'me' ];
947
948 foreach ( $domain_template_subs as $index => $domain_template_sub ) {
949 $domain_subs[ $index ] = trim( $domain_subs[ $index ] );
950
951 if ( ! $domain_subs[ $index ] ) {
952 $domain_subs[ $index ] = $domain_template_sub;
953 }
954 }
955
956 return implode( '.', $domain_subs );
957 }
958
959 /**
960 * Get pattern from local and domain parts.
961 *
962 * @since 1.7.5
963 *
964 * @param string $local Local part.
965 * @param string $domain Domain part.
966 *
967 * @return string
968 */
969 private function get_pattern( $local, $domain = '' ) {
970
971 return implode( '@', array_filter( [ $local, $domain ] ) );
972 }
973
974 /**
975 * Sanitize restricted rules.
976 *
977 * @since 1.6.3
978 *
979 * @param string $content Content.
980 *
981 * @return array
982 */
983 private function sanitize_restricted_rules( $content ) {
984
985 $patterns = array_filter( preg_split( '/\r\n|\r|\n|,/', $content ) );
986
987 foreach ( $patterns as $key => $pattern ) {
988 $pattern = mb_strtolower( trim( $pattern ) );
989 $email_pattern = $this->is_email_pattern( $pattern );
990
991 if ( ! $email_pattern ) {
992 unset( $patterns[ $key ] );
993 $this->restricted_rules[] = $pattern;
994
995 continue;
996 }
997
998 $patterns[ $key ] = $this->encode_punycode( $email_pattern );
999 }
1000
1001 return array_unique( $patterns );
1002 }
1003
1004 /**
1005 * The check is a restricted email.
1006 *
1007 * @since 1.6.3
1008 *
1009 * @param string $email Email string.
1010 * @param array $field Field data.
1011 *
1012 * @return bool
1013 */
1014 private function is_restricted_email( $email, $field ) {
1015
1016 if ( empty( $field['filter_type'] ) || empty( $field[ $field['filter_type'] ] ) ) {
1017 return true;
1018 }
1019
1020 $email = mb_strtolower( trim( $email ) );
1021
1022 if ( ! wpforms_is_email( $email ) ) {
1023 return false;
1024 }
1025
1026 // Chrome and Edge encode <input type="email"> to punycode, but domain part only.
1027 // Firefox sends intl email as is.
1028 if ( $this->is_encoded_punycode( $email ) ) {
1029 $email = $this->decode_punycode( $email );
1030 }
1031
1032 $patterns = $this->sanitize_restricted_rules( $field[ $field['filter_type'] ] );
1033 $patterns = array_map( [ $this, 'decode_punycode' ], $patterns );
1034 $patterns = array_map( [ $this, 'sanitize_email_pattern' ], $patterns );
1035
1036 $check = $field['filter_type'] === 'allowlist';
1037
1038 foreach ( $patterns as $pattern ) {
1039 if ( preg_match( '/' . $pattern . '/', $email ) ) {
1040 return $check;
1041 }
1042 }
1043
1044 return ! $check;
1045 }
1046
1047 /**
1048 * Sanitize from email patter a REGEX pattern.
1049 *
1050 * @since 1.6.3
1051 *
1052 * @param string $pattern Pattern line.
1053 *
1054 * @return string
1055 */
1056 private function sanitize_email_pattern( $pattern ) {
1057
1058 $chars = [ '.', '*', '/' ];
1059 $replace = [ '\.', '.*', '\/' ];
1060
1061 // Create regex pattern from a string.
1062 return '^' . str_replace( $chars, $replace, $pattern ) . '$';
1063 }
1064
1065 /**
1066 * Sanitize allow/deny list and default value before saving.
1067 *
1068 * @since 1.6.8
1069 *
1070 * @param array $form Form array which is usable with `wp_update_post()`.
1071 * @param array $data Data retrieved from $_POST and processed.
1072 * @param array $args Empty by default, may contain custom data not intended to be saved, but used for processing.
1073 *
1074 * @return array
1075 */
1076 public function save_form_args( $form, $data, $args ) {
1077
1078 // Get a filtered form content.
1079 $form_data = json_decode( stripslashes( $form['post_content'] ), true );
1080
1081 if ( ! empty( $form_data['fields'] ) ) {
1082 foreach ( (array) $form_data['fields'] as $key => $field ) {
1083 if ( empty( $field['type'] ) || $field['type'] !== 'email' ) {
1084 continue;
1085 }
1086
1087 $form_data['fields'][ $key ]['allowlist'] = ! empty( $field['allowlist'] ) ? implode( PHP_EOL, $this->sanitize_restricted_rules( $field['allowlist'] ) ) : '';
1088 $form_data['fields'][ $key ]['denylist'] = ! empty( $field['denylist'] ) ? implode( PHP_EOL, $this->sanitize_restricted_rules( $field['denylist'] ) ) : '';
1089 $form_data['fields'][ $key ]['default_value'] = isset( $field['default_value'] ) ? wpforms_is_email( $field['default_value'] ) : '';
1090 }
1091 }
1092
1093 $form['post_content'] = wpforms_encode( $form_data );
1094
1095 return $form;
1096 }
1097
1098 /**
1099 * Add a custom JS i18n strings for the builder.
1100 *
1101 * @since 1.7.5
1102 *
1103 * @param array $strings List of strings.
1104 * @param array $form Current form.
1105 *
1106 * @return array
1107 */
1108 public function add_builder_strings( $strings, $form ) {
1109
1110 $email_strings = [
1111 'allow_deny_lists_intersect' => esc_html__(
1112 'We’ve detected the same text in your allowlist and denylist. To prevent a conflict, we’ve removed the following text from the list you’re currently viewing:',
1113 'wpforms-lite'
1114 ),
1115 'restricted_rules' => esc_html__(
1116 'At least one of the emails in your list contained an error and has been removed.',
1117 'wpforms-lite'
1118 ),
1119 'restricted_default_email' => esc_html__(
1120 'The provided email is not valid.',
1121 'wpforms-lite'
1122 ),
1123 ];
1124
1125 return array_merge( $strings, $email_strings );
1126 }
1127
1128 /**
1129 * Get Punycode lib class.
1130 *
1131 * @since 1.6.9
1132 *
1133 * @return WPForms\Vendor\TrueBV\Punycode
1134 */
1135 private function get_punycode() {
1136
1137 static $punycode;
1138
1139 if ( ! $punycode ) {
1140 $punycode = new Punycode();
1141 }
1142
1143 return $punycode;
1144 }
1145
1146 /**
1147 * Get email patterns parts splitted by @ and *.
1148 *
1149 * @since 1.6.9
1150 *
1151 * @param string $email_pattern Email pattern.
1152 *
1153 * @return array
1154 */
1155 private function get_email_pattern_parts( $email_pattern ) {
1156
1157 $parts = preg_split( '/[*@.]/', $email_pattern, - 1, PREG_SPLIT_OFFSET_CAPTURE );
1158
1159 if ( empty( $parts ) ) {
1160 return [];
1161 }
1162
1163 foreach ( $parts as $key => $part ) {
1164
1165 // Replace split symbol position to the split symbol.
1166 $part[1] = $part[1] > 0 ? $email_pattern[ $part[1] - 1 ] : '';
1167
1168 $parts[ $key ] = $part;
1169 }
1170
1171 return $parts;
1172 }
1173
1174 /**
1175 * Glue email patterns parts.
1176 *
1177 * @since 1.6.9
1178 *
1179 * @param array $parts Email pattern parts.
1180 *
1181 * @return string
1182 */
1183 private function glue_email_pattern_parts( $parts ) {
1184
1185 $email_pattern = '';
1186
1187 foreach ( $parts as $part ) {
1188 $email_pattern .= $part[1] . $part[0];
1189 }
1190
1191 return $email_pattern;
1192 }
1193
1194 /**
1195 * Decode email patterns rules array.
1196 *
1197 * @since 1.7.5
1198 *
1199 * @param array $rules_arr Patterns rules array.
1200 *
1201 * @return string
1202 */
1203 private function decode_email_patterns_rules_array( $rules_arr ) {
1204
1205 return implode(
1206 PHP_EOL,
1207 array_filter(
1208 array_map(
1209 function ( $rule ) {
1210 $rule = mb_strtolower( trim( $rule ) );
1211
1212 return $this->is_email_pattern( $rule ) ? $this->decode_punycode( $rule ) : '';
1213 },
1214 $rules_arr
1215 )
1216 )
1217 );
1218 }
1219
1220 /**
1221 * Decode email patterns rules list.
1222 *
1223 * @since 1.6.9
1224 *
1225 * @param string $rules Patterns rules list.
1226 *
1227 * @return string
1228 */
1229 private function decode_email_patterns_rules_list( $rules ) {
1230
1231 return $this->decode_email_patterns_rules_array( preg_split( '/\r\n|\r|\n|,/', $rules ) );
1232 }
1233
1234 /**
1235 * Encode email.
1236 *
1237 * @since 1.7.3
1238 *
1239 * @param string $email Email.
1240 *
1241 * @return string
1242 */
1243 private function email_encode_punycode( $email ) {
1244
1245 if ( ! wpforms_is_email( $email ) ) {
1246 return '';
1247 }
1248
1249 return $this->encode_punycode( $email );
1250 }
1251
1252 /**
1253 * Is email encoded.
1254 *
1255 * @since 1.7.5
1256 *
1257 * @param string $email Email.
1258 *
1259 * @return bool
1260 */
1261 private function is_encoded_punycode( $email ) {
1262
1263 list( $local, $domain ) = $this->parse_email_pattern( $email );
1264
1265 // Check xn-- prefix in the beginning of domain part only.
1266 return strpos( $domain, 'xn--' ) === 0;
1267 }
1268
1269 /**
1270 * Encode email pattern.
1271 *
1272 * @since 1.6.9
1273 *
1274 * @param string $email_pattern Email pattern.
1275 *
1276 * @return string
1277 */
1278 private function encode_punycode( $email_pattern ) {
1279
1280 try {
1281 $encoded = $this->transform_punycode( $email_pattern, [ $this->get_punycode(), 'encode' ] );
1282 } catch ( Exception $e ) {
1283 return '';
1284 }
1285
1286 return $encoded;
1287 }
1288
1289 /**
1290 * Decode email pattern.
1291 *
1292 * @since 1.6.9
1293 *
1294 * @param string $email_pattern Email pattern.
1295 *
1296 * @return string
1297 */
1298 private function decode_punycode( $email_pattern ) {
1299
1300 return $this->transform_punycode( $email_pattern, [ $this->get_punycode(), 'decode' ] );
1301 }
1302
1303 /**
1304 * Transform email pattern.
1305 *
1306 * @since 1.6.9
1307 *
1308 * @param string $email_pattern Email pattern.
1309 * @param callable $callback Punycode callback.
1310 *
1311 * @return string
1312 */
1313 private function transform_punycode( $email_pattern, callable $callback ) {
1314
1315 $parts = $this->get_email_pattern_parts( $email_pattern );
1316
1317 foreach ( $parts as $key => $part ) {
1318 if ( ! $part[0] ) {
1319 continue;
1320 }
1321
1322 $parts[ $key ][0] = call_user_func( $callback, $part[0] );
1323 }
1324
1325 return $this->glue_email_pattern_parts( $parts );
1326 }
1327
1328 /**
1329 * Parse email pattern and return local and domain parts (maybe empty).
1330 *
1331 * @since 1.7.5
1332 *
1333 * @param string $pattern Email pattern.
1334 *
1335 * @return array
1336 */
1337 private function parse_email_pattern( $pattern ) {
1338
1339 return array_pad( explode( '@', $pattern ), 2, '' );
1340 }
1341
1342 /**
1343 * Verify that an email pattern without @ is valid.
1344 *
1345 * @since 1.7.5
1346 *
1347 * @param string $pattern Local part.
1348 *
1349 * @return false|string
1350 */
1351 private function is_email_pattern_without_at( $pattern ) {
1352
1353 if ( mb_strpos( $pattern, '*' ) === false ) {
1354 return false;
1355 }
1356
1357 /**
1358 * If pattern does not have @ separator, we should check the pattern twice, assuming:
1359 * case 1 - pattern is a local pattern,
1360 * case 2 - pattern is a domain pattern.
1361 */
1362
1363 // Check case 1.
1364 $pattern_check = $this->get_pattern( $pattern, 'a.me' );
1365
1366 if ( wpforms_is_email( $pattern_check ) ) {
1367 return $this->get_pattern( $pattern );
1368 }
1369
1370 // Check case 2.
1371 // Asterisk in the email is allowed in local part, but not in the domain part.
1372 $pattern_check = $this->get_pattern( 'a', str_replace( '*', '', $pattern ) );
1373
1374 if ( wpforms_is_email( $pattern_check ) ) {
1375 return $this->get_pattern( $pattern );
1376 }
1377
1378 return false;
1379 }
1380
1381 /**
1382 * Determine if the field requires fieldset instead of the regular field label.
1383 *
1384 * @since 1.8.1
1385 *
1386 * @param bool $requires_fieldset True if requires fieldset.
1387 * @param array $field Field data.
1388 *
1389 * @return bool
1390 *
1391 * @noinspection PhpUnusedParameterInspection
1392 */
1393 public function is_field_requires_fieldset( $requires_fieldset, $field ) {
1394
1395 return ! empty( $field['confirmation'] );
1396 }
1397 }
1398