Completed
Push — master ( 18e890...cbbb72 )
by Stephanie
03:12
created

FrmEntryValidate::spam_check()   B

Complexity

Conditions 9
Paths 13

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 10
nc 13
nop 3
dl 0
loc 20
rs 7.756
c 0
b 0
f 0
1
<?php
2
3
class FrmEntryValidate {
4
    public static function validate( $values, $exclude = false ) {
5
        global $wpdb;
6
7
        FrmEntry::sanitize_entry_post( $values );
8
        $errors = array();
9
10
        if ( ! isset($values['form_id']) || ! isset($values['item_meta']) ) {
11
            $errors['form'] = __( 'There was a problem with your submission. Please try again.', 'formidable' );
12
            return $errors;
13
        }
14
15
		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' ) ) ) {
16
            $errors['form'] = __( 'You do not have permission to do that', 'formidable' );
17
        }
18
19
		if ( ! isset( $values['item_key'] ) || $values['item_key'] == '' ) {
20
			$values['item_key'] = FrmAppHelper::get_unique_key( '', $wpdb->prefix . 'frm_items', 'item_key' );
21
			$_POST['item_key'] = $values['item_key'];
22
		}
23
24
        $where = apply_filters('frm_posted_field_ids', array( 'fi.form_id' => $values['form_id'] ) );
25
		// Don't get subfields
26
		$where['fr.parent_form_id'] = array( null, 0 );
27
		// Don't get excluded fields (like file upload fields in the ajax validation)
28
		if ( ! empty( $exclude ) ) {
29
			$where['fi.type not'] = $exclude;
30
		}
31
32
        $posted_fields = FrmField::getAll($where, 'field_order');
33
34
        // Pass exclude value to validate_field function so it can be used for repeating sections
35
        $args = array( 'exclude' => $exclude );
36
37
        foreach ( $posted_fields as $posted_field ) {
38
            self::validate_field($posted_field, $errors, $values, $args);
39
            unset($posted_field);
40
        }
41
42
        // check for spam
43
        self::spam_check( $exclude, $values, $errors );
44
45
        $errors = apply_filters( 'frm_validate_entry', $errors, $values, compact('exclude') );
46
47
        return $errors;
48
    }
49
50
    public static function validate_field( $posted_field, &$errors, $values, $args = array() ) {
51
        $defaults = array(
52
            'id'              => $posted_field->id,
53
            'parent_field_id' => '', // the id of the repeat or embed form
54
            'key_pointer'     => '', // the pointer in the posted array
55
            'exclude'         => array(), // exclude these field types from validation
56
        );
57
        $args = wp_parse_args( $args, $defaults );
58
59
        if ( empty($args['parent_field_id']) ) {
60
			$value = isset( $values['item_meta'][ $args['id'] ] ) ? $values['item_meta'][ $args['id'] ] : '';
61
        } else {
62
            // value is from a nested form
63
            $value = $values;
64
        }
65
66
        // Check for values in "Other" fields
67
        FrmEntriesHelper::maybe_set_other_validation( $posted_field, $value, $args );
68
69
		self::maybe_clear_value_for_default_blank_setting( $posted_field, $value );
70
71
		// Reset arrays with only one value if it's not a field where array keys need to be preserved
72 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...
73
			$value = reset($value);
74
		}
75
76
        if ( $posted_field->required == '1' && ! is_array( $value ) && trim( $value ) == '' ) {
77
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $posted_field, 'blank' );
78
        } else if ( $posted_field->type == 'text' && ! isset( $_POST['item_name'] ) ) {
79
            $_POST['item_name'] = $value;
80
        }
81
82
		if ( $value != '' ) {
83
			self::validate_url_field( $errors, $posted_field, $value, $args );
84
			self::validate_email_field( $errors, $posted_field, $value, $args );
85
			self::validate_number_field( $errors, $posted_field, $value, $args );
86
			self::validate_phone_field( $errors, $posted_field, $value, $args );
87
		}
88
89
        FrmEntriesHelper::set_posted_value($posted_field, $value, $args);
90
91
        self::validate_recaptcha($errors, $posted_field, $args);
92
93
		$errors = apply_filters( 'frm_validate_' . $posted_field->type . '_field_entry', $errors, $posted_field, $value, $args );
94
		$errors = apply_filters( 'frm_validate_field_entry', $errors, $posted_field, $value, $args );
95
    }
96
97
	private static function maybe_clear_value_for_default_blank_setting( $field, &$value ) {
98
		if ( FrmField::is_option_true_in_object( $field, 'default_blank' ) && $value == $field->default_value ) {
99
			$value = '';
100
		}
101
	}
102
103
	public static function validate_url_field( &$errors, $field, &$value, $args ) {
104
		if ( $value == '' || ! in_array( $field->type, array( 'website', 'url', 'image' ) ) ) {
105
            return;
106
        }
107
108
        if ( trim($value) == 'http://' ) {
109
            $value = '';
110
        } else {
111
            $value = esc_url_raw( $value );
112
			$value = preg_match( '/^(https?|ftps?|mailto|news|feed|telnet):/is', $value ) ? $value : 'http://' . $value;
113
        }
114
115
        // validate the url format
116 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...
117
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
118
		}
119
    }
120
121
	public static function validate_email_field( &$errors, $field, $value, $args ) {
122
        if ( $value == '' || $field->type != 'email' ) {
123
            return;
124
        }
125
126
        //validate the email format
127 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...
128
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
129
        }
130
    }
131
132
	public static function validate_number_field( &$errors, $field, $value, $args ) {
133
		//validate the number format
134
		if ( $field->type != 'number' ) {
135
			return;
136
		}
137
138
		if ( ! is_numeric( $value) ) {
139
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
140
		}
141
142
		// validate number settings
143
		if ( $value != '' ) {
144
			$frm_settings = FrmAppHelper::get_settings();
145
			// only check if options are available in settings
146
			if ( $frm_settings->use_html && isset( $field->field_options['minnum'] ) && isset( $field->field_options['maxnum'] ) ) {
147
				//minnum maxnum
148
				if ( (float) $value < $field->field_options['minnum'] ) {
149
					$errors[ 'field' . $args['id'] ] = __( 'Please select a higher number', 'formidable' );
150
				} else if ( (float) $value > $field->field_options['maxnum'] ) {
151
					$errors[ 'field' . $args['id'] ] = __( 'Please select a lower number', 'formidable' );
152
				}
153
			}
154
		}
155
	}
156
157
	public static function validate_phone_field( &$errors, $field, $value, $args ) {
158
		if ( $field->type == 'phone' || ( $field->type == 'text' && FrmField::is_option_true_in_object( $field, 'format' ) ) ) {
159
160
			$pattern = self::phone_format( $field );
161
162 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...
163
				$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
164
			}
165
		}
166
	}
167
168
	public static function phone_format( $field ) {
169
		$default_format = '^((\+\d{1,3}(-|.| )?\(?\d\)?(-| |.)?\d{1,5})|(\(?\d{2,6}\)?))(-|.| )?(\d{3,4})(-|.| )?(\d{4})(( x| ext)\d{1,5}){0,1}$';
170
		if ( FrmField::is_option_empty( $field, 'format' ) ) {
171
			$pattern = $default_format;
172
		} else {
173
			$pattern = FrmField::get_option( $field, 'format' );
174
		}
175
176
		$pattern = apply_filters( 'frm_phone_pattern', $pattern, $field );
177
178
		// Create a regexp if format is not already a regexp
179
		if ( strpos( $pattern, '^' ) !== 0 ) {
180
			$pattern = self::create_regular_expression_from_format( $pattern );
181
		}
182
183
		$pattern = '/' . $pattern . '/';
184
		return $pattern;
185
	}
186
187
	/**
188
	 * Create a regular expression from a phone number format
189
	 *
190
	 * @since 2.02.02
191
	 * @param string $pattern
192
	 * @return string
193
	 */
194
	private static function create_regular_expression_from_format( $pattern ) {
195
		$pattern = preg_quote( $pattern );
196
197
		// Firefox doesn't like escaped dashes or colons
198
		$pattern = str_replace( array( '\-', '\:' ), array( '-', ':' ), $pattern );
199
200
		// Switch generic values out for their regular expression
201
		$pattern = preg_replace( '/\d/', '\d', $pattern );
202
		$pattern = str_replace( 'a', '[a-z]', $pattern );
203
		$pattern = str_replace( 'A', '[A-Z]', $pattern );
204
		$pattern = str_replace( '*', 'w', $pattern );
205
		$pattern = str_replace( '/', '\/', $pattern );
206
207
		if ( strpos( $pattern, '\?' ) !== false ) {
208
			$parts = explode( '\?', $pattern );
209
			$pattern = '';
210
			foreach ( $parts as $part ) {
211
				if ( empty( $pattern ) ) {
212
					$pattern .= $part;
213
				} else {
214
					$pattern .= '(' . $part . ')?';
215
				}
216
			}
217
		}
218
		$pattern = '^' . $pattern . '$';
219
220
		return $pattern;
221
	}
222
223
	public static function validate_recaptcha( &$errors, $field, $args ) {
224
        if ( $field->type != 'captcha' || FrmAppHelper::is_admin() || apply_filters( 'frm_is_field_hidden', false, $field, stripslashes_deep( $_POST ) ) ) {
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
225
            return;
226
        }
227
228
		$frm_settings = FrmAppHelper::get_settings();
229
		if ( empty( $frm_settings->pubkey ) ) {
230
			// don't require the captcha if it shouldn't be shown
231
			return;
232
		}
233
234
        if ( ! isset($_POST['g-recaptcha-response']) ) {
235
            // If captcha is missing, check if it was already verified
236
			if ( ! isset( $_POST['recaptcha_checked'] ) || ! wp_verify_nonce( $_POST['recaptcha_checked'], 'frm_ajax' ) ) {
237
                // There was no captcha submitted
238
				$errors[ 'field' . $args['id'] ] = __( 'The captcha is missing from this form', 'formidable' );
239
            }
240
            return;
241
        }
242
243
        $arg_array = array(
244
            'body'      => array(
245
				'secret'   => $frm_settings->privkey,
246
				'response' => $_POST['g-recaptcha-response'],
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
247
				'remoteip' => FrmAppHelper::get_ip_address(),
248
			),
249
		);
250
        $resp = wp_remote_post( 'https://www.google.com/recaptcha/api/siteverify', $arg_array );
251
        $response = json_decode(wp_remote_retrieve_body( $resp ), true);
252
253
        if ( isset( $response['success'] ) && ! $response['success'] ) {
254
            // What happens when the CAPTCHA was entered incorrectly
255
			$errors[ 'field' . $args['id'] ] = ( ! isset( $field->field_options['invalid'] ) || $field->field_options['invalid'] == '' ) ? $frm_settings->re_msg : $field->field_options['invalid'];
256
        } else if ( is_wp_error( $resp ) ) {
257
			$error_string = $resp->get_error_message();
258
			$errors[ 'field' . $args['id'] ] = __( 'There was a problem verifying your recaptcha', 'formidable' );
259
			$errors[ 'field' . $args['id'] ] .= ' ' . $error_string;
260
        }
261
    }
262
263
    /**
264
     * check for spam
265
     * @param boolean $exclude
266
     * @param array $values
267
     * @param array $errors by reference
268
     */
269
    public static function spam_check( $exclude, $values, &$errors ) {
270
        if ( ! empty( $exclude ) || ! isset( $values['item_meta'] ) || empty( $values['item_meta'] ) || ! empty( $errors ) ) {
271
            // only check spam if there are no other errors
272
            return;
273
        }
274
275
		if ( self::is_honeypot_spam() ) {
276
			$errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
277
		}
278
279
    	if ( self::blacklist_check( $values ) ) {
280
            $errors['spam'] = __( 'Your entry appears to be blacklist spam!', 'formidable' );
281
    	}
282
283
        if ( self::is_akismet_spam( $values ) ) {
284
			if ( self::is_akismet_enabled_for_user( $values['form_id'] ) ) {
285
				$errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
286
			}
287
	    }
288
    }
289
290
	private static function is_honeypot_spam() {
291
		$honeypot_value = FrmAppHelper::get_param( 'frm_verify', '', 'get', 'sanitize_text_field' );
292
		return ( $honeypot_value !== '' );
293
	}
294
295
	private static function is_akismet_spam( $values ) {
296
		global $wpcom_api_key;
297
		return ( is_callable('Akismet::http_post') && ( get_option('wordpress_api_key') || $wpcom_api_key ) && self::akismet( $values ) );
298
	}
299
300
	private static function is_akismet_enabled_for_user( $form_id ) {
301
		$form = FrmForm::getOne( $form_id );
302
		return ( isset( $form->options['akismet'] ) && ! empty( $form->options['akismet'] ) && ( $form->options['akismet'] != 'logged' || ! is_user_logged_in() ) );
303
	}
304
305
    public static function blacklist_check( $values ) {
306
        if ( ! apply_filters('frm_check_blacklist', true, $values) ) {
307
            return false;
308
        }
309
310
    	$mod_keys = trim( get_option( 'blacklist_keys' ) );
311
    	if ( empty( $mod_keys ) ) {
312
    		return false;
313
    	}
314
315
		$content = FrmEntriesHelper::entry_array_to_string( $values );
316
		if ( empty( $content ) ) {
317
			return false;
318
		}
319
320
		$ip = FrmAppHelper::get_ip_address();
321
		$user_agent = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
322
		$user_info = self::get_spam_check_user_info( $values );
323
324
		return wp_blacklist_check( $user_info['comment_author'], $user_info['comment_author_email'], $user_info['comment_author_url'], $content, $ip, $user_agent );
325
    }
326
327
	/**
328
	 * Check entries for Akismet spam
329
	 *
330
	 * @return boolean true if is spam
331
	 */
332
	public static function akismet( $values ) {
333
		$content = FrmEntriesHelper::entry_array_to_string( $values );
334
		if ( empty( $content ) ) {
335
			return false;
336
		}
337
338
		$datas = array( 'comment_type' => 'formidable', 'comment_content' => $content );
339
		self::parse_akismet_array( $datas, $values );
340
341
		$query_string = _http_build_query( $datas, '', '&' );
342
		$response = Akismet::http_post( $query_string, 'comment-check' );
343
344
		return ( is_array( $response ) && $response[1] == 'true' );
345
	}
346
347
	/**
348
	 * @since 2.0
349
	 */
350
	private  static function parse_akismet_array( &$datas, $values ) {
351
		self::add_site_info_to_akismet( $datas );
352
		self::add_user_info_to_akismet( $datas, $values );
353
		self::add_server_values_to_akismet( $datas );
354
	}
355
356
	private static function add_site_info_to_akismet( &$datas ) {
357
		$datas['blog'] = FrmAppHelper::site_url();
358
		$datas['user_ip'] = preg_replace( '/[^0-9., ]/', '', FrmAppHelper::get_ip_address() );
359
		$datas['user_agent'] = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
360
		$datas['referrer'] = isset( $_SERVER['HTTP_REFERER'] ) ? FrmAppHelper::get_server_value( 'HTTP_REFERER' ) : false;
361
		$datas['blog_lang'] = get_locale();
362
		$datas['blog_charset'] = get_option('blog_charset');
363
364
		if ( akismet_test_mode() ) {
365
			$datas['is_test'] = 'true';
366
		}
367
	}
368
369
	private static function add_user_info_to_akismet( &$datas, $values ) {
370
		$user_info = self::get_spam_check_user_info( $values );
371
		$datas = $datas + $user_info;
372
373
		if ( isset( $user_info['user_ID'] ) ) {
374
			$datas['user_role'] = Akismet::get_user_roles( $user_info['user_ID'] );
375
		}
376
	}
377
378
	private static function get_spam_check_user_info( $values ) {
379
		$datas = array();
380
381
		if ( is_user_logged_in() ) {
382
			$user = wp_get_current_user();
383
			$datas['user_ID'] = $user->ID;
384
			$datas['user_id'] = $user->ID;
385
			$datas['comment_author'] = $user->display_name;
386
			$datas['comment_author_email'] = $user->user_email;
387
			$datas['comment_author_url'] = $user->user_url;
388
		} else {
389
			$datas['comment_author'] = '';
390
			$datas['comment_author_email'] = '';
391
			$datas['comment_author_url'] = '';
392
393
			$values = array_filter( $values );
394
			foreach ( $values as $value ) {
395
				if ( ! is_array( $value ) ) {
396
					if ( $datas['comment_author_email'] == '' && strpos( $value, '@' ) && is_email( $value ) ) {
397
						$datas['comment_author_email'] = $value;
398
					} elseif ( $datas['comment_author_url'] == '' && strpos( $value, 'http' ) === 0 ) {
399
						$datas['comment_author_url'] = $value;
400
					} elseif ( $datas['comment_author'] == '' && ! is_numeric( $value ) && strlen( $value ) < 200 ) {
401
						$datas['comment_author'] = $value;
402
					}
403
				}
404
			}
405
		}
406
407
		return $datas;
408
	}
409
410
	private static function add_server_values_to_akismet( &$datas ) {
411
		foreach ( $_SERVER as $key => $value ) {
412
			$include_value = is_string( $value ) && ! preg_match( '/^HTTP_COOKIE/', $key ) && preg_match( '/^(HTTP_|REMOTE_ADDR|REQUEST_URI|DOCUMENT_URI)/', $key );
413
414
			// Send any potentially useful $_SERVER vars, but avoid sending junk we don't need.
415
			if ( $include_value ) {
416
				$datas[ $key ] = $value;
417
			}
418
			unset( $key, $value );
419
		}
420
	}
421
}
422