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

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