Completed
Push — master ( 84bf61...3e58cb )
by Stephanie
03:20
created

FrmEntryValidate::spam_check()   B

Complexity

Conditions 10
Paths 13

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 10
nc 13
nop 3
dl 0
loc 20
rs 7.2765
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
		foreach ( $posted_fields as $posted_field ) {
25
			self::validate_field( $posted_field, $errors, $values, $args );
26
			unset( $posted_field );
27
		}
28
29
		if ( empty( $errors ) ) {
30
			self::spam_check( $exclude, $values, $errors );
31
		}
32
33
		$errors = apply_filters( 'frm_validate_entry', $errors, $values, compact( 'exclude' ) );
34
35
		return $errors;
36
	}
37
38
	private static function set_item_key( &$values ) {
39
		if ( ! isset( $values['item_key'] ) || $values['item_key'] == '' ) {
40
			global $wpdb;
41
			$values['item_key'] = FrmAppHelper::get_unique_key( '', $wpdb->prefix . 'frm_items', 'item_key' );
42
			$_POST['item_key'] = $values['item_key'];
43
		}
44
	}
45
46
	private static function get_fields_to_validate( $values, $exclude ) {
47
		$where = apply_filters( 'frm_posted_field_ids', array( 'fi.form_id' => $values['form_id'] ) );
48
49
		// Don't get subfields
50
		$where['fr.parent_form_id'] = array( null, 0 );
51
52
		// Don't get excluded fields (like file upload fields in the ajax validation)
53
		if ( ! empty( $exclude ) ) {
54
			$where['fi.type not'] = $exclude;
55
		}
56
57
		return FrmField::getAll( $where, 'field_order' );
58
	}
59
60
    public static function validate_field( $posted_field, &$errors, $values, $args = array() ) {
61
        $defaults = array(
62
            'id'              => $posted_field->id,
63
            'parent_field_id' => '', // the id of the repeat or embed form
64
            'key_pointer'     => '', // the pointer in the posted array
65
            'exclude'         => array(), // exclude these field types from validation
66
        );
67
        $args = wp_parse_args( $args, $defaults );
68
69
        if ( empty($args['parent_field_id']) ) {
70
			$value = isset( $values['item_meta'][ $args['id'] ] ) ? $values['item_meta'][ $args['id'] ] : '';
71
        } else {
72
            // value is from a nested form
73
            $value = $values;
74
        }
75
76
        // Check for values in "Other" fields
77
        FrmEntriesHelper::maybe_set_other_validation( $posted_field, $value, $args );
78
79
		self::maybe_clear_value_for_default_blank_setting( $posted_field, $value );
80
81
		// Reset arrays with only one value if it's not a field where array keys need to be preserved
82 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...
83
			$value = reset($value);
84
		}
85
86
		if ( ! is_array( $value ) ) {
87
			$value = trim( $value );
88
		}
89
90
        if ( $posted_field->required == '1' && ! is_array( $value ) && trim( $value ) == '' ) {
91
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $posted_field, 'blank' );
92
        } else if ( $posted_field->type == 'text' && ! isset( $_POST['item_name'] ) ) {
93
            $_POST['item_name'] = $value;
94
        }
95
96
		FrmEntriesHelper::set_posted_value( $posted_field, $value, $args );
97
98
		self::validate_field_types( $errors, $posted_field, $value, $args );
99
100
		if ( $value != '' ) {
101
			self::validate_phone_field( $errors, $posted_field, $value, $args );
102
		}
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
		$is_default = ( FrmField::is_option_true_in_object( $field, 'default_blank' ) && $value == $field->default_value );
110
		$is_label = false;
111
112
		if ( ! $is_default ) {
113
			$position = FrmField::get_option( $field, 'label' );
114
			if ( empty( $position ) ) {
115
				$position = FrmStylesController::get_style_val( 'position', $field->form_id );
116
			}
117
118
			$is_label = ( $position == 'inside' && FrmFieldsHelper::is_placeholder_field_type( $field->type ) && $value == $field->name );
119
		}
120
121
		if ( $is_label || $is_default ) {
122
			$value = '';
123
		}
124
	}
125
126
	public static function validate_field_types( &$errors, $posted_field, $value, $args ) {
127
		$field_obj = FrmFieldFactory::get_field_object( $posted_field );
128
		$args['value'] = $value;
129
		$args['errors'] = $errors;
130
131
		$new_errors = $field_obj->validate( $args );
132
		if ( ! empty( $new_errors ) ) {
133
			$errors = array_merge( $errors, $new_errors );
134
		}
135
	}
136
137
	public static function validate_url_field( &$errors, $field, $value, $args ) {
138
		_deprecated_function( __FUNCTION__, '3.0', 'FrmFieldType::validate' );
139
140
		if ( $value == '' || ! in_array( $field->type, array( 'website', 'url' ) ) ) {
141
			return;
142
		}
143
144
		self::validate_field_types( $errors, $field, $value, $args );
145
	}
146
147 View Code Duplication
	public static function validate_email_field( &$errors, $field, $value, $args ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
148
		_deprecated_function( __FUNCTION__, '3.0', 'FrmFieldType::validate' );
149
150
		if ( $field->type != 'email' ) {
151
			return;
152
		}
153
154
		self::validate_field_types( $errors, $field, $value, $args );
155
	}
156
157 View Code Duplication
	public static function validate_number_field( &$errors, $field, $value, $args ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
158
		_deprecated_function( __FUNCTION__, '3.0', 'FrmFieldType::validate' );
159
160
		//validate the number format
161
		if ( $field->type != 'number' ) {
162
			return;
163
		}
164
165
		self::validate_field_types( $errors, $field, $value, $args );
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
			if ( ! preg_match( $pattern, $value ) ) {
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
		_deprecated_function( __FUNCTION__, '3.0', 'FrmFieldType::validate' );
236
237
		if ( $field->type != 'captcha' ) {
238
			return;
239
		}
240
241
		self::validate_field_types( $errors, $field, $value, $args );
0 ignored issues
show
Bug introduced by
The variable $value does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
242
    }
243
244
    /**
245
     * check for spam
246
     * @param boolean $exclude
247
     * @param array $values
248
     * @param array $errors by reference
249
     */
250
    public static function spam_check( $exclude, $values, &$errors ) {
251
        if ( ! empty( $exclude ) || ! isset( $values['item_meta'] ) || empty( $values['item_meta'] ) || ! empty( $errors ) ) {
252
            // only check spam if there are no other errors
253
            return;
254
        }
255
256
		if ( self::is_honeypot_spam() || self::is_spam_bot() ) {
257
			$errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
258
		}
259
260
    	if ( self::blacklist_check( $values ) ) {
261
            $errors['spam'] = __( 'Your entry appears to be blacklist spam!', 'formidable' );
262
    	}
263
264
        if ( self::is_akismet_spam( $values ) ) {
265
			if ( self::is_akismet_enabled_for_user( $values['form_id'] ) ) {
266
				$errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
267
			}
268
	    }
269
    }
270
271
	private static function is_honeypot_spam() {
272
		$honeypot_value = FrmAppHelper::get_param( 'frm_verify', '', 'get', 'sanitize_text_field' );
273
		return ( $honeypot_value !== '' );
274
	}
275
276
	private static function is_spam_bot() {
277
		$ip = FrmAppHelper::get_ip_address();
278
		return empty( $ip );
279
	}
280
281
	private static function is_akismet_spam( $values ) {
282
		global $wpcom_api_key;
283
		return ( is_callable('Akismet::http_post') && ( get_option('wordpress_api_key') || $wpcom_api_key ) && self::akismet( $values ) );
284
	}
285
286
	private static function is_akismet_enabled_for_user( $form_id ) {
287
		$form = FrmForm::getOne( $form_id );
288
		return ( isset( $form->options['akismet'] ) && ! empty( $form->options['akismet'] ) && ( $form->options['akismet'] != 'logged' || ! is_user_logged_in() ) );
289
	}
290
291
    public static function blacklist_check( $values ) {
292
        if ( ! apply_filters('frm_check_blacklist', true, $values) ) {
293
            return false;
294
        }
295
296
    	$mod_keys = trim( get_option( 'blacklist_keys' ) );
297
    	if ( empty( $mod_keys ) ) {
298
    		return false;
299
    	}
300
301
		$content = FrmEntriesHelper::entry_array_to_string( $values );
302
		if ( empty( $content ) ) {
303
			return false;
304
		}
305
306
		$ip = FrmAppHelper::get_ip_address();
307
		$user_agent = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
308
		$user_info = self::get_spam_check_user_info( $values );
309
310
		return wp_blacklist_check( $user_info['comment_author'], $user_info['comment_author_email'], $user_info['comment_author_url'], $content, $ip, $user_agent );
311
    }
312
313
	/**
314
	 * Check entries for Akismet spam
315
	 *
316
	 * @return boolean true if is spam
317
	 */
318
	public static function akismet( $values ) {
319
		$content = FrmEntriesHelper::entry_array_to_string( $values );
320
		if ( empty( $content ) ) {
321
			return false;
322
		}
323
324
		$datas = array(
325
			'comment_type'    => 'formidable',
326
			'comment_content' => $content,
327
		);
328
		self::parse_akismet_array( $datas, $values );
329
330
		$query_string = _http_build_query( $datas, '', '&' );
331
		$response = Akismet::http_post( $query_string, 'comment-check' );
332
333
		return ( is_array( $response ) && $response[1] == 'true' );
334
	}
335
336
	/**
337
	 * @since 2.0
338
	 */
339
	private  static function parse_akismet_array( &$datas, $values ) {
340
		self::add_site_info_to_akismet( $datas );
341
		self::add_user_info_to_akismet( $datas, $values );
342
		self::add_server_values_to_akismet( $datas );
343
	}
344
345
	private static function add_site_info_to_akismet( &$datas ) {
346
		$datas['blog'] = FrmAppHelper::site_url();
347
		$datas['user_ip'] = preg_replace( '/[^0-9., ]/', '', FrmAppHelper::get_ip_address() );
348
		$datas['user_agent'] = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
349
		$datas['referrer'] = isset( $_SERVER['HTTP_REFERER'] ) ? FrmAppHelper::get_server_value( 'HTTP_REFERER' ) : false;
350
		$datas['blog_lang'] = get_locale();
351
		$datas['blog_charset'] = get_option('blog_charset');
352
353
		if ( akismet_test_mode() ) {
354
			$datas['is_test'] = 'true';
355
		}
356
	}
357
358
	private static function add_user_info_to_akismet( &$datas, $values ) {
359
		$user_info = self::get_spam_check_user_info( $values );
360
		$datas = $datas + $user_info;
361
362
		if ( isset( $user_info['user_ID'] ) ) {
363
			$datas['user_role'] = Akismet::get_user_roles( $user_info['user_ID'] );
364
		}
365
	}
366
367
	private static function get_spam_check_user_info( $values ) {
368
		$datas = array();
369
370
		if ( is_user_logged_in() ) {
371
			$user = wp_get_current_user();
372
			$datas['user_ID'] = $user->ID;
373
			$datas['user_id'] = $user->ID;
374
			$datas['comment_author'] = $user->display_name;
375
			$datas['comment_author_email'] = $user->user_email;
376
			$datas['comment_author_url'] = $user->user_url;
377
		} else {
378
			$datas['comment_author'] = '';
379
			$datas['comment_author_email'] = '';
380
			$datas['comment_author_url'] = '';
381
382
			$values = array_filter( $values );
383
			foreach ( $values as $value ) {
384
				if ( ! is_array( $value ) ) {
385
					if ( $datas['comment_author_email'] == '' && strpos( $value, '@' ) && is_email( $value ) ) {
386
						$datas['comment_author_email'] = $value;
387
					} elseif ( $datas['comment_author_url'] == '' && strpos( $value, 'http' ) === 0 ) {
388
						$datas['comment_author_url'] = $value;
389
					} elseif ( $datas['comment_author'] == '' && ! is_numeric( $value ) && strlen( $value ) < 200 ) {
390
						$datas['comment_author'] = $value;
391
					}
392
				}
393
			}
394
		}
395
396
		return $datas;
397
	}
398
399
	private static function add_server_values_to_akismet( &$datas ) {
400
		foreach ( $_SERVER as $key => $value ) {
401
			$include_value = is_string( $value ) && ! preg_match( '/^HTTP_COOKIE/', $key ) && preg_match( '/^(HTTP_|REMOTE_ADDR|REQUEST_URI|DOCUMENT_URI)/', $key );
402
403
			// Send any potentially useful $_SERVER vars, but avoid sending junk we don't need.
404
			if ( $include_value ) {
405
				$datas[ $key ] = $value;
406
			}
407
			unset( $key, $value );
408
		}
409
	}
410
}
411