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

827 lines 24.5 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 if ( empty( $content ) ) {
364 return false;
365 }
366
367 self::prepare_values_for_spam_check( $values );
368 $ip = FrmAppHelper::get_ip_address();
369 $user_agent = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
370 $user_info = self::get_spam_check_user_info( $values );
371
372 return self::check_disallowed_words( $user_info['comment_author'], $user_info['comment_author_email'], $user_info['comment_author_url'], $content, $ip, $user_agent );
373 }
374
375 /**
376 * For WP 5.5 compatibility.
377 *
378 * @since 4.06.02
379 */
380 private static function check_disallowed_words( $author, $email, $url, $content, $ip, $user_agent ) {
381 if ( function_exists( 'wp_check_comment_disallowed_list' ) ) {
382 return wp_check_comment_disallowed_list( $author, $email, $url, $content, $ip, $user_agent );
383 } else {
384 return wp_blacklist_check( $author, $email, $url, $content, $ip, $user_agent );
385 }
386 }
387
388 /**
389 * For WP 5.5 compatibility.
390 *
391 * @since 4.06.02
392 */
393 private static function get_disallowed_words() {
394 $keys = get_option( 'disallowed_keys' );
395 if ( false === $keys ) {
396 // Fallback for WP < 5.5.
397 $keys = get_option( 'blacklist_keys' );
398 }
399 return $keys;
400 }
401
402 /**
403 * Check entries for Akismet spam
404 *
405 * @return boolean true if is spam
406 */
407 public static function akismet( $values ) {
408 if ( empty( $values['item_meta'] ) ) {
409 return false;
410 }
411
412 $datas = array(
413 'comment_type' => 'formidable',
414 );
415 self::parse_akismet_array( $datas, $values );
416
417 /**
418 * Allows modifying the values sent to Akismet.
419 *
420 * @since 5.0.07
421 *
422 * @param array $datas The array of values being sent to Akismet.
423 */
424 $datas = apply_filters( 'frm_akismet_values', $datas );
425
426 $query_string = _http_build_query( $datas, '', '&' );
427 $response = Akismet::http_post( $query_string, 'comment-check' );
428
429 return ( is_array( $response ) && $response[1] == 'true' );
430 }
431
432 /**
433 * @since 2.0
434 */
435 private static function parse_akismet_array( &$datas, $values ) {
436 self::add_site_info_to_akismet( $datas );
437 self::add_server_values_to_akismet( $datas );
438
439 self::prepare_values_for_spam_check( $values );
440
441 self::add_user_info_to_akismet( $datas, $values );
442 self::add_comment_content_to_akismet( $datas, $values );
443 }
444
445 private static function add_site_info_to_akismet( &$datas ) {
446 $datas['blog'] = FrmAppHelper::site_url();
447 $datas['user_ip'] = preg_replace( '/[^0-9., ]/', '', FrmAppHelper::get_ip_address() );
448 $datas['user_agent'] = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
449 $datas['referrer'] = isset( $_SERVER['HTTP_REFERER'] ) ? FrmAppHelper::get_server_value( 'HTTP_REFERER' ) : false;
450 $datas['blog_lang'] = get_locale();
451 $datas['blog_charset'] = get_option( 'blog_charset' );
452
453 if ( akismet_test_mode() ) {
454 $datas['is_test'] = 'true';
455 }
456 }
457
458 private static function add_user_info_to_akismet( &$datas, $values ) {
459 $user_info = self::get_spam_check_user_info( $values );
460 $datas = $datas + $user_info;
461
462 if ( isset( $user_info['user_ID'] ) ) {
463 $datas['user_role'] = Akismet::get_user_roles( $user_info['user_ID'] );
464 }
465 }
466
467 /**
468 * Gets user info for Akismet spam check.
469 *
470 * @since 5.0.13 Separate code for guest. Handle value of embedded|repeater.
471 *
472 * @param array $values Entry values after running through {@see FrmEntryValidate::prepare_values_for_spam_check()}.
473 * @return array
474 */
475 private static function get_spam_check_user_info( $values ) {
476 if ( ! is_user_logged_in() ) {
477 return self::get_spam_check_user_info_for_guest( $values );
478 }
479
480 $user = wp_get_current_user();
481
482 return array(
483 'user_ID' => $user->ID,
484 'user_id' => $user->ID,
485 'comment_author' => $user->display_name,
486 'comment_author_email' => $user->user_email,
487 'comment_author_url' => $user->user_url,
488 );
489 }
490
491 /**
492 * Gets user info for Akismet spam check for guest.
493 *
494 * @since 5.0.13
495 *
496 * @param array $values Entry values after flattened.
497 * @return array
498 */
499 private static function get_spam_check_user_info_for_guest( $values ) {
500 $datas = array(
501 'comment_author' => '',
502 'comment_author_email' => '',
503 'comment_author_url' => '',
504 'name_field_ids' => $values['name_field_ids'],
505 'missing_keys' => array( 'comment_author_email', 'comment_author_url', 'comment_author' ),
506 'frm_duplicated' => array(),
507 );
508
509 if ( isset( $values['item_meta'] ) ) {
510 $values = $values['item_meta'];
511 }
512
513 $values = array_filter( $values );
514
515 self::recursive_add_akismet_guest_info( $datas, $values );
516 unset( $datas['name_field_ids'] );
517 unset( $datas['missing_keys'] );
518
519 return $datas;
520 }
521
522 /**
523 * Recursive adds akismet guest info.
524 *
525 * @since 5.0.13
526 *
527 * @param array $datas Guest data.
528 * @param array $values The values.
529 * @param int|null $custom_index Custom index (or field ID).
530 */
531 private static function recursive_add_akismet_guest_info( &$datas, $values, $custom_index = null ) {
532 foreach ( $values as $index => $value ) {
533 if ( ! $datas['missing_keys'] ) {
534 return; // Found all info.
535 }
536
537 if ( is_array( $value ) ) {
538 self::recursive_add_akismet_guest_info( $datas, $value, $index );
539 continue;
540 }
541
542 $field_id = ! is_null( $custom_index ) ? $custom_index : $index;
543 foreach ( $datas['missing_keys'] as $key_index => $key ) {
544 $found = self::is_akismet_guest_info_value( $key, $value, $field_id, $datas['name_field_ids'] );
545 if ( $found ) {
546 $datas[ $key ] = $value;
547 $datas['frm_duplicated'][] = $field_id;
548 unset( $datas['missing_keys'][ $key_index ] );
549 }
550 }
551 }
552 }
553
554 /**
555 * Checks if given value is an akismet guest info.
556 *
557 * @since 5.0.13
558 *
559 * @param string $key Guest info key.
560 * @param string $value Value to check.
561 * @param int $field_id Field ID.
562 * @param array $name_field_ids Name field IDs.
563 * @return bool
564 */
565 private static function is_akismet_guest_info_value( $key, $value, $field_id, $name_field_ids ) {
566 if ( ! $value || is_numeric( $value ) ) {
567 return false;
568 }
569
570 switch ( $key ) {
571 case 'comment_author_email':
572 return strpos( $value, '@' ) && is_email( $value );
573
574 case 'comment_author_url':
575 return 0 === strpos( $value, 'http' );
576
577 case 'comment_author':
578 if ( $name_field_ids ) {
579 // If there is name field in the form, we should always use it as author name.
580 return in_array( $field_id, $name_field_ids, true );
581 }
582 return strlen( $value ) < 200;
583 }
584
585 return false;
586 }
587
588 private static function add_server_values_to_akismet( &$datas ) {
589 foreach ( $_SERVER as $key => $value ) {
590 $include_value = is_string( $value ) && ! preg_match( '/^HTTP_COOKIE/', $key ) && preg_match( '/^(HTTP_|REMOTE_ADDR|REQUEST_URI|DOCUMENT_URI)/', $key );
591
592 // Send any potentially useful $_SERVER vars, but avoid sending junk we don't need.
593 if ( $include_value ) {
594 $datas[ $key ] = $value;
595 }
596 unset( $key, $value );
597 }
598 }
599
600 /**
601 * Adds comment content to Akismet data.
602 *
603 * @since 5.0.09
604 *
605 * @param array $datas The array of values being sent to Akismet.
606 * @param array $values Entry values.
607 */
608 private static function add_comment_content_to_akismet( &$datas, $values ) {
609 if ( isset( $datas['frm_duplicated'] ) ) {
610 foreach ( $datas['frm_duplicated'] as $index ) {
611 if ( isset( $values['item_meta'][ $index ] ) ) {
612 unset( $values['item_meta'][ $index ] );
613 } else {
614 unset( $values[ $index ] );
615 }
616 }
617 unset( $datas['frm_duplicated'] );
618 }
619
620 self::skip_adding_values_to_akismet( $values );
621
622 $datas['comment_content'] = FrmEntriesHelper::entry_array_to_string( $values );
623 }
624
625 /**
626 * Skips adding field values to Akismet.
627 *
628 * @since 5.0.09
629 *
630 * @param array $values Entry values.
631 */
632 private static function skip_adding_values_to_akismet( &$values ) {
633 $skipped_fields = self::get_akismet_skipped_field_ids( $values );
634 foreach ( $skipped_fields as $skipped_field ) {
635 if ( ! isset( $values['item_meta'][ $skipped_field->id ] ) ) {
636 continue;
637 }
638
639 if ( self::should_really_skip_field( $skipped_field, $values ) ) {
640 unset( $values['item_meta'][ $skipped_field->id ] );
641 if ( isset( $values['item_meta']['other'][ $skipped_field->id ] ) ) {
642 unset( $values['item_meta']['other'][ $skipped_field->id ] );
643 }
644 }
645 }
646 }
647
648 /**
649 * Checks if a skip field should be really skipped.
650 *
651 * @since 5.02.04
652 *
653 * @param object $field_data Object contains `id` and `options`.
654 * @param array $values Entry values.
655 * @return bool
656 */
657 private static function should_really_skip_field( $field_data, $values ) {
658 if ( empty( $field_data->options ) ) { // This is skipped field types.
659 return true;
660 }
661
662 FrmAppHelper::unserialize_or_decode( $field_data->options );
663 if ( ! $field_data->options ) { // Check if an error happens when unserializing, or empty options.
664 return true;
665 }
666
667 end( $field_data->options );
668 $last_key = key( $field_data->options );
669
670 // If a choice field has no Other option.
671 if ( is_numeric( $last_key ) || 0 !== strpos( $last_key, 'other_' ) ) {
672 return true;
673 }
674
675 // If a choice field has Other option, but Other is not selected.
676 if ( empty( $values['item_meta']['other'][ $field_data->id ] ) ) {
677 return true;
678 }
679
680 // Check if submitted value is same as one of field option.
681 foreach ( $field_data->options as $option ) {
682 $option_value = ! is_array( $option ) ? $option : ( isset( $option['value'] ) ? $option['value'] : '' );
683 if ( $values['item_meta']['other'][ $field_data->id ] === $option_value ) {
684 return true;
685 }
686 }
687
688 return false;
689 }
690
691 /**
692 * Gets field IDs that are skipped from sending to Akismet spam check.
693 *
694 * @since 5.0.09
695 * @since 5.0.13 Move out get_all_form_ids_and_flatten_meta() call and get `form_ids` from `$values`.
696 * @since 5.2.04 This method returns array of object contains `id` and `options` instead of array of `id` only.
697 *
698 * @param array $values Entry values after running through {@see FrmEntryValidate::prepare_values_for_spam_check()}.
699 * @return array
700 */
701 private static function get_akismet_skipped_field_ids( $values ) {
702 if ( empty( $values['form_ids'] ) ) {
703 return array();
704 }
705
706 $skipped_types = array( 'divider', 'form', 'hidden', 'user_id', 'file', 'date', 'time', 'scale', 'star', 'range', 'toggle', 'data', 'lookup', 'likert', 'nps' );
707 $has_other_types = array( 'radio', 'checkbox', 'select' );
708
709 $where = array(
710 array(
711 'form_id' => $values['form_ids'],
712 'type' => array_merge( $skipped_types, $has_other_types ),
713 ),
714 );
715
716 return FrmDb::get_results( 'frm_fields', $where, 'id,options' );
717 }
718
719 /**
720 * Prepares values array for spam check.
721 *
722 * @since 5.0.13
723 *
724 * @param array $values Entry values.
725 */
726 private static function prepare_values_for_spam_check( &$values ) {
727 $form_ids = self::get_all_form_ids_and_flatten_meta( $values );
728 $values['form_ids'] = $form_ids;
729 }
730
731 /**
732 * Gets all form IDs (include child form IDs) and flatten item_meta array. Used for skipping values sent to Akismet.
733 * This also removes some unused data from the item_meta.
734 *
735 * @since 5.0.09
736 * @since 5.0.13 Convert name field value to string.
737 *
738 * @param array $values Entry values.
739 * @return array Form IDs.
740 */
741 private static function get_all_form_ids_and_flatten_meta( &$values ) {
742 $values['name_field_ids'] = array();
743
744 // Blacklist check for File field in the old version doesn't contain `form_id`.
745 $form_ids = isset( $values['form_id'] ) ? array( absint( $values['form_id'] ) ) : array();
746 foreach ( $values['item_meta'] as $field_id => $value ) {
747 if ( ! is_numeric( $field_id ) ) { // Maybe `other`.
748 continue;
749 }
750
751 // Convert name array to string.
752 if ( isset( $value['first'] ) && isset( $value['last'] ) ) {
753 $values['item_meta'][ $field_id ] = trim( implode( ' ', $value ) );
754 $values['name_field_ids'][] = $field_id;
755 continue;
756 }
757
758 if ( ! is_array( $value ) || empty( $value['form'] ) ) {
759 continue;
760 }
761
762 $form_ids[] = absint( $value['form'] );
763
764 foreach ( $value as $subindex => $subvalue ) {
765 if ( ! is_numeric( $subindex ) || ! is_array( $subvalue ) ) {
766 continue;
767 }
768
769 foreach ( $subvalue as $subsubindex => $subsubvalue ) {
770 if ( ! $subsubvalue ) {
771 continue;
772 }
773
774 if ( ! isset( $values['item_meta'][ $subsubindex ] ) ) {
775 $values['item_meta'][ $subsubindex ] = array();
776 }
777
778 // Convert name array to string.
779 if ( isset( $subsubvalue['first'] ) && isset( $subsubvalue['last'] ) ) {
780 $subsubvalue = trim( implode( ' ', $subsubvalue ) );
781
782 $values['name_field_ids'][] = $subsubindex;
783 }
784
785 $values['item_meta'][ $subsubindex ][] = $subsubvalue;
786 }
787 }
788
789 unset( $values['item_meta'][ $field_id ] );
790 }
791
792 return $form_ids;
793 }
794
795 /**
796 * @deprecated 3.0
797 * @codeCoverageIgnore
798 */
799 public static function validate_url_field( &$errors, $field, $value, $args ) {
800 FrmDeprecated::validate_url_field( $errors, $field, $value, $args );
801 }
802
803 /**
804 * @deprecated 3.0
805 * @codeCoverageIgnore
806 */
807 public static function validate_email_field( &$errors, $field, $value, $args ) {
808 FrmDeprecated::validate_email_field( $errors, $field, $value, $args );
809 }
810
811 /**
812 * @deprecated 3.0
813 * @codeCoverageIgnore
814 */
815 public static function validate_number_field( &$errors, $field, $value, $args ) {
816 FrmDeprecated::validate_number_field( $errors, $field, $value, $args );
817 }
818
819 /**
820 * @deprecated 3.0
821 * @codeCoverageIgnore
822 */
823 public static function validate_recaptcha( &$errors, $field, $args ) {
824 FrmDeprecated::validate_recaptcha( $errors, $field, $args );
825 }
826 }
827