PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 5.5.6
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v5.5.6
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / models / FrmEntryValidate.php

FrmEntryValidate.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 5.5.6, at classes/models/FrmEntryValidate.php

825 lines 24.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 class FrmEntryValidate {
7 public static function validate( $values, $exclude = false ) {
8 FrmEntry::sanitize_entry_post( $values );
9 $errors = array();
10
11 if ( ! isset( $values['form_id'] ) || ! isset( $values['item_meta'] ) ) {
12 $errors['form'] = __( 'There was a problem with your submission. Please try again.', 'formidable' );
13
14 return $errors;
15 }
16
17 if ( FrmAppHelper::is_admin() && is_user_logged_in() && ( ! isset( $values[ 'frm_submit_entry_' . $values['form_id'] ] ) || ! wp_verify_nonce( $values[ 'frm_submit_entry_' . $values['form_id'] ], 'frm_submit_entry_nonce' ) ) ) {
18 $frm_settings = FrmAppHelper::get_settings();
19 $errors['form'] = $frm_settings->admin_permission;
20 }
21
22 self::set_item_key( $values );
23
24 $posted_fields = self::get_fields_to_validate( $values, $exclude );
25
26 // Pass exclude value to validate_field function so it can be used for repeating sections
27 $args = array( 'exclude' => $exclude );
28
29 foreach ( $posted_fields as $posted_field ) {
30 self::validate_field( $posted_field, $errors, $values, $args );
31 unset( $posted_field );
32 }
33
34 if ( empty( $errors ) ) {
35 self::spam_check( $exclude, $values, $errors );
36 }
37
38 /**
39 * Allows modifying the validation errors after validating all fields.
40 *
41 * @since 5.0.04 Added `posted_fields` to the third param.
42 *
43 * @param array $errors Errors data.
44 * @param array $values Value data of the form.
45 * @param array $args Custom arguments. Contains `exclude` and `posted_fields`.
46 */
47 $errors = apply_filters( 'frm_validate_entry', $errors, $values, compact( 'exclude', 'posted_fields' ) );
48
49 return $errors;
50 }
51
52 private static function set_item_key( &$values ) {
53 if ( ! isset( $values['item_key'] ) || $values['item_key'] == '' ) {
54 global $wpdb;
55 $values['item_key'] = FrmAppHelper::get_unique_key( '', $wpdb->prefix . 'frm_items', 'item_key' );
56 $_POST['item_key'] = $values['item_key'];
57 }
58 }
59
60 private static function get_fields_to_validate( $values, $exclude ) {
61 $where = apply_filters( 'frm_posted_field_ids', array( 'fi.form_id' => $values['form_id'] ) );
62
63 // Don't get subfields
64 $where['fr.parent_form_id'] = array( null, 0 );
65
66 // Don't get excluded fields (like file upload fields in the ajax validation)
67 if ( ! empty( $exclude ) ) {
68 $where['fi.type not'] = $exclude;
69 }
70
71 $fields = FrmField::getAll( $where, 'field_order' );
72
73 /**
74 * Allows modifying fields to validate.
75 *
76 * @since 5.0.06
77 *
78 * @param array $fields List of fields.
79 * @param array $args Includes `values`, `exclude`, `where`.
80 */
81 return apply_filters( 'frm_fields_to_validate', $fields, compact( 'values', 'exclude', 'where' ) );
82 }
83
84 public static function validate_field( $posted_field, &$errors, $values, $args = array() ) {
85 $defaults = array(
86 'id' => $posted_field->id,
87 'parent_field_id' => '', // the id of the repeat or embed form
88 'key_pointer' => '', // the pointer in the posted array
89 'exclude' => array(), // exclude these field types from validation
90 );
91 $args = wp_parse_args( $args, $defaults );
92
93 if ( empty( $args['parent_field_id'] ) ) {
94 $value = isset( $values['item_meta'][ $args['id'] ] ) ? $values['item_meta'][ $args['id'] ] : '';
95 } else {
96 // value is from a nested form
97 $value = $values;
98 }
99
100 // Check for values in "Other" fields
101 FrmEntriesHelper::maybe_set_other_validation( $posted_field, $value, $args );
102
103 self::maybe_clear_value_for_default_blank_setting( $posted_field, $value );
104
105 $should_trim = is_array( $value ) && count( $value ) == 1 && isset( $value[0] ) && $posted_field->type !== 'checkbox';
106 if ( $should_trim ) {
107 $value = reset( $value );
108 }
109
110 if ( ! is_array( $value ) ) {
111 $value = trim( $value );
112 }
113
114 if ( $posted_field->required == '1' && FrmAppHelper::is_empty_value( $value ) ) {
115 $errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $posted_field, 'blank' );
116 } elseif ( ! isset( $_POST['item_name'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
117 self::maybe_add_item_name( $value, $posted_field );
118 }
119
120 FrmEntriesHelper::set_posted_value( $posted_field, $value, $args );
121
122 self::validate_field_types( $errors, $posted_field, $value, $args );
123
124 // Field might want to modify value before other parts of the system
125 // e.g. trim off excess values like in the case of fields with limit.
126 $value = apply_filters( 'frm_modify_posted_field_value', $value, $errors, $posted_field, $args );
127
128 if ( $value != '' ) {
129 self::validate_phone_field( $errors, $posted_field, $value, $args );
130 }
131
132 $errors = apply_filters( 'frm_validate_' . $posted_field->type . '_field_entry', $errors, $posted_field, $value, $args );
133 $errors = apply_filters( 'frm_validate_field_entry', $errors, $posted_field, $value, $args );
134 }
135
136 /**
137 * Maybe add item_name to $_POST to save it in items table.
138 *
139 * @since 5.2.02
140 *
141 * @param object $field Field object.
142 */
143 private static function maybe_add_item_name( $value, $field ) {
144 $item_name = false;
145 if ( 'name' === $field->type ) {
146 $field_obj = FrmFieldFactory::get_field_object( $field );
147 $item_name = $field_obj->get_display_value( $value );
148 } elseif ( 'text' === $field->type ) {
149 $item_name = $value;
150 }
151
152 if ( false !== $item_name ) {
153 // Item name has a max length of 255 characters so truncate it so it doesn't fail to save in the database.
154 $_POST['item_name'] = substr( $item_name, 0, 255 );
155 }
156 }
157
158 /**
159 * Set $value to an empty string if it matches its label
160 *
161 * @param object $field
162 * @param string $value
163 */
164 private static function maybe_clear_value_for_default_blank_setting( $field, &$value ) {
165 $position = FrmField::get_option( $field, 'label' );
166 if ( ! $position ) {
167 $position = FrmStylesController::get_style_val( 'position', $field->form_id );
168 }
169
170 if ( $position === 'inside' && FrmFieldsHelper::is_placeholder_field_type( $field->type ) && $value === $field->name ) {
171 $value = '';
172 }
173 }
174
175 public static function validate_field_types( &$errors, $posted_field, $value, $args ) {
176 $field_obj = FrmFieldFactory::get_field_object( $posted_field );
177 $args['value'] = $value;
178 $args['errors'] = $errors;
179
180 $new_errors = $field_obj->validate( $args );
181 if ( ! empty( $new_errors ) ) {
182 $errors = array_merge( $errors, $new_errors );
183 }
184 }
185
186 public static function validate_phone_field( &$errors, $field, $value, $args ) {
187 if ( $field->type == 'phone' || ( $field->type == 'text' && FrmField::is_option_true_in_object( $field, 'format' ) ) ) {
188
189 $pattern = self::phone_format( $field );
190
191 if ( ! preg_match( $pattern, $value ) ) {
192 $errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
193 }
194 }
195 }
196
197 public static function phone_format( $field ) {
198 if ( FrmField::is_option_empty( $field, 'format' ) ) {
199 $pattern = self::default_phone_format();
200 } else {
201 $pattern = FrmField::get_option( $field, 'format' );
202 }
203
204 $pattern = apply_filters( 'frm_phone_pattern', $pattern, $field );
205
206 // Create a regexp if format is not already a regexp
207 if ( strpos( $pattern, '^' ) !== 0 ) {
208 $pattern = self::create_regular_expression_from_format( $pattern );
209 }
210
211 $pattern = '/' . $pattern . '/';
212
213 return $pattern;
214 }
215
216 /**
217 * @since 3.01
218 */
219 private static function default_phone_format() {
220 return '^((\+\d{1,3}(-|.| )?\(?\d\)?(-| |.)?\d{1,5})|(\(?\d{2,6}\)?))(-|.| )?(\d{3,4})(-|.| )?(\d{4})(( x| ext)\d{1,5}){0,1}$';
221 }
222
223 /**
224 * Create a regular expression from a phone number format
225 *
226 * @since 2.02.02
227 *
228 * @param string $pattern
229 *
230 * @return string
231 */
232 private static function create_regular_expression_from_format( $pattern ) {
233 $pattern = preg_quote( $pattern );
234
235 // Firefox doesn't like escaped dashes or colons
236 $pattern = str_replace( array( '\-', '\:' ), array( '-', ':' ), $pattern );
237
238 // Switch generic values out for their regular expression
239 $pattern = preg_replace( '/\d/', '\d', $pattern );
240 $pattern = str_replace( 'A', '[A-Z]', $pattern );
241 $pattern = str_replace( 'a', '[a-zA-Z]', $pattern );
242 $pattern = str_replace( '*', 'w', $pattern );
243 $pattern = str_replace( '/', '\/', $pattern );
244
245 if ( strpos( $pattern, '\?' ) !== false ) {
246 $parts = explode( '\?', $pattern );
247 $pattern = '';
248 foreach ( $parts as $part ) {
249 if ( empty( $pattern ) ) {
250 $pattern .= $part;
251 } else {
252 $pattern .= '(' . $part . ')?';
253 }
254 }
255 }
256 $pattern = '^' . $pattern . '$';
257
258 return $pattern;
259 }
260
261 /**
262 * Check for spam
263 *
264 * @param boolean $exclude
265 * @param array $values
266 * @param array $errors by reference
267 */
268 public static function spam_check( $exclude, $values, &$errors ) {
269 if ( ! empty( $exclude ) || ! isset( $values['item_meta'] ) || empty( $values['item_meta'] ) || ! empty( $errors ) ) {
270 // only check spam if there are no other errors
271 return;
272 }
273
274 $antispam_check = self::is_antispam_check( $values['form_id'] );
275 if ( is_string( $antispam_check ) ) {
276 $errors['spam'] = $antispam_check;
277 } elseif ( self::is_honeypot_spam( $values ) || self::is_spam_bot() ) {
278 $errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
279 } elseif ( self::blacklist_check( $values ) ) {
280 $errors['spam'] = __( 'Your entry appears to be blocked spam!', 'formidable' );
281 }
282
283 if ( isset( $errors['spam'] ) || self::form_is_in_progress( $values ) ) {
284 return;
285 }
286
287 if ( self::is_akismet_enabled_for_user( $values['form_id'] ) && self::is_akismet_spam( $values ) ) {
288 $errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
289 }
290 }
291
292 /**
293 * Checks if form is in progress.
294 *
295 * @since 5.0.13
296 *
297 * @param array $values The values.
298 * @return bool
299 */
300 private static function form_is_in_progress( $values ) {
301 return FrmAppHelper::pro_is_installed() &&
302 ( isset( $values[ 'frm_page_order_' . $values['form_id'] ] ) || FrmAppHelper::get_post_param( 'frm_next_page' ) ) &&
303 FrmField::get_all_types_in_form( $values['form_id'], 'break' );
304 }
305
306 /**
307 * @param int $form_id
308 * @return boolean
309 */
310 private static function is_antispam_check( $form_id ) {
311 $aspm = new FrmAntiSpam( $form_id );
312 return $aspm->validate();
313 }
314
315 /**
316 * @param array $values
317 * @return boolean
318 */
319 private static function is_honeypot_spam( $values ) {
320 $honeypot = new FrmHoneypot( $values['form_id'] );
321 return ! $honeypot->validate();
322 }
323
324 /**
325 * @return boolean
326 */
327 private static function is_spam_bot() {
328 $ip = FrmAppHelper::get_ip_address();
329
330 return empty( $ip );
331 }
332
333 /**
334 * @param array $values
335 * @return boolean
336 */
337 private static function is_akismet_spam( $values ) {
338 global $wpcom_api_key;
339
340 return ( is_callable( 'Akismet::http_post' ) && ( get_option( 'wordpress_api_key' ) || $wpcom_api_key ) && self::akismet( $values ) );
341 }
342
343 /**
344 * @param int $form_id
345 * @return bool
346 */
347 private static function is_akismet_enabled_for_user( $form_id ) {
348 $form = FrmForm::getOne( $form_id );
349
350 return ( ! empty( $form->options['akismet'] ) && ( $form->options['akismet'] !== 'logged' || ! is_user_logged_in() ) );
351 }
352
353 public static function blacklist_check( $values ) {
354 if ( ! apply_filters( 'frm_check_blacklist', true, $values ) ) {
355 return false;
356 }
357
358 $mod_keys = trim( self::get_disallowed_words() );
359 if ( empty( $mod_keys ) ) {
360 return false;
361 }
362
363 $content = FrmEntriesHelper::entry_array_to_string( $values );
364
365 self::prepare_values_for_spam_check( $values );
366 $ip = FrmAppHelper::get_ip_address();
367 $user_agent = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
368 $user_info = self::get_spam_check_user_info( $values );
369
370 return self::check_disallowed_words( $user_info['comment_author'], $user_info['comment_author_email'], $user_info['comment_author_url'], $content, $ip, $user_agent );
371 }
372
373 /**
374 * For WP 5.5 compatibility.
375 *
376 * @since 4.06.02
377 */
378 private static function check_disallowed_words( $author, $email, $url, $content, $ip, $user_agent ) {
379 if ( function_exists( 'wp_check_comment_disallowed_list' ) ) {
380 return wp_check_comment_disallowed_list( $author, $email, $url, $content, $ip, $user_agent );
381 } else {
382 return wp_blacklist_check( $author, $email, $url, $content, $ip, $user_agent );
383 }
384 }
385
386 /**
387 * For WP 5.5 compatibility.
388 *
389 * @since 4.06.02
390 */
391 private static function get_disallowed_words() {
392 $keys = get_option( 'disallowed_keys' );
393 if ( false === $keys ) {
394 // Fallback for WP < 5.5.
395 $keys = get_option( 'blacklist_keys' );
396 }
397 return $keys;
398 }
399
400 /**
401 * Check entries for Akismet spam
402 *
403 * @return boolean true if is spam
404 */
405 public static function akismet( $values ) {
406 if ( empty( $values['item_meta'] ) ) {
407 return false;
408 }
409
410 $datas = array(
411 'comment_type' => 'formidable',
412 );
413 self::parse_akismet_array( $datas, $values );
414
415 /**
416 * Allows modifying the values sent to Akismet.
417 *
418 * @since 5.0.07
419 *
420 * @param array $datas The array of values being sent to Akismet.
421 */
422 $datas = apply_filters( 'frm_akismet_values', $datas );
423
424 $query_string = _http_build_query( $datas, '', '&' );
425 $response = Akismet::http_post( $query_string, 'comment-check' );
426
427 return ( is_array( $response ) && $response[1] == 'true' );
428 }
429
430 /**
431 * @since 2.0
432 */
433 private static function parse_akismet_array( &$datas, $values ) {
434 self::add_site_info_to_akismet( $datas );
435 self::add_server_values_to_akismet( $datas );
436
437 self::prepare_values_for_spam_check( $values );
438
439 self::add_user_info_to_akismet( $datas, $values );
440 self::add_comment_content_to_akismet( $datas, $values );
441 }
442
443 private static function add_site_info_to_akismet( &$datas ) {
444 $datas['blog'] = FrmAppHelper::site_url();
445 $datas['user_ip'] = preg_replace( '/[^0-9., ]/', '', FrmAppHelper::get_ip_address() );
446 $datas['user_agent'] = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
447 $datas['referrer'] = isset( $_SERVER['HTTP_REFERER'] ) ? FrmAppHelper::get_server_value( 'HTTP_REFERER' ) : false;
448 $datas['blog_lang'] = get_locale();
449 $datas['blog_charset'] = get_option( 'blog_charset' );
450
451 if ( akismet_test_mode() ) {
452 $datas['is_test'] = 'true';
453 }
454 }
455
456 private static function add_user_info_to_akismet( &$datas, $values ) {
457 $user_info = self::get_spam_check_user_info( $values );
458 $datas = $datas + $user_info;
459
460 if ( isset( $user_info['user_ID'] ) ) {
461 $datas['user_role'] = Akismet::get_user_roles( $user_info['user_ID'] );
462 }
463 }
464
465 /**
466 * Gets user info for Akismet spam check.
467 *
468 * @since 5.0.13 Separate code for guest. Handle value of embedded|repeater.
469 *
470 * @param array $values Entry values after running through {@see FrmEntryValidate::prepare_values_for_spam_check()}.
471 * @return array
472 */
473 private static function get_spam_check_user_info( $values ) {
474 if ( ! is_user_logged_in() ) {
475 return self::get_spam_check_user_info_for_guest( $values );
476 }
477
478 $user = wp_get_current_user();
479
480 return array(
481 'user_ID' => $user->ID,
482 'user_id' => $user->ID,
483 'comment_author' => $user->display_name,
484 'comment_author_email' => $user->user_email,
485 'comment_author_url' => $user->user_url,
486 );
487 }
488
489 /**
490 * Gets user info for Akismet spam check for guest.
491 *
492 * @since 5.0.13
493 *
494 * @param array $values Entry values after flattened.
495 * @return array
496 */
497 private static function get_spam_check_user_info_for_guest( $values ) {
498 $datas = array(
499 'comment_author' => '',
500 'comment_author_email' => '',
501 'comment_author_url' => '',
502 'name_field_ids' => $values['name_field_ids'],
503 'missing_keys' => array( 'comment_author_email', 'comment_author_url', 'comment_author' ),
504 'frm_duplicated' => array(),
505 );
506
507 if ( isset( $values['item_meta'] ) ) {
508 $values = $values['item_meta'];
509 }
510
511 $values = array_filter( $values );
512
513 self::recursive_add_akismet_guest_info( $datas, $values );
514 unset( $datas['name_field_ids'] );
515 unset( $datas['missing_keys'] );
516
517 return $datas;
518 }
519
520 /**
521 * Recursive adds akismet guest info.
522 *
523 * @since 5.0.13
524 *
525 * @param array $datas Guest data.
526 * @param array $values The values.
527 * @param int|null $custom_index Custom index (or field ID).
528 */
529 private static function recursive_add_akismet_guest_info( &$datas, $values, $custom_index = null ) {
530 foreach ( $values as $index => $value ) {
531 if ( ! $datas['missing_keys'] ) {
532 return; // Found all info.
533 }
534
535 if ( is_array( $value ) ) {
536 self::recursive_add_akismet_guest_info( $datas, $value, $index );
537 continue;
538 }
539
540 $field_id = ! is_null( $custom_index ) ? $custom_index : $index;
541 foreach ( $datas['missing_keys'] as $key_index => $key ) {
542 $found = self::is_akismet_guest_info_value( $key, $value, $field_id, $datas['name_field_ids'] );
543 if ( $found ) {
544 $datas[ $key ] = $value;
545 $datas['frm_duplicated'][] = $field_id;
546 unset( $datas['missing_keys'][ $key_index ] );
547 }
548 }
549 }
550 }
551
552 /**
553 * Checks if given value is an akismet guest info.
554 *
555 * @since 5.0.13
556 *
557 * @param string $key Guest info key.
558 * @param string $value Value to check.
559 * @param int $field_id Field ID.
560 * @param array $name_field_ids Name field IDs.
561 * @return bool
562 */
563 private static function is_akismet_guest_info_value( $key, $value, $field_id, $name_field_ids ) {
564 if ( ! $value || is_numeric( $value ) ) {
565 return false;
566 }
567
568 switch ( $key ) {
569 case 'comment_author_email':
570 return strpos( $value, '@' ) && is_email( $value );
571
572 case 'comment_author_url':
573 return 0 === strpos( $value, 'http' );
574
575 case 'comment_author':
576 if ( $name_field_ids ) {
577 // If there is name field in the form, we should always use it as author name.
578 return in_array( $field_id, $name_field_ids, true );
579 }
580 return strlen( $value ) < 200;
581 }
582
583 return false;
584 }
585
586 private static function add_server_values_to_akismet( &$datas ) {
587 foreach ( $_SERVER as $key => $value ) {
588 $include_value = is_string( $value ) && ! preg_match( '/^HTTP_COOKIE/', $key ) && preg_match( '/^(HTTP_|REMOTE_ADDR|REQUEST_URI|DOCUMENT_URI)/', $key );
589
590 // Send any potentially useful $_SERVER vars, but avoid sending junk we don't need.
591 if ( $include_value ) {
592 $datas[ $key ] = $value;
593 }
594 unset( $key, $value );
595 }
596 }
597
598 /**
599 * Adds comment content to Akismet data.
600 *
601 * @since 5.0.09
602 *
603 * @param array $datas The array of values being sent to Akismet.
604 * @param array $values Entry values.
605 */
606 private static function add_comment_content_to_akismet( &$datas, $values ) {
607 if ( isset( $datas['frm_duplicated'] ) ) {
608 foreach ( $datas['frm_duplicated'] as $index ) {
609 if ( isset( $values['item_meta'][ $index ] ) ) {
610 unset( $values['item_meta'][ $index ] );
611 } else {
612 unset( $values[ $index ] );
613 }
614 }
615 unset( $datas['frm_duplicated'] );
616 }
617
618 self::skip_adding_values_to_akismet( $values );
619
620 $datas['comment_content'] = FrmEntriesHelper::entry_array_to_string( $values );
621 }
622
623 /**
624 * Skips adding field values to Akismet.
625 *
626 * @since 5.0.09
627 *
628 * @param array $values Entry values.
629 */
630 private static function skip_adding_values_to_akismet( &$values ) {
631 $skipped_fields = self::get_akismet_skipped_field_ids( $values );
632 foreach ( $skipped_fields as $skipped_field ) {
633 if ( ! isset( $values['item_meta'][ $skipped_field->id ] ) ) {
634 continue;
635 }
636
637 if ( self::should_really_skip_field( $skipped_field, $values ) ) {
638 unset( $values['item_meta'][ $skipped_field->id ] );
639 if ( isset( $values['item_meta']['other'][ $skipped_field->id ] ) ) {
640 unset( $values['item_meta']['other'][ $skipped_field->id ] );
641 }
642 }
643 }
644 }
645
646 /**
647 * Checks if a skip field should be really skipped.
648 *
649 * @since 5.02.04
650 *
651 * @param object $field_data Object contains `id` and `options`.
652 * @param array $values Entry values.
653 * @return bool
654 */
655 private static function should_really_skip_field( $field_data, $values ) {
656 if ( empty( $field_data->options ) ) { // This is skipped field types.
657 return true;
658 }
659
660 FrmAppHelper::unserialize_or_decode( $field_data->options );
661 if ( ! $field_data->options ) { // Check if an error happens when unserializing, or empty options.
662 return true;
663 }
664
665 end( $field_data->options );
666 $last_key = key( $field_data->options );
667
668 // If a choice field has no Other option.
669 if ( is_numeric( $last_key ) || 0 !== strpos( $last_key, 'other_' ) ) {
670 return true;
671 }
672
673 // If a choice field has Other option, but Other is not selected.
674 if ( empty( $values['item_meta']['other'][ $field_data->id ] ) ) {
675 return true;
676 }
677
678 // Check if submitted value is same as one of field option.
679 foreach ( $field_data->options as $option ) {
680 $option_value = ! is_array( $option ) ? $option : ( isset( $option['value'] ) ? $option['value'] : '' );
681 if ( $values['item_meta']['other'][ $field_data->id ] === $option_value ) {
682 return true;
683 }
684 }
685
686 return false;
687 }
688
689 /**
690 * Gets field IDs that are skipped from sending to Akismet spam check.
691 *
692 * @since 5.0.09
693 * @since 5.0.13 Move out get_all_form_ids_and_flatten_meta() call and get `form_ids` from `$values`.
694 * @since 5.2.04 This method returns array of object contains `id` and `options` instead of array of `id` only.
695 *
696 * @param array $values Entry values after running through {@see FrmEntryValidate::prepare_values_for_spam_check()}.
697 * @return array
698 */
699 private static function get_akismet_skipped_field_ids( $values ) {
700 if ( empty( $values['form_ids'] ) ) {
701 return array();
702 }
703
704 $skipped_types = array( 'divider', 'form', 'hidden', 'user_id', 'file', 'date', 'time', 'scale', 'star', 'range', 'toggle', 'data', 'lookup', 'likert', 'nps' );
705 $has_other_types = array( 'radio', 'checkbox', 'select' );
706
707 $where = array(
708 array(
709 'form_id' => $values['form_ids'],
710 'type' => array_merge( $skipped_types, $has_other_types ),
711 ),
712 );
713
714 return FrmDb::get_results( 'frm_fields', $where, 'id,options' );
715 }
716
717 /**
718 * Prepares values array for spam check.
719 *
720 * @since 5.0.13
721 *
722 * @param array $values Entry values.
723 */
724 private static function prepare_values_for_spam_check( &$values ) {
725 $form_ids = self::get_all_form_ids_and_flatten_meta( $values );
726 $values['form_ids'] = $form_ids;
727 }
728
729 /**
730 * Gets all form IDs (include child form IDs) and flatten item_meta array. Used for skipping values sent to Akismet.
731 * This also removes some unused data from the item_meta.
732 *
733 * @since 5.0.09
734 * @since 5.0.13 Convert name field value to string.
735 *
736 * @param array $values Entry values.
737 * @return array Form IDs.
738 */
739 private static function get_all_form_ids_and_flatten_meta( &$values ) {
740 $values['name_field_ids'] = array();
741
742 // Blacklist check for File field in the old version doesn't contain `form_id`.
743 $form_ids = isset( $values['form_id'] ) ? array( absint( $values['form_id'] ) ) : array();
744 foreach ( $values['item_meta'] as $field_id => $value ) {
745 if ( ! is_numeric( $field_id ) ) { // Maybe `other`.
746 continue;
747 }
748
749 // Convert name array to string.
750 if ( isset( $value['first'] ) && isset( $value['last'] ) ) {
751 $values['item_meta'][ $field_id ] = trim( implode( ' ', $value ) );
752 $values['name_field_ids'][] = $field_id;
753 continue;
754 }
755
756 if ( ! is_array( $value ) || empty( $value['form'] ) ) {
757 continue;
758 }
759
760 $form_ids[] = absint( $value['form'] );
761
762 foreach ( $value as $subindex => $subvalue ) {
763 if ( ! is_numeric( $subindex ) || ! is_array( $subvalue ) ) {
764 continue;
765 }
766
767 foreach ( $subvalue as $subsubindex => $subsubvalue ) {
768 if ( ! $subsubvalue ) {
769 continue;
770 }
771
772 if ( ! isset( $values['item_meta'][ $subsubindex ] ) ) {
773 $values['item_meta'][ $subsubindex ] = array();
774 }
775
776 // Convert name array to string.
777 if ( isset( $subsubvalue['first'] ) && isset( $subsubvalue['last'] ) ) {
778 $subsubvalue = trim( implode( ' ', $subsubvalue ) );
779
780 $values['name_field_ids'][] = $subsubindex;
781 }
782
783 $values['item_meta'][ $subsubindex ][] = $subsubvalue;
784 }
785 }
786
787 unset( $values['item_meta'][ $field_id ] );
788 }
789
790 return $form_ids;
791 }
792
793 /**
794 * @deprecated 3.0
795 * @codeCoverageIgnore
796 */
797 public static function validate_url_field( &$errors, $field, $value, $args ) {
798 FrmDeprecated::validate_url_field( $errors, $field, $value, $args );
799 }
800
801 /**
802 * @deprecated 3.0
803 * @codeCoverageIgnore
804 */
805 public static function validate_email_field( &$errors, $field, $value, $args ) {
806 FrmDeprecated::validate_email_field( $errors, $field, $value, $args );
807 }
808
809 /**
810 * @deprecated 3.0
811 * @codeCoverageIgnore
812 */
813 public static function validate_number_field( &$errors, $field, $value, $args ) {
814 FrmDeprecated::validate_number_field( $errors, $field, $value, $args );
815 }
816
817 /**
818 * @deprecated 3.0
819 * @codeCoverageIgnore
820 */
821 public static function validate_recaptcha( &$errors, $field, $args ) {
822 FrmDeprecated::validate_recaptcha( $errors, $field, $args );
823 }
824 }
825