Completed
Push — master ( cbbb72...0e27d3 )
by Stephanie
03:08
created

FrmEntryValidate   D

Complexity

Total Complexity 120

Size/Duplication

Total Lines 424
Duplicated Lines 2.83 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
dl 12
loc 424
rs 4.8717
c 0
b 0
f 0
wmc 120
lcom 1
cbo 7

22 Methods

Rating   Name   Duplication   Size   Complexity  
C validate_recaptcha() 0 39 13
C validate() 0 45 11
C validate_field() 3 46 12
A maybe_clear_value_for_default_blank_setting() 0 5 3
B validate_url_field() 3 17 6
A validate_email_field() 3 10 4
C validate_number_field() 0 24 9
B validate_phone_field() 3 10 5
A phone_format() 0 18 3
B create_regular_expression_from_format() 0 28 4
A is_honeypot_spam() 0 4 1
B spam_check() 0 20 10
A is_spam_bot() 0 4 1
A is_akismet_spam() 0 4 4
A is_akismet_enabled_for_user() 0 4 4
A blacklist_check() 0 21 4
A akismet() 0 14 3
A parse_akismet_array() 0 5 1
A add_site_info_to_akismet() 0 12 3
A add_user_info_to_akismet() 0 8 2
C get_spam_check_user_info() 0 31 12
B add_server_values_to_akismet() 0 11 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like FrmEntryValidate often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FrmEntryValidate, and based on these observations, apply Extract Interface, too.

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() || self::is_spam_bot() ) {
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_spam_bot() {
296
		$ip = FrmAppHelper::get_ip_address();
297
		return empty( $ip );
298
	}
299
300
	private static function is_akismet_spam( $values ) {
301
		global $wpcom_api_key;
302
		return ( is_callable('Akismet::http_post') && ( get_option('wordpress_api_key') || $wpcom_api_key ) && self::akismet( $values ) );
303
	}
304
305
	private static function is_akismet_enabled_for_user( $form_id ) {
306
		$form = FrmForm::getOne( $form_id );
307
		return ( isset( $form->options['akismet'] ) && ! empty( $form->options['akismet'] ) && ( $form->options['akismet'] != 'logged' || ! is_user_logged_in() ) );
308
	}
309
310
    public static function blacklist_check( $values ) {
311
        if ( ! apply_filters('frm_check_blacklist', true, $values) ) {
312
            return false;
313
        }
314
315
    	$mod_keys = trim( get_option( 'blacklist_keys' ) );
316
    	if ( empty( $mod_keys ) ) {
317
    		return false;
318
    	}
319
320
		$content = FrmEntriesHelper::entry_array_to_string( $values );
321
		if ( empty( $content ) ) {
322
			return false;
323
		}
324
325
		$ip = FrmAppHelper::get_ip_address();
326
		$user_agent = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
327
		$user_info = self::get_spam_check_user_info( $values );
328
329
		return wp_blacklist_check( $user_info['comment_author'], $user_info['comment_author_email'], $user_info['comment_author_url'], $content, $ip, $user_agent );
330
    }
331
332
	/**
333
	 * Check entries for Akismet spam
334
	 *
335
	 * @return boolean true if is spam
336
	 */
337
	public static function akismet( $values ) {
338
		$content = FrmEntriesHelper::entry_array_to_string( $values );
339
		if ( empty( $content ) ) {
340
			return false;
341
		}
342
343
		$datas = array( 'comment_type' => 'formidable', 'comment_content' => $content );
344
		self::parse_akismet_array( $datas, $values );
345
346
		$query_string = _http_build_query( $datas, '', '&' );
347
		$response = Akismet::http_post( $query_string, 'comment-check' );
348
349
		return ( is_array( $response ) && $response[1] == 'true' );
350
	}
351
352
	/**
353
	 * @since 2.0
354
	 */
355
	private  static function parse_akismet_array( &$datas, $values ) {
356
		self::add_site_info_to_akismet( $datas );
357
		self::add_user_info_to_akismet( $datas, $values );
358
		self::add_server_values_to_akismet( $datas );
359
	}
360
361
	private static function add_site_info_to_akismet( &$datas ) {
362
		$datas['blog'] = FrmAppHelper::site_url();
363
		$datas['user_ip'] = preg_replace( '/[^0-9., ]/', '', FrmAppHelper::get_ip_address() );
364
		$datas['user_agent'] = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
365
		$datas['referrer'] = isset( $_SERVER['HTTP_REFERER'] ) ? FrmAppHelper::get_server_value( 'HTTP_REFERER' ) : false;
366
		$datas['blog_lang'] = get_locale();
367
		$datas['blog_charset'] = get_option('blog_charset');
368
369
		if ( akismet_test_mode() ) {
370
			$datas['is_test'] = 'true';
371
		}
372
	}
373
374
	private static function add_user_info_to_akismet( &$datas, $values ) {
375
		$user_info = self::get_spam_check_user_info( $values );
376
		$datas = $datas + $user_info;
377
378
		if ( isset( $user_info['user_ID'] ) ) {
379
			$datas['user_role'] = Akismet::get_user_roles( $user_info['user_ID'] );
380
		}
381
	}
382
383
	private static function get_spam_check_user_info( $values ) {
384
		$datas = array();
385
386
		if ( is_user_logged_in() ) {
387
			$user = wp_get_current_user();
388
			$datas['user_ID'] = $user->ID;
389
			$datas['user_id'] = $user->ID;
390
			$datas['comment_author'] = $user->display_name;
391
			$datas['comment_author_email'] = $user->user_email;
392
			$datas['comment_author_url'] = $user->user_url;
393
		} else {
394
			$datas['comment_author'] = '';
395
			$datas['comment_author_email'] = '';
396
			$datas['comment_author_url'] = '';
397
398
			$values = array_filter( $values );
399
			foreach ( $values as $value ) {
400
				if ( ! is_array( $value ) ) {
401
					if ( $datas['comment_author_email'] == '' && strpos( $value, '@' ) && is_email( $value ) ) {
402
						$datas['comment_author_email'] = $value;
403
					} elseif ( $datas['comment_author_url'] == '' && strpos( $value, 'http' ) === 0 ) {
404
						$datas['comment_author_url'] = $value;
405
					} elseif ( $datas['comment_author'] == '' && ! is_numeric( $value ) && strlen( $value ) < 200 ) {
406
						$datas['comment_author'] = $value;
407
					}
408
				}
409
			}
410
		}
411
412
		return $datas;
413
	}
414
415
	private static function add_server_values_to_akismet( &$datas ) {
416
		foreach ( $_SERVER as $key => $value ) {
417
			$include_value = is_string( $value ) && ! preg_match( '/^HTTP_COOKIE/', $key ) && preg_match( '/^(HTTP_|REMOTE_ADDR|REQUEST_URI|DOCUMENT_URI)/', $key );
418
419
			// Send any potentially useful $_SERVER vars, but avoid sending junk we don't need.
420
			if ( $include_value ) {
421
				$datas[ $key ] = $value;
422
			}
423
			unset( $key, $value );
424
		}
425
	}
426
}
427