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

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