Completed
Push — master ( 96188e...703576 )
by Jamie
03:14
created

FrmEntryValidate::validate()   D

Complexity

Conditions 9
Paths 7

Size

Total Lines 34
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 19
nc 7
nop 2
dl 0
loc 34
rs 4.909
c 0
b 0
f 0
1
<?php
2
3
class FrmEntryValidate {
4
    public static function validate( $values, $exclude = false ) {
5
        FrmEntry::sanitize_entry_post( $values );
6
        $errors = array();
7
8
        if ( ! isset($values['form_id']) || ! isset($values['item_meta']) ) {
9
            $errors['form'] = __( 'There was a problem with your submission. Please try again.', 'formidable' );
10
            return $errors;
11
        }
12
13
		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' ) ) ) {
14
            $errors['form'] = __( 'You do not have permission to do that', 'formidable' );
15
        }
16
17
		self::set_item_key( $values );
18
19
		$posted_fields = self::get_fields_to_validate( $values, $exclude );
20
21
		// Pass exclude value to validate_field function so it can be used for repeating sections
22
	    $args = array( 'exclude' => $exclude );
23
24
	    self::spam_check( $exclude, $values, $errors );
25
	    if ( ! empty( $errors ) ) {
26
		    return $errors;
27
	    }
28
29
	    foreach ( $posted_fields as $posted_field ) {
30
		    self::validate_field( $posted_field, $errors, $values, $args );
31
		    unset( $posted_field );
32
	    }
33
34
	    $errors = apply_filters( 'frm_validate_entry', $errors, $values, compact( 'exclude' ) );
35
36
		return $errors;
37
	}
38
39
	private static function set_item_key( &$values ) {
40
		if ( ! isset( $values['item_key'] ) || $values['item_key'] == '' ) {
41
			global $wpdb;
42
			$values['item_key'] = FrmAppHelper::get_unique_key( '', $wpdb->prefix . 'frm_items', 'item_key' );
43
			$_POST['item_key'] = $values['item_key'];
44
		}
45
	}
46
47
	private static function get_fields_to_validate( $values, $exclude ) {
48
		$where = apply_filters( 'frm_posted_field_ids', array( 'fi.form_id' => $values['form_id'] ) );
49
50
		// Don't get subfields
51
		$where['fr.parent_form_id'] = array( null, 0 );
52
53
		// Don't get excluded fields (like file upload fields in the ajax validation)
54
		if ( ! empty( $exclude ) ) {
55
			$where['fi.type not'] = $exclude;
56
		}
57
58
		return FrmField::getAll( $where, 'field_order' );
59
	}
60
61
    public static function validate_field( $posted_field, &$errors, $values, $args = array() ) {
62
        $defaults = array(
63
            'id'              => $posted_field->id,
64
            'parent_field_id' => '', // the id of the repeat or embed form
65
            'key_pointer'     => '', // the pointer in the posted array
66
            'exclude'         => array(), // exclude these field types from validation
67
        );
68
        $args = wp_parse_args( $args, $defaults );
69
70
        if ( empty($args['parent_field_id']) ) {
71
			$value = isset( $values['item_meta'][ $args['id'] ] ) ? $values['item_meta'][ $args['id'] ] : '';
72
        } else {
73
            // value is from a nested form
74
            $value = $values;
75
        }
76
77
        // Check for values in "Other" fields
78
        FrmEntriesHelper::maybe_set_other_validation( $posted_field, $value, $args );
79
80
		self::maybe_clear_value_for_default_blank_setting( $posted_field, $value );
81
82
		// Reset arrays with only one value if it's not a field where array keys need to be preserved
83 View Code Duplication
		if ( is_array($value) && count( $value ) == 1 && isset( $value[0] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
84
			$value = reset($value);
85
		}
86
87
        if ( $posted_field->required == '1' && ! is_array( $value ) && trim( $value ) == '' ) {
88
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $posted_field, 'blank' );
89
        } else if ( $posted_field->type == 'text' && ! isset( $_POST['item_name'] ) ) {
90
            $_POST['item_name'] = $value;
91
        }
92
93
		if ( $value != '' ) {
94
			self::validate_url_field( $errors, $posted_field, $value, $args );
95
			self::validate_email_field( $errors, $posted_field, $value, $args );
96
			self::validate_number_field( $errors, $posted_field, $value, $args );
97
			self::validate_phone_field( $errors, $posted_field, $value, $args );
98
		}
99
100
        FrmEntriesHelper::set_posted_value($posted_field, $value, $args);
101
102
        self::validate_recaptcha($errors, $posted_field, $args);
103
104
		$errors = apply_filters( 'frm_validate_' . $posted_field->type . '_field_entry', $errors, $posted_field, $value, $args );
105
		$errors = apply_filters( 'frm_validate_field_entry', $errors, $posted_field, $value, $args );
106
    }
107
108
	private static function maybe_clear_value_for_default_blank_setting( $field, &$value ) {
109
		if ( FrmField::is_option_true_in_object( $field, 'default_blank' ) && $value == $field->default_value ) {
110
			$value = '';
111
		}
112
	}
113
114
	public static function validate_url_field( &$errors, $field, &$value, $args ) {
115
		if ( $value == '' || ! in_array( $field->type, array( 'website', 'url', 'image' ) ) ) {
116
            return;
117
        }
118
119
        if ( trim($value) == 'http://' ) {
120
            $value = '';
121
        } else {
122
            $value = esc_url_raw( $value );
123
			$value = preg_match( '/^(https?|ftps?|mailto|news|feed|telnet):/is', $value ) ? $value : 'http://' . $value;
124
        }
125
126
        // validate the url format
127 View Code Duplication
		if ( ! preg_match('/^http(s)?:\/\/(?:localhost|(?:[\da-z\.-]+\.[\da-z\.-]+))/i', $value) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
128
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
129
		}
130
    }
131
132
	public static function validate_email_field( &$errors, $field, $value, $args ) {
133
        if ( $value == '' || $field->type != 'email' ) {
134
            return;
135
        }
136
137
        //validate the email format
138 View Code Duplication
        if ( ! is_email($value) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
139
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
140
        }
141
    }
142
143
	public static function validate_number_field( &$errors, $field, $value, $args ) {
144
		//validate the number format
145
		if ( $field->type != 'number' ) {
146
			return;
147
		}
148
149
		if ( ! is_numeric( $value) ) {
150
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
151
		}
152
153
		// validate number settings
154
		if ( $value != '' ) {
155
			$frm_settings = FrmAppHelper::get_settings();
156
			// only check if options are available in settings
157
			if ( $frm_settings->use_html && isset( $field->field_options['minnum'] ) && isset( $field->field_options['maxnum'] ) ) {
158
				//minnum maxnum
159
				if ( (float) $value < $field->field_options['minnum'] ) {
160
					$errors[ 'field' . $args['id'] ] = __( 'Please select a higher number', 'formidable' );
161
				} else if ( (float) $value > $field->field_options['maxnum'] ) {
162
					$errors[ 'field' . $args['id'] ] = __( 'Please select a lower number', 'formidable' );
163
				}
164
			}
165
		}
166
	}
167
168
	public static function validate_phone_field( &$errors, $field, $value, $args ) {
169
		if ( $field->type == 'phone' || ( $field->type == 'text' && FrmField::is_option_true_in_object( $field, 'format' ) ) ) {
170
171
			$pattern = self::phone_format( $field );
172
173 View Code Duplication
			if ( ! preg_match( $pattern, $value ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
174
				$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
175
			}
176
		}
177
	}
178
179
	public static function phone_format( $field ) {
180
		$default_format = '^((\+\d{1,3}(-|.| )?\(?\d\)?(-| |.)?\d{1,5})|(\(?\d{2,6}\)?))(-|.| )?(\d{3,4})(-|.| )?(\d{4})(( x| ext)\d{1,5}){0,1}$';
181
		if ( FrmField::is_option_empty( $field, 'format' ) ) {
182
			$pattern = $default_format;
183
		} else {
184
			$pattern = FrmField::get_option( $field, 'format' );
185
		}
186
187
		$pattern = apply_filters( 'frm_phone_pattern', $pattern, $field );
188
189
		// Create a regexp if format is not already a regexp
190
		if ( strpos( $pattern, '^' ) !== 0 ) {
191
			$pattern = self::create_regular_expression_from_format( $pattern );
192
		}
193
194
		$pattern = '/' . $pattern . '/';
195
		return $pattern;
196
	}
197
198
	/**
199
	 * Create a regular expression from a phone number format
200
	 *
201
	 * @since 2.02.02
202
	 * @param string $pattern
203
	 * @return string
204
	 */
205
	private static function create_regular_expression_from_format( $pattern ) {
206
		$pattern = preg_quote( $pattern );
207
208
		// Firefox doesn't like escaped dashes or colons
209
		$pattern = str_replace( array( '\-', '\:' ), array( '-', ':' ), $pattern );
210
211
		// Switch generic values out for their regular expression
212
		$pattern = preg_replace( '/\d/', '\d', $pattern );
213
		$pattern = str_replace( 'a', '[a-z]', $pattern );
214
		$pattern = str_replace( 'A', '[A-Z]', $pattern );
215
		$pattern = str_replace( '*', 'w', $pattern );
216
		$pattern = str_replace( '/', '\/', $pattern );
217
218
		if ( strpos( $pattern, '\?' ) !== false ) {
219
			$parts = explode( '\?', $pattern );
220
			$pattern = '';
221
			foreach ( $parts as $part ) {
222
				if ( empty( $pattern ) ) {
223
					$pattern .= $part;
224
				} else {
225
					$pattern .= '(' . $part . ')?';
226
				}
227
			}
228
		}
229
		$pattern = '^' . $pattern . '$';
230
231
		return $pattern;
232
	}
233
234
	public static function validate_recaptcha( &$errors, $field, $args ) {
235
        if ( $field->type != 'captcha' || FrmAppHelper::is_admin() || apply_filters( 'frm_is_field_hidden', false, $field, stripslashes_deep( $_POST ) ) ) {
236
            return;
237
        }
238
239
		$frm_settings = FrmAppHelper::get_settings();
240
		if ( empty( $frm_settings->pubkey ) ) {
241
			// don't require the captcha if it shouldn't be shown
242
			return;
243
		}
244
245
        if ( ! isset($_POST['g-recaptcha-response']) ) {
246
            // If captcha is missing, check if it was already verified
247
			if ( ! isset( $_POST['recaptcha_checked'] ) || ! wp_verify_nonce( $_POST['recaptcha_checked'], 'frm_ajax' ) ) {
248
                // There was no captcha submitted
249
				$errors[ 'field' . $args['id'] ] = __( 'The captcha is missing from this form', 'formidable' );
250
            }
251
            return;
252
        }
253
254
        $arg_array = array(
255
            'body'      => array(
256
				'secret'   => $frm_settings->privkey,
257
				'response' => $_POST['g-recaptcha-response'],
258
				'remoteip' => FrmAppHelper::get_ip_address(),
259
			),
260
		);
261
        $resp = wp_remote_post( 'https://www.google.com/recaptcha/api/siteverify', $arg_array );
262
        $response = json_decode(wp_remote_retrieve_body( $resp ), true);
263
264
        if ( isset( $response['success'] ) && ! $response['success'] ) {
265
            // What happens when the CAPTCHA was entered incorrectly
266
			$errors[ 'field' . $args['id'] ] = ( ! isset( $field->field_options['invalid'] ) || $field->field_options['invalid'] == '' ) ? $frm_settings->re_msg : $field->field_options['invalid'];
267
        } else if ( is_wp_error( $resp ) ) {
268
			$error_string = $resp->get_error_message();
269
			$errors[ 'field' . $args['id'] ] = __( 'There was a problem verifying your recaptcha', 'formidable' );
270
			$errors[ 'field' . $args['id'] ] .= ' ' . $error_string;
271
        }
272
    }
273
274
    /**
275
     * check for spam
276
     * @param boolean $exclude
277
     * @param array $values
278
     * @param array $errors by reference
279
     */
280
    public static function spam_check( $exclude, $values, &$errors ) {
281
        if ( ! empty( $exclude ) || ! isset( $values['item_meta'] ) || empty( $values['item_meta'] ) || ! empty( $errors ) ) {
282
            // only check spam if there are no other errors
283
            return;
284
        }
285
286
		if ( self::is_honeypot_spam() || self::is_spam_bot() ) {
287
			$errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
288
		}
289
290
    	if ( self::blacklist_check( $values ) ) {
291
            $errors['spam'] = __( 'Your entry appears to be blacklist spam!', 'formidable' );
292
    	}
293
294
        if ( self::is_akismet_spam( $values ) ) {
295
			if ( self::is_akismet_enabled_for_user( $values['form_id'] ) ) {
296
				$errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
297
			}
298
	    }
299
    }
300
301
	private static function is_honeypot_spam() {
302
		$honeypot_value = FrmAppHelper::get_param( 'frm_verify', '', 'get', 'sanitize_text_field' );
303
		return ( $honeypot_value !== '' );
304
	}
305
306
	private static function is_spam_bot() {
307
		$ip = FrmAppHelper::get_ip_address();
308
		return empty( $ip );
309
	}
310
311
	private static function is_akismet_spam( $values ) {
312
		global $wpcom_api_key;
313
		return ( is_callable('Akismet::http_post') && ( get_option('wordpress_api_key') || $wpcom_api_key ) && self::akismet( $values ) );
314
	}
315
316
	private static function is_akismet_enabled_for_user( $form_id ) {
317
		$form = FrmForm::getOne( $form_id );
318
		return ( isset( $form->options['akismet'] ) && ! empty( $form->options['akismet'] ) && ( $form->options['akismet'] != 'logged' || ! is_user_logged_in() ) );
319
	}
320
321
    public static function blacklist_check( $values ) {
322
        if ( ! apply_filters('frm_check_blacklist', true, $values) ) {
323
            return false;
324
        }
325
326
    	$mod_keys = trim( get_option( 'blacklist_keys' ) );
327
    	if ( empty( $mod_keys ) ) {
328
    		return false;
329
    	}
330
331
		$content = FrmEntriesHelper::entry_array_to_string( $values );
332
		if ( empty( $content ) ) {
333
			return false;
334
		}
335
336
		$ip = FrmAppHelper::get_ip_address();
337
		$user_agent = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
338
		$user_info = self::get_spam_check_user_info( $values );
339
340
		return wp_blacklist_check( $user_info['comment_author'], $user_info['comment_author_email'], $user_info['comment_author_url'], $content, $ip, $user_agent );
341
    }
342
343
	/**
344
	 * Check entries for Akismet spam
345
	 *
346
	 * @return boolean true if is spam
347
	 */
348
	public static function akismet( $values ) {
349
		$content = FrmEntriesHelper::entry_array_to_string( $values );
350
		if ( empty( $content ) ) {
351
			return false;
352
		}
353
354
		$datas = array( 'comment_type' => 'formidable', 'comment_content' => $content );
355
		self::parse_akismet_array( $datas, $values );
356
357
		$query_string = _http_build_query( $datas, '', '&' );
358
		$response = Akismet::http_post( $query_string, 'comment-check' );
359
360
		return ( is_array( $response ) && $response[1] == 'true' );
361
	}
362
363
	/**
364
	 * @since 2.0
365
	 */
366
	private  static function parse_akismet_array( &$datas, $values ) {
367
		self::add_site_info_to_akismet( $datas );
368
		self::add_user_info_to_akismet( $datas, $values );
369
		self::add_server_values_to_akismet( $datas );
370
	}
371
372
	private static function add_site_info_to_akismet( &$datas ) {
373
		$datas['blog'] = FrmAppHelper::site_url();
374
		$datas['user_ip'] = preg_replace( '/[^0-9., ]/', '', FrmAppHelper::get_ip_address() );
375
		$datas['user_agent'] = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
376
		$datas['referrer'] = isset( $_SERVER['HTTP_REFERER'] ) ? FrmAppHelper::get_server_value( 'HTTP_REFERER' ) : false;
377
		$datas['blog_lang'] = get_locale();
378
		$datas['blog_charset'] = get_option('blog_charset');
379
380
		if ( akismet_test_mode() ) {
381
			$datas['is_test'] = 'true';
382
		}
383
	}
384
385
	private static function add_user_info_to_akismet( &$datas, $values ) {
386
		$user_info = self::get_spam_check_user_info( $values );
387
		$datas = $datas + $user_info;
388
389
		if ( isset( $user_info['user_ID'] ) ) {
390
			$datas['user_role'] = Akismet::get_user_roles( $user_info['user_ID'] );
391
		}
392
	}
393
394
	private static function get_spam_check_user_info( $values ) {
395
		$datas = array();
396
397
		if ( is_user_logged_in() ) {
398
			$user = wp_get_current_user();
399
			$datas['user_ID'] = $user->ID;
400
			$datas['user_id'] = $user->ID;
401
			$datas['comment_author'] = $user->display_name;
402
			$datas['comment_author_email'] = $user->user_email;
403
			$datas['comment_author_url'] = $user->user_url;
404
		} else {
405
			$datas['comment_author'] = '';
406
			$datas['comment_author_email'] = '';
407
			$datas['comment_author_url'] = '';
408
409
			$values = array_filter( $values );
410
			foreach ( $values as $value ) {
411
				if ( ! is_array( $value ) ) {
412
					if ( $datas['comment_author_email'] == '' && strpos( $value, '@' ) && is_email( $value ) ) {
413
						$datas['comment_author_email'] = $value;
414
					} elseif ( $datas['comment_author_url'] == '' && strpos( $value, 'http' ) === 0 ) {
415
						$datas['comment_author_url'] = $value;
416
					} elseif ( $datas['comment_author'] == '' && ! is_numeric( $value ) && strlen( $value ) < 200 ) {
417
						$datas['comment_author'] = $value;
418
					}
419
				}
420
			}
421
		}
422
423
		return $datas;
424
	}
425
426
	private static function add_server_values_to_akismet( &$datas ) {
427
		foreach ( $_SERVER as $key => $value ) {
428
			$include_value = is_string( $value ) && ! preg_match( '/^HTTP_COOKIE/', $key ) && preg_match( '/^(HTTP_|REMOTE_ADDR|REQUEST_URI|DOCUMENT_URI)/', $key );
429
430
			// Send any potentially useful $_SERVER vars, but avoid sending junk we don't need.
431
			if ( $include_value ) {
432
				$datas[ $key ] = $value;
433
			}
434
			unset( $key, $value );
435
		}
436
	}
437
}
438