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

484 lines 15.0 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 $errors['form'] = __( 'You do not have permission to do that', 'formidable' );
19 }
20
21 self::set_item_key( $values );
22
23 $posted_fields = self::get_fields_to_validate( $values, $exclude );
24
25 // Pass exclude value to validate_field function so it can be used for repeating sections
26 $args = array( 'exclude' => $exclude );
27
28 foreach ( $posted_fields as $posted_field ) {
29 self::validate_field( $posted_field, $errors, $values, $args );
30 unset( $posted_field );
31 }
32
33 if ( empty( $errors ) ) {
34 self::spam_check( $exclude, $values, $errors );
35 }
36
37 /**
38 * Allows modifying the validation errors after validating all fields.
39 *
40 * @since 5.0.04 Added `posted_fields` to the third param.
41 *
42 * @param array $errors Errors data.
43 * @param array $values Value data of the form.
44 * @param array $args Custom arguments. Contains `exclude` and `posted_fields`.
45 */
46 $errors = apply_filters( 'frm_validate_entry', $errors, $values, compact( 'exclude', 'posted_fields' ) );
47
48 return $errors;
49 }
50
51 private static function set_item_key( &$values ) {
52 if ( ! isset( $values['item_key'] ) || $values['item_key'] == '' ) {
53 global $wpdb;
54 $values['item_key'] = FrmAppHelper::get_unique_key( '', $wpdb->prefix . 'frm_items', 'item_key' );
55 $_POST['item_key'] = $values['item_key'];
56 }
57 }
58
59 private static function get_fields_to_validate( $values, $exclude ) {
60 $where = apply_filters( 'frm_posted_field_ids', array( 'fi.form_id' => $values['form_id'] ) );
61
62 // Don't get subfields
63 $where['fr.parent_form_id'] = array( null, 0 );
64
65 // Don't get excluded fields (like file upload fields in the ajax validation)
66 if ( ! empty( $exclude ) ) {
67 $where['fi.type not'] = $exclude;
68 }
69
70 return FrmField::getAll( $where, 'field_order' );
71 }
72
73 public static function validate_field( $posted_field, &$errors, $values, $args = array() ) {
74 $defaults = array(
75 'id' => $posted_field->id,
76 'parent_field_id' => '', // the id of the repeat or embed form
77 'key_pointer' => '', // the pointer in the posted array
78 'exclude' => array(), // exclude these field types from validation
79 );
80 $args = wp_parse_args( $args, $defaults );
81
82 if ( empty( $args['parent_field_id'] ) ) {
83 $value = isset( $values['item_meta'][ $args['id'] ] ) ? $values['item_meta'][ $args['id'] ] : '';
84 } else {
85 // value is from a nested form
86 $value = $values;
87 }
88
89 // Check for values in "Other" fields
90 FrmEntriesHelper::maybe_set_other_validation( $posted_field, $value, $args );
91
92 self::maybe_clear_value_for_default_blank_setting( $posted_field, $value );
93
94 $should_trim = is_array( $value ) && count( $value ) == 1 && isset( $value[0] ) && $posted_field->type !== 'checkbox';
95 if ( $should_trim ) {
96 $value = reset( $value );
97 }
98
99 if ( ! is_array( $value ) ) {
100 $value = trim( $value );
101 }
102
103 if ( $posted_field->required == '1' && FrmAppHelper::is_empty_value( $value ) ) {
104 $errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $posted_field, 'blank' );
105 } elseif ( $posted_field->type == 'text' && ! isset( $_POST['item_name'] ) ) { // WPCS: CSRF ok.
106 $_POST['item_name'] = $value;
107 }
108
109 FrmEntriesHelper::set_posted_value( $posted_field, $value, $args );
110
111 self::validate_field_types( $errors, $posted_field, $value, $args );
112
113 // Field might want to modify value before other parts of the system
114 // e.g. trim off excess values like in the case of fields with limit.
115 $value = apply_filters( 'frm_modify_posted_field_value', $value, $errors, $posted_field, $args );
116
117 if ( $value != '' ) {
118 self::validate_phone_field( $errors, $posted_field, $value, $args );
119 }
120
121 $errors = apply_filters( 'frm_validate_' . $posted_field->type . '_field_entry', $errors, $posted_field, $value, $args );
122 $errors = apply_filters( 'frm_validate_field_entry', $errors, $posted_field, $value, $args );
123 }
124
125 /**
126 * Set $value to an empty string if it matches its label
127 *
128 * @param object $field
129 * @param string $value
130 */
131 private static function maybe_clear_value_for_default_blank_setting( $field, &$value ) {
132 $position = FrmField::get_option( $field, 'label' );
133 if ( ! $position ) {
134 $position = FrmStylesController::get_style_val( 'position', $field->form_id );
135 }
136
137 if ( $position === 'inside' && FrmFieldsHelper::is_placeholder_field_type( $field->type ) && $value === $field->name ) {
138 $value = '';
139 }
140 }
141
142 public static function validate_field_types( &$errors, $posted_field, $value, $args ) {
143 $field_obj = FrmFieldFactory::get_field_object( $posted_field );
144 $args['value'] = $value;
145 $args['errors'] = $errors;
146
147 $new_errors = $field_obj->validate( $args );
148 if ( ! empty( $new_errors ) ) {
149 $errors = array_merge( $errors, $new_errors );
150 }
151 }
152
153 public static function validate_phone_field( &$errors, $field, $value, $args ) {
154 if ( $field->type == 'phone' || ( $field->type == 'text' && FrmField::is_option_true_in_object( $field, 'format' ) ) ) {
155
156 $pattern = self::phone_format( $field );
157
158 if ( ! preg_match( $pattern, $value ) ) {
159 $errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
160 }
161 }
162 }
163
164 public static function phone_format( $field ) {
165 if ( FrmField::is_option_empty( $field, 'format' ) ) {
166 $pattern = self::default_phone_format();
167 } else {
168 $pattern = FrmField::get_option( $field, 'format' );
169 }
170
171 $pattern = apply_filters( 'frm_phone_pattern', $pattern, $field );
172
173 // Create a regexp if format is not already a regexp
174 if ( strpos( $pattern, '^' ) !== 0 ) {
175 $pattern = self::create_regular_expression_from_format( $pattern );
176 }
177
178 $pattern = '/' . $pattern . '/';
179
180 return $pattern;
181 }
182
183 /**
184 * @since 3.01
185 */
186 private static function default_phone_format() {
187 return '^((\+\d{1,3}(-|.| )?\(?\d\)?(-| |.)?\d{1,5})|(\(?\d{2,6}\)?))(-|.| )?(\d{3,4})(-|.| )?(\d{4})(( x| ext)\d{1,5}){0,1}$';
188 }
189
190 /**
191 * Create a regular expression from a phone number format
192 *
193 * @since 2.02.02
194 *
195 * @param string $pattern
196 *
197 * @return string
198 */
199 private static function create_regular_expression_from_format( $pattern ) {
200 $pattern = preg_quote( $pattern );
201
202 // Firefox doesn't like escaped dashes or colons
203 $pattern = str_replace( array( '\-', '\:' ), array( '-', ':' ), $pattern );
204
205 // Switch generic values out for their regular expression
206 $pattern = preg_replace( '/\d/', '\d', $pattern );
207 $pattern = str_replace( 'A', '[A-Z]', $pattern );
208 $pattern = str_replace( 'a', '[a-zA-Z]', $pattern );
209 $pattern = str_replace( '*', 'w', $pattern );
210 $pattern = str_replace( '/', '\/', $pattern );
211
212 if ( strpos( $pattern, '\?' ) !== false ) {
213 $parts = explode( '\?', $pattern );
214 $pattern = '';
215 foreach ( $parts as $part ) {
216 if ( empty( $pattern ) ) {
217 $pattern .= $part;
218 } else {
219 $pattern .= '(' . $part . ')?';
220 }
221 }
222 }
223 $pattern = '^' . $pattern . '$';
224
225 return $pattern;
226 }
227
228 /**
229 * Check for spam
230 *
231 * @param boolean $exclude
232 * @param array $values
233 * @param array $errors by reference
234 */
235 public static function spam_check( $exclude, $values, &$errors ) {
236 if ( ! empty( $exclude ) || ! isset( $values['item_meta'] ) || empty( $values['item_meta'] ) || ! empty( $errors ) ) {
237 // only check spam if there are no other errors
238 return;
239 }
240
241 $antispam_check = self::is_antispam_check( $values['form_id'] );
242 if ( is_string( $antispam_check ) ) {
243 $errors['spam'] = $antispam_check;
244 } elseif ( self::is_honeypot_spam( $values ) || self::is_spam_bot() ) {
245 $errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
246 } elseif ( self::blacklist_check( $values ) ) {
247 $errors['spam'] = __( 'Your entry appears to be blocked spam!', 'formidable' );
248 } elseif ( self::is_akismet_spam( $values ) && self::is_akismet_enabled_for_user( $values['form_id'] ) ) {
249 $errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
250 }
251 }
252
253 /**
254 * @param int $form_id
255 * @return boolean
256 */
257 private static function is_antispam_check( $form_id ) {
258 $aspm = new FrmAntiSpam( $form_id );
259 return $aspm->validate();
260 }
261
262 /**
263 * @param array $values
264 * @return boolean
265 */
266 private static function is_honeypot_spam( $values ) {
267 $honeypot = new FrmHoneypot( $values['form_id'] );
268 return ! $honeypot->validate();
269 }
270
271 /**
272 * @return boolean
273 */
274 private static function is_spam_bot() {
275 $ip = FrmAppHelper::get_ip_address();
276
277 return empty( $ip );
278 }
279
280 /**
281 * @param array $values
282 * @return boolean
283 */
284 private static function is_akismet_spam( $values ) {
285 global $wpcom_api_key;
286
287 return ( is_callable( 'Akismet::http_post' ) && ( get_option( 'wordpress_api_key' ) || $wpcom_api_key ) && self::akismet( $values ) );
288 }
289
290 /**
291 * @param int $form_id
292 * @return bool
293 */
294 private static function is_akismet_enabled_for_user( $form_id ) {
295 $form = FrmForm::getOne( $form_id );
296
297 return ( isset( $form->options['akismet'] ) && ! empty( $form->options['akismet'] ) && ( $form->options['akismet'] !== 'logged' || ! is_user_logged_in() ) );
298 }
299
300 public static function blacklist_check( $values ) {
301 if ( ! apply_filters( 'frm_check_blacklist', true, $values ) ) {
302 return false;
303 }
304
305 $mod_keys = trim( self::get_disallowed_words() );
306 if ( empty( $mod_keys ) ) {
307 return false;
308 }
309
310 $content = FrmEntriesHelper::entry_array_to_string( $values );
311 if ( empty( $content ) ) {
312 return false;
313 }
314
315 $ip = FrmAppHelper::get_ip_address();
316 $user_agent = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
317 $user_info = self::get_spam_check_user_info( $values );
318
319 return self::check_disallowed_words( $user_info['comment_author'], $user_info['comment_author_email'], $user_info['comment_author_url'], $content, $ip, $user_agent );
320 }
321
322 /**
323 * For WP 5.5 compatibility.
324 *
325 * @since 4.06.02
326 */
327 private static function check_disallowed_words( $author, $email, $url, $content, $ip, $user_agent ) {
328 if ( function_exists( 'wp_check_comment_disallowed_list' ) ) {
329 return wp_check_comment_disallowed_list( $author, $email, $url, $content, $ip, $user_agent );
330 } else {
331 return wp_blacklist_check( $author, $email, $url, $content, $ip, $user_agent );
332 }
333 }
334
335 /**
336 * For WP 5.5 compatibility.
337 *
338 * @since 4.06.02
339 */
340 private static function get_disallowed_words() {
341 $keys = get_option( 'disallowed_keys' );
342 if ( false === $keys ) {
343 // Fallback for WP < 5.5.
344 $keys = get_option( 'blacklist_keys' );
345 }
346 return $keys;
347 }
348
349 /**
350 * Check entries for Akismet spam
351 *
352 * @return boolean true if is spam
353 */
354 public static function akismet( $values ) {
355 $content = FrmEntriesHelper::entry_array_to_string( $values );
356 if ( empty( $content ) ) {
357 return false;
358 }
359
360 $datas = array(
361 'comment_type' => 'formidable',
362 'comment_content' => $content,
363 );
364 self::parse_akismet_array( $datas, $values );
365
366 $query_string = _http_build_query( $datas, '', '&' );
367 $response = Akismet::http_post( $query_string, 'comment-check' );
368
369 return ( is_array( $response ) && $response[1] == 'true' );
370 }
371
372 /**
373 * @since 2.0
374 */
375 private static function parse_akismet_array( &$datas, $values ) {
376 self::add_site_info_to_akismet( $datas );
377 self::add_user_info_to_akismet( $datas, $values );
378 self::add_server_values_to_akismet( $datas );
379 }
380
381 private static function add_site_info_to_akismet( &$datas ) {
382 $datas['blog'] = FrmAppHelper::site_url();
383 $datas['user_ip'] = preg_replace( '/[^0-9., ]/', '', FrmAppHelper::get_ip_address() );
384 $datas['user_agent'] = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
385 $datas['referrer'] = isset( $_SERVER['HTTP_REFERER'] ) ? FrmAppHelper::get_server_value( 'HTTP_REFERER' ) : false;
386 $datas['blog_lang'] = get_locale();
387 $datas['blog_charset'] = get_option( 'blog_charset' );
388
389 if ( akismet_test_mode() ) {
390 $datas['is_test'] = 'true';
391 }
392 }
393
394 private static function add_user_info_to_akismet( &$datas, $values ) {
395 $user_info = self::get_spam_check_user_info( $values );
396 $datas = $datas + $user_info;
397
398 if ( isset( $user_info['user_ID'] ) ) {
399 $datas['user_role'] = Akismet::get_user_roles( $user_info['user_ID'] );
400 }
401 }
402
403 private static function get_spam_check_user_info( $values ) {
404 $datas = array();
405
406 if ( is_user_logged_in() ) {
407 $user = wp_get_current_user();
408
409 $datas['user_ID'] = $user->ID;
410 $datas['user_id'] = $user->ID;
411 $datas['comment_author'] = $user->display_name;
412 $datas['comment_author_email'] = $user->user_email;
413 $datas['comment_author_url'] = $user->user_url;
414 } else {
415 $datas['comment_author'] = '';
416 $datas['comment_author_email'] = '';
417 $datas['comment_author_url'] = '';
418
419 if ( isset( $values['item_meta'] ) ) {
420 $values = $values['item_meta'];
421 }
422
423 $values = array_filter( $values );
424 foreach ( $values as $value ) {
425 if ( ! is_array( $value ) ) {
426 if ( $datas['comment_author_email'] == '' && strpos( $value, '@' ) && is_email( $value ) ) {
427 $datas['comment_author_email'] = $value;
428 } elseif ( $datas['comment_author_url'] == '' && strpos( $value, 'http' ) === 0 ) {
429 $datas['comment_author_url'] = $value;
430 } elseif ( $datas['comment_author'] == '' && ! is_numeric( $value ) && strlen( $value ) < 200 ) {
431 $datas['comment_author'] = $value;
432 }
433 }
434 }
435 }
436
437 return $datas;
438 }
439
440 private static function add_server_values_to_akismet( &$datas ) {
441 foreach ( $_SERVER as $key => $value ) {
442 $include_value = is_string( $value ) && ! preg_match( '/^HTTP_COOKIE/', $key ) && preg_match( '/^(HTTP_|REMOTE_ADDR|REQUEST_URI|DOCUMENT_URI)/', $key );
443
444 // Send any potentially useful $_SERVER vars, but avoid sending junk we don't need.
445 if ( $include_value ) {
446 $datas[ $key ] = $value;
447 }
448 unset( $key, $value );
449 }
450 }
451
452 /**
453 * @deprecated 3.0
454 * @codeCoverageIgnore
455 */
456 public static function validate_url_field( &$errors, $field, $value, $args ) {
457 FrmDeprecated::validate_url_field( $errors, $field, $value, $args );
458 }
459
460 /**
461 * @deprecated 3.0
462 * @codeCoverageIgnore
463 */
464 public static function validate_email_field( &$errors, $field, $value, $args ) {
465 FrmDeprecated::validate_email_field( $errors, $field, $value, $args );
466 }
467
468 /**
469 * @deprecated 3.0
470 * @codeCoverageIgnore
471 */
472 public static function validate_number_field( &$errors, $field, $value, $args ) {
473 FrmDeprecated::validate_number_field( $errors, $field, $value, $args );
474 }
475
476 /**
477 * @deprecated 3.0
478 * @codeCoverageIgnore
479 */
480 public static function validate_recaptcha( &$errors, $field, $args ) {
481 FrmDeprecated::validate_recaptcha( $errors, $field, $args );
482 }
483 }
484