PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 5.4.4
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v5.4.4
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.4.4, at classes/models/FrmEntryValidate.php

824 lines 24.4 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 $_POST['item_name'] = $item_name;
154 }
155 }
156
157 /**
158 * Set $value to an empty string if it matches its label
159 *
160 * @param object $field
161 * @param string $value
162 */
163 private static function maybe_clear_value_for_default_blank_setting( $field, &$value ) {
164 $position = FrmField::get_option( $field, 'label' );
165 if ( ! $position ) {
166 $position = FrmStylesController::get_style_val( 'position', $field->form_id );
167 }
168
169 if ( $position === 'inside' && FrmFieldsHelper::is_placeholder_field_type( $field->type ) && $value === $field->name ) {
170 $value = '';
171 }
172 }
173
174 public static function validate_field_types( &$errors, $posted_field, $value, $args ) {
175 $field_obj = FrmFieldFactory::get_field_object( $posted_field );
176 $args['value'] = $value;
177 $args['errors'] = $errors;
178
179 $new_errors = $field_obj->validate( $args );
180 if ( ! empty( $new_errors ) ) {
181 $errors = array_merge( $errors, $new_errors );
182 }
183 }
184
185 public static function validate_phone_field( &$errors, $field, $value, $args ) {
186 if ( $field->type == 'phone' || ( $field->type == 'text' && FrmField::is_option_true_in_object( $field, 'format' ) ) ) {
187
188 $pattern = self::phone_format( $field );
189
190 if ( ! preg_match( $pattern, $value ) ) {
191 $errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
192 }
193 }
194 }
195
196 public static function phone_format( $field ) {
197 if ( FrmField::is_option_empty( $field, 'format' ) ) {
198 $pattern = self::default_phone_format();
199 } else {
200 $pattern = FrmField::get_option( $field, 'format' );
201 }
202
203 $pattern = apply_filters( 'frm_phone_pattern', $pattern, $field );
204
205 // Create a regexp if format is not already a regexp
206 if ( strpos( $pattern, '^' ) !== 0 ) {
207 $pattern = self::create_regular_expression_from_format( $pattern );
208 }
209
210 $pattern = '/' . $pattern . '/';
211
212 return $pattern;
213 }
214
215 /**
216 * @since 3.01
217 */
218 private static function default_phone_format() {
219 return '^((\+\d{1,3}(-|.| )?\(?\d\)?(-| |.)?\d{1,5})|(\(?\d{2,6}\)?))(-|.| )?(\d{3,4})(-|.| )?(\d{4})(( x| ext)\d{1,5}){0,1}$';
220 }
221
222 /**
223 * Create a regular expression from a phone number format
224 *
225 * @since 2.02.02
226 *
227 * @param string $pattern
228 *
229 * @return string
230 */
231 private static function create_regular_expression_from_format( $pattern ) {
232 $pattern = preg_quote( $pattern );
233
234 // Firefox doesn't like escaped dashes or colons
235 $pattern = str_replace( array( '\-', '\:' ), array( '-', ':' ), $pattern );
236
237 // Switch generic values out for their regular expression
238 $pattern = preg_replace( '/\d/', '\d', $pattern );
239 $pattern = str_replace( 'A', '[A-Z]', $pattern );
240 $pattern = str_replace( 'a', '[a-zA-Z]', $pattern );
241 $pattern = str_replace( '*', 'w', $pattern );
242 $pattern = str_replace( '/', '\/', $pattern );
243
244 if ( strpos( $pattern, '\?' ) !== false ) {
245 $parts = explode( '\?', $pattern );
246 $pattern = '';
247 foreach ( $parts as $part ) {
248 if ( empty( $pattern ) ) {
249 $pattern .= $part;
250 } else {
251 $pattern .= '(' . $part . ')?';
252 }
253 }
254 }
255 $pattern = '^' . $pattern . '$';
256
257 return $pattern;
258 }
259
260 /**
261 * Check for spam
262 *
263 * @param boolean $exclude
264 * @param array $values
265 * @param array $errors by reference
266 */
267 public static function spam_check( $exclude, $values, &$errors ) {
268 if ( ! empty( $exclude ) || ! isset( $values['item_meta'] ) || empty( $values['item_meta'] ) || ! empty( $errors ) ) {
269 // only check spam if there are no other errors
270 return;
271 }
272
273 $antispam_check = self::is_antispam_check( $values['form_id'] );
274 if ( is_string( $antispam_check ) ) {
275 $errors['spam'] = $antispam_check;
276 } elseif ( self::is_honeypot_spam( $values ) || self::is_spam_bot() ) {
277 $errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
278 } elseif ( self::blacklist_check( $values ) ) {
279 $errors['spam'] = __( 'Your entry appears to be blocked spam!', 'formidable' );
280 }
281
282 if ( isset( $errors['spam'] ) || self::form_is_in_progress( $values ) ) {
283 return;
284 }
285
286 if ( self::is_akismet_enabled_for_user( $values['form_id'] ) && self::is_akismet_spam( $values ) ) {
287 $errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
288 }
289 }
290
291 /**
292 * Checks if form is in progress.
293 *
294 * @since 5.0.13
295 *
296 * @param array $values The values.
297 * @return bool
298 */
299 private static function form_is_in_progress( $values ) {
300 return FrmAppHelper::pro_is_installed() &&
301 ( isset( $values[ 'frm_page_order_' . $values['form_id'] ] ) || FrmAppHelper::get_post_param( 'frm_next_page' ) ) &&
302 FrmField::get_all_types_in_form( $values['form_id'], 'break' );
303 }
304
305 /**
306 * @param int $form_id
307 * @return boolean
308 */
309 private static function is_antispam_check( $form_id ) {
310 $aspm = new FrmAntiSpam( $form_id );
311 return $aspm->validate();
312 }
313
314 /**
315 * @param array $values
316 * @return boolean
317 */
318 private static function is_honeypot_spam( $values ) {
319 $honeypot = new FrmHoneypot( $values['form_id'] );
320 return ! $honeypot->validate();
321 }
322
323 /**
324 * @return boolean
325 */
326 private static function is_spam_bot() {
327 $ip = FrmAppHelper::get_ip_address();
328
329 return empty( $ip );
330 }
331
332 /**
333 * @param array $values
334 * @return boolean
335 */
336 private static function is_akismet_spam( $values ) {
337 global $wpcom_api_key;
338
339 return ( is_callable( 'Akismet::http_post' ) && ( get_option( 'wordpress_api_key' ) || $wpcom_api_key ) && self::akismet( $values ) );
340 }
341
342 /**
343 * @param int $form_id
344 * @return bool
345 */
346 private static function is_akismet_enabled_for_user( $form_id ) {
347 $form = FrmForm::getOne( $form_id );
348
349 return ( ! empty( $form->options['akismet'] ) && ( $form->options['akismet'] !== 'logged' || ! is_user_logged_in() ) );
350 }
351
352 public static function blacklist_check( $values ) {
353 if ( ! apply_filters( 'frm_check_blacklist', true, $values ) ) {
354 return false;
355 }
356
357 $mod_keys = trim( self::get_disallowed_words() );
358 if ( empty( $mod_keys ) ) {
359 return false;
360 }
361
362 $content = FrmEntriesHelper::entry_array_to_string( $values );
363
364 self::prepare_values_for_spam_check( $values );
365 $ip = FrmAppHelper::get_ip_address();
366 $user_agent = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
367 $user_info = self::get_spam_check_user_info( $values );
368
369 return self::check_disallowed_words( $user_info['comment_author'], $user_info['comment_author_email'], $user_info['comment_author_url'], $content, $ip, $user_agent );
370 }
371
372 /**
373 * For WP 5.5 compatibility.
374 *
375 * @since 4.06.02
376 */
377 private static function check_disallowed_words( $author, $email, $url, $content, $ip, $user_agent ) {
378 if ( function_exists( 'wp_check_comment_disallowed_list' ) ) {
379 return wp_check_comment_disallowed_list( $author, $email, $url, $content, $ip, $user_agent );
380 } else {
381 return wp_blacklist_check( $author, $email, $url, $content, $ip, $user_agent );
382 }
383 }
384
385 /**
386 * For WP 5.5 compatibility.
387 *
388 * @since 4.06.02
389 */
390 private static function get_disallowed_words() {
391 $keys = get_option( 'disallowed_keys' );
392 if ( false === $keys ) {
393 // Fallback for WP < 5.5.
394 $keys = get_option( 'blacklist_keys' );
395 }
396 return $keys;
397 }
398
399 /**
400 * Check entries for Akismet spam
401 *
402 * @return boolean true if is spam
403 */
404 public static function akismet( $values ) {
405 if ( empty( $values['item_meta'] ) ) {
406 return false;
407 }
408
409 $datas = array(
410 'comment_type' => 'formidable',
411 );
412 self::parse_akismet_array( $datas, $values );
413
414 /**
415 * Allows modifying the values sent to Akismet.
416 *
417 * @since 5.0.07
418 *
419 * @param array $datas The array of values being sent to Akismet.
420 */
421 $datas = apply_filters( 'frm_akismet_values', $datas );
422
423 $query_string = _http_build_query( $datas, '', '&' );
424 $response = Akismet::http_post( $query_string, 'comment-check' );
425
426 return ( is_array( $response ) && $response[1] == 'true' );
427 }
428
429 /**
430 * @since 2.0
431 */
432 private static function parse_akismet_array( &$datas, $values ) {
433 self::add_site_info_to_akismet( $datas );
434 self::add_server_values_to_akismet( $datas );
435
436 self::prepare_values_for_spam_check( $values );
437
438 self::add_user_info_to_akismet( $datas, $values );
439 self::add_comment_content_to_akismet( $datas, $values );
440 }
441
442 private static function add_site_info_to_akismet( &$datas ) {
443 $datas['blog'] = FrmAppHelper::site_url();
444 $datas['user_ip'] = preg_replace( '/[^0-9., ]/', '', FrmAppHelper::get_ip_address() );
445 $datas['user_agent'] = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
446 $datas['referrer'] = isset( $_SERVER['HTTP_REFERER'] ) ? FrmAppHelper::get_server_value( 'HTTP_REFERER' ) : false;
447 $datas['blog_lang'] = get_locale();
448 $datas['blog_charset'] = get_option( 'blog_charset' );
449
450 if ( akismet_test_mode() ) {
451 $datas['is_test'] = 'true';
452 }
453 }
454
455 private static function add_user_info_to_akismet( &$datas, $values ) {
456 $user_info = self::get_spam_check_user_info( $values );
457 $datas = $datas + $user_info;
458
459 if ( isset( $user_info['user_ID'] ) ) {
460 $datas['user_role'] = Akismet::get_user_roles( $user_info['user_ID'] );
461 }
462 }
463
464 /**
465 * Gets user info for Akismet spam check.
466 *
467 * @since 5.0.13 Separate code for guest. Handle value of embedded|repeater.
468 *
469 * @param array $values Entry values after running through {@see FrmEntryValidate::prepare_values_for_spam_check()}.
470 * @return array
471 */
472 private static function get_spam_check_user_info( $values ) {
473 if ( ! is_user_logged_in() ) {
474 return self::get_spam_check_user_info_for_guest( $values );
475 }
476
477 $user = wp_get_current_user();
478
479 return array(
480 'user_ID' => $user->ID,
481 'user_id' => $user->ID,
482 'comment_author' => $user->display_name,
483 'comment_author_email' => $user->user_email,
484 'comment_author_url' => $user->user_url,
485 );
486 }
487
488 /**
489 * Gets user info for Akismet spam check for guest.
490 *
491 * @since 5.0.13
492 *
493 * @param array $values Entry values after flattened.
494 * @return array
495 */
496 private static function get_spam_check_user_info_for_guest( $values ) {
497 $datas = array(
498 'comment_author' => '',
499 'comment_author_email' => '',
500 'comment_author_url' => '',
501 'name_field_ids' => $values['name_field_ids'],
502 'missing_keys' => array( 'comment_author_email', 'comment_author_url', 'comment_author' ),
503 'frm_duplicated' => array(),
504 );
505
506 if ( isset( $values['item_meta'] ) ) {
507 $values = $values['item_meta'];
508 }
509
510 $values = array_filter( $values );
511
512 self::recursive_add_akismet_guest_info( $datas, $values );
513 unset( $datas['name_field_ids'] );
514 unset( $datas['missing_keys'] );
515
516 return $datas;
517 }
518
519 /**
520 * Recursive adds akismet guest info.
521 *
522 * @since 5.0.13
523 *
524 * @param array $datas Guest data.
525 * @param array $values The values.
526 * @param int|null $custom_index Custom index (or field ID).
527 */
528 private static function recursive_add_akismet_guest_info( &$datas, $values, $custom_index = null ) {
529 foreach ( $values as $index => $value ) {
530 if ( ! $datas['missing_keys'] ) {
531 return; // Found all info.
532 }
533
534 if ( is_array( $value ) ) {
535 self::recursive_add_akismet_guest_info( $datas, $value, $index );
536 continue;
537 }
538
539 $field_id = ! is_null( $custom_index ) ? $custom_index : $index;
540 foreach ( $datas['missing_keys'] as $key_index => $key ) {
541 $found = self::is_akismet_guest_info_value( $key, $value, $field_id, $datas['name_field_ids'] );
542 if ( $found ) {
543 $datas[ $key ] = $value;
544 $datas['frm_duplicated'][] = $field_id;
545 unset( $datas['missing_keys'][ $key_index ] );
546 }
547 }
548 }
549 }
550
551 /**
552 * Checks if given value is an akismet guest info.
553 *
554 * @since 5.0.13
555 *
556 * @param string $key Guest info key.
557 * @param string $value Value to check.
558 * @param int $field_id Field ID.
559 * @param array $name_field_ids Name field IDs.
560 * @return bool
561 */
562 private static function is_akismet_guest_info_value( $key, $value, $field_id, $name_field_ids ) {
563 if ( ! $value || is_numeric( $value ) ) {
564 return false;
565 }
566
567 switch ( $key ) {
568 case 'comment_author_email':
569 return strpos( $value, '@' ) && is_email( $value );
570
571 case 'comment_author_url':
572 return 0 === strpos( $value, 'http' );
573
574 case 'comment_author':
575 if ( $name_field_ids ) {
576 // If there is name field in the form, we should always use it as author name.
577 return in_array( $field_id, $name_field_ids, true );
578 }
579 return strlen( $value ) < 200;
580 }
581
582 return false;
583 }
584
585 private static function add_server_values_to_akismet( &$datas ) {
586 foreach ( $_SERVER as $key => $value ) {
587 $include_value = is_string( $value ) && ! preg_match( '/^HTTP_COOKIE/', $key ) && preg_match( '/^(HTTP_|REMOTE_ADDR|REQUEST_URI|DOCUMENT_URI)/', $key );
588
589 // Send any potentially useful $_SERVER vars, but avoid sending junk we don't need.
590 if ( $include_value ) {
591 $datas[ $key ] = $value;
592 }
593 unset( $key, $value );
594 }
595 }
596
597 /**
598 * Adds comment content to Akismet data.
599 *
600 * @since 5.0.09
601 *
602 * @param array $datas The array of values being sent to Akismet.
603 * @param array $values Entry values.
604 */
605 private static function add_comment_content_to_akismet( &$datas, $values ) {
606 if ( isset( $datas['frm_duplicated'] ) ) {
607 foreach ( $datas['frm_duplicated'] as $index ) {
608 if ( isset( $values['item_meta'][ $index ] ) ) {
609 unset( $values['item_meta'][ $index ] );
610 } else {
611 unset( $values[ $index ] );
612 }
613 }
614 unset( $datas['frm_duplicated'] );
615 }
616
617 self::skip_adding_values_to_akismet( $values );
618
619 $datas['comment_content'] = FrmEntriesHelper::entry_array_to_string( $values );
620 }
621
622 /**
623 * Skips adding field values to Akismet.
624 *
625 * @since 5.0.09
626 *
627 * @param array $values Entry values.
628 */
629 private static function skip_adding_values_to_akismet( &$values ) {
630 $skipped_fields = self::get_akismet_skipped_field_ids( $values );
631 foreach ( $skipped_fields as $skipped_field ) {
632 if ( ! isset( $values['item_meta'][ $skipped_field->id ] ) ) {
633 continue;
634 }
635
636 if ( self::should_really_skip_field( $skipped_field, $values ) ) {
637 unset( $values['item_meta'][ $skipped_field->id ] );
638 if ( isset( $values['item_meta']['other'][ $skipped_field->id ] ) ) {
639 unset( $values['item_meta']['other'][ $skipped_field->id ] );
640 }
641 }
642 }
643 }
644
645 /**
646 * Checks if a skip field should be really skipped.
647 *
648 * @since 5.02.04
649 *
650 * @param object $field_data Object contains `id` and `options`.
651 * @param array $values Entry values.
652 * @return bool
653 */
654 private static function should_really_skip_field( $field_data, $values ) {
655 if ( empty( $field_data->options ) ) { // This is skipped field types.
656 return true;
657 }
658
659 FrmAppHelper::unserialize_or_decode( $field_data->options );
660 if ( ! $field_data->options ) { // Check if an error happens when unserializing, or empty options.
661 return true;
662 }
663
664 end( $field_data->options );
665 $last_key = key( $field_data->options );
666
667 // If a choice field has no Other option.
668 if ( is_numeric( $last_key ) || 0 !== strpos( $last_key, 'other_' ) ) {
669 return true;
670 }
671
672 // If a choice field has Other option, but Other is not selected.
673 if ( empty( $values['item_meta']['other'][ $field_data->id ] ) ) {
674 return true;
675 }
676
677 // Check if submitted value is same as one of field option.
678 foreach ( $field_data->options as $option ) {
679 $option_value = ! is_array( $option ) ? $option : ( isset( $option['value'] ) ? $option['value'] : '' );
680 if ( $values['item_meta']['other'][ $field_data->id ] === $option_value ) {
681 return true;
682 }
683 }
684
685 return false;
686 }
687
688 /**
689 * Gets field IDs that are skipped from sending to Akismet spam check.
690 *
691 * @since 5.0.09
692 * @since 5.0.13 Move out get_all_form_ids_and_flatten_meta() call and get `form_ids` from `$values`.
693 * @since 5.2.04 This method returns array of object contains `id` and `options` instead of array of `id` only.
694 *
695 * @param array $values Entry values after running through {@see FrmEntryValidate::prepare_values_for_spam_check()}.
696 * @return array
697 */
698 private static function get_akismet_skipped_field_ids( $values ) {
699 if ( empty( $values['form_ids'] ) ) {
700 return array();
701 }
702
703 $skipped_types = array( 'divider', 'form', 'hidden', 'user_id', 'file', 'date', 'time', 'scale', 'star', 'range', 'toggle', 'data', 'lookup', 'likert', 'nps' );
704 $has_other_types = array( 'radio', 'checkbox', 'select' );
705
706 $where = array(
707 array(
708 'form_id' => $values['form_ids'],
709 'type' => array_merge( $skipped_types, $has_other_types ),
710 ),
711 );
712
713 return FrmDb::get_results( 'frm_fields', $where, 'id,options' );
714 }
715
716 /**
717 * Prepares values array for spam check.
718 *
719 * @since 5.0.13
720 *
721 * @param array $values Entry values.
722 */
723 private static function prepare_values_for_spam_check( &$values ) {
724 $form_ids = self::get_all_form_ids_and_flatten_meta( $values );
725 $values['form_ids'] = $form_ids;
726 }
727
728 /**
729 * Gets all form IDs (include child form IDs) and flatten item_meta array. Used for skipping values sent to Akismet.
730 * This also removes some unused data from the item_meta.
731 *
732 * @since 5.0.09
733 * @since 5.0.13 Convert name field value to string.
734 *
735 * @param array $values Entry values.
736 * @return array Form IDs.
737 */
738 private static function get_all_form_ids_and_flatten_meta( &$values ) {
739 $values['name_field_ids'] = array();
740
741 // Blacklist check for File field in the old version doesn't contain `form_id`.
742 $form_ids = isset( $values['form_id'] ) ? array( absint( $values['form_id'] ) ) : array();
743 foreach ( $values['item_meta'] as $field_id => $value ) {
744 if ( ! is_numeric( $field_id ) ) { // Maybe `other`.
745 continue;
746 }
747
748 // Convert name array to string.
749 if ( isset( $value['first'] ) && isset( $value['last'] ) ) {
750 $values['item_meta'][ $field_id ] = trim( implode( ' ', $value ) );
751 $values['name_field_ids'][] = $field_id;
752 continue;
753 }
754
755 if ( ! is_array( $value ) || empty( $value['form'] ) ) {
756 continue;
757 }
758
759 $form_ids[] = absint( $value['form'] );
760
761 foreach ( $value as $subindex => $subvalue ) {
762 if ( ! is_numeric( $subindex ) || ! is_array( $subvalue ) ) {
763 continue;
764 }
765
766 foreach ( $subvalue as $subsubindex => $subsubvalue ) {
767 if ( ! $subsubvalue ) {
768 continue;
769 }
770
771 if ( ! isset( $values['item_meta'][ $subsubindex ] ) ) {
772 $values['item_meta'][ $subsubindex ] = array();
773 }
774
775 // Convert name array to string.
776 if ( isset( $subsubvalue['first'] ) && isset( $subsubvalue['last'] ) ) {
777 $subsubvalue = trim( implode( ' ', $subsubvalue ) );
778
779 $values['name_field_ids'][] = $subsubindex;
780 }
781
782 $values['item_meta'][ $subsubindex ][] = $subsubvalue;
783 }
784 }
785
786 unset( $values['item_meta'][ $field_id ] );
787 }
788
789 return $form_ids;
790 }
791
792 /**
793 * @deprecated 3.0
794 * @codeCoverageIgnore
795 */
796 public static function validate_url_field( &$errors, $field, $value, $args ) {
797 FrmDeprecated::validate_url_field( $errors, $field, $value, $args );
798 }
799
800 /**
801 * @deprecated 3.0
802 * @codeCoverageIgnore
803 */
804 public static function validate_email_field( &$errors, $field, $value, $args ) {
805 FrmDeprecated::validate_email_field( $errors, $field, $value, $args );
806 }
807
808 /**
809 * @deprecated 3.0
810 * @codeCoverageIgnore
811 */
812 public static function validate_number_field( &$errors, $field, $value, $args ) {
813 FrmDeprecated::validate_number_field( $errors, $field, $value, $args );
814 }
815
816 /**
817 * @deprecated 3.0
818 * @codeCoverageIgnore
819 */
820 public static function validate_recaptcha( &$errors, $field, $args ) {
821 FrmDeprecated::validate_recaptcha( $errors, $field, $args );
822 }
823 }
824