Completed
Push — master ( 5a0717...7a4032 )
by Stephanie
02:48
created

FrmEntryValidate::add_server_values_to_akismet()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 6
nc 7
nop 1
dl 0
loc 11
rs 8.8571
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 View Code Duplication
        if ( ! isset($values['item_key']) || $values['item_key'] == '' ) {
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...
20
			$_POST['item_key'] = $values['item_key'] = FrmAppHelper::get_unique_key( '', $wpdb->prefix . 'frm_items', 'item_key' );
21
        }
22
23
        $where = apply_filters('frm_posted_field_ids', array( 'fi.form_id' => $values['form_id'] ) );
24
		// Don't get subfields
25
		$where['fr.parent_form_id'] = array( null, 0 );
26
		// Don't get excluded fields (like file upload fields in the ajax validation)
27
		if ( ! empty( $exclude ) ) {
28
			$where['fi.type not'] = $exclude;
29
		}
30
31
        $posted_fields = FrmField::getAll($where, 'field_order');
32
33
        // Pass exclude value to validate_field function so it can be used for repeating sections
34
        $args = array( 'exclude' => $exclude );
35
36
        foreach ( $posted_fields as $posted_field ) {
37
            self::validate_field($posted_field, $errors, $values, $args);
38
            unset($posted_field);
39
        }
40
41
        // check for spam
42
        self::spam_check( $exclude, $values, $errors );
43
44
        $errors = apply_filters( 'frm_validate_entry', $errors, $values, compact('exclude') );
45
46
        return $errors;
47
    }
48
49
    public static function validate_field( $posted_field, &$errors, $values, $args = array() ) {
50
        $defaults = array(
51
            'id'              => $posted_field->id,
52
            'parent_field_id' => '', // the id of the repeat or embed form
53
            'key_pointer'     => '', // the pointer in the posted array
54
            'exclude'         => array(), // exclude these field types from validation
55
        );
56
        $args = wp_parse_args( $args, $defaults );
57
58
        if ( empty($args['parent_field_id']) ) {
59
			$value = isset( $values['item_meta'][ $args['id'] ] ) ? $values['item_meta'][ $args['id'] ] : '';
60
        } else {
61
            // value is from a nested form
62
            $value = $values;
63
        }
64
65
        // Check for values in "Other" fields
66
        FrmEntriesHelper::maybe_set_other_validation( $posted_field, $value, $args );
67
68
		self::maybe_clear_value_for_default_blank_setting( $posted_field, $value );
69
70
		// Reset arrays with only one value if it's not a field where array keys need to be preserved
71 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...
72
			$value = reset($value);
73
		}
74
75
        if ( $posted_field->required == '1' && ! is_array( $value ) && trim( $value ) == '' ) {
76
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $posted_field, 'blank' );
77
        } else if ( $posted_field->type == 'text' && ! isset( $_POST['item_name'] ) ) {
78
            $_POST['item_name'] = $value;
79
        }
80
81
		if ( $value != '' ) {
82
			self::validate_url_field( $errors, $posted_field, $value, $args );
83
			self::validate_email_field( $errors, $posted_field, $value, $args );
84
			self::validate_number_field( $errors, $posted_field, $value, $args );
85
			self::validate_phone_field( $errors, $posted_field, $value, $args );
86
		}
87
88
        FrmEntriesHelper::set_posted_value($posted_field, $value, $args);
89
90
        self::validate_recaptcha($errors, $posted_field, $args);
91
92
		$errors = apply_filters( 'frm_validate_' . $posted_field->type . '_field_entry', $errors, $posted_field, $value, $args );
93
		$errors = apply_filters( 'frm_validate_field_entry', $errors, $posted_field, $value, $args );
94
    }
95
96
	private static function maybe_clear_value_for_default_blank_setting( $field, &$value ) {
97
		if ( FrmField::is_option_true_in_object( $field, 'default_blank' ) && $value == $field->default_value ) {
98
			$value = '';
99
		}
100
	}
101
102
	public static function validate_url_field( &$errors, $field, &$value, $args ) {
103
		if ( $value == '' || ! in_array( $field->type, array( 'website', 'url', 'image' ) ) ) {
104
            return;
105
        }
106
107
        if ( trim($value) == 'http://' ) {
108
            $value = '';
109
        } else {
110
            $value = esc_url_raw( $value );
111
			$value = preg_match( '/^(https?|ftps?|mailto|news|feed|telnet):/is', $value ) ? $value : 'http://' . $value;
112
        }
113
114
        // validate the url format
115 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...
116
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
117
		}
118
    }
119
120
	public static function validate_email_field( &$errors, $field, $value, $args ) {
121
        if ( $value == '' || $field->type != 'email' ) {
122
            return;
123
        }
124
125
        //validate the email format
126 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...
127
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
128
        }
129
    }
130
131
	public static function validate_number_field( &$errors, $field, $value, $args ) {
132
		//validate the number format
133
		if ( $field->type != 'number' ) {
134
			return;
135
		}
136
137
		if ( ! is_numeric( $value) ) {
138
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
139
		}
140
141
		// validate number settings
142
		if ( $value != '' ) {
143
			$frm_settings = FrmAppHelper::get_settings();
144
			// only check if options are available in settings
145
			if ( $frm_settings->use_html && isset( $field->field_options['minnum'] ) && isset( $field->field_options['maxnum'] ) ) {
146
				//minnum maxnum
147
				if ( (float) $value < $field->field_options['minnum'] ) {
148
					$errors[ 'field' . $args['id'] ] = __( 'Please select a higher number', 'formidable' );
149
				} else if ( (float) $value > $field->field_options['maxnum'] ) {
150
					$errors[ 'field' . $args['id'] ] = __( 'Please select a lower number', 'formidable' );
151
				}
152
			}
153
		}
154
	}
155
156
	public static function validate_phone_field( &$errors, $field, $value, $args ) {
157
		if ( $field->type == 'phone' || ( $field->type == 'text' && FrmField::is_option_true_in_object( $field, 'format' ) ) ) {
158
159
			$pattern = self::phone_format( $field );
160
161 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...
162
				$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
163
			}
164
		}
165
	}
166
167
	public static function phone_format( $field ) {
168
		$default_format = '^((\+\d{1,3}(-|.| )?\(?\d\)?(-| |.)?\d{1,5})|(\(?\d{2,6}\)?))(-|.| )?(\d{3,4})(-|.| )?(\d{4})(( x| ext)\d{1,5}){0,1}$';
169
		if ( FrmField::is_option_empty( $field, 'format' ) ) {
170
			$pattern = $default_format;
171
		} else {
172
			$pattern = FrmField::get_option( $field, 'format' );
173
		}
174
175
		$pattern = apply_filters( 'frm_phone_pattern', $pattern, $field );
176
177
		// Create a regexp if format is not already a regexp
178
		if ( strpos( $pattern, '^' ) !== 0 ) {
179
			$pattern = self::create_regular_expression_from_format( $pattern );
180
		}
181
182
		$pattern = '/' . $pattern . '/';
183
		return $pattern;
184
	}
185
186
	/**
187
	 * Create a regular expression from a phone number format
188
	 *
189
	 * @since 2.02.02
190
	 * @param string $pattern
191
	 * @return string
192
	 */
193
	private static function create_regular_expression_from_format( $pattern ) {
194
		$pattern = preg_quote( $pattern );
195
196
		// Firefox doesn't like escaped dashes or colons
197
		$pattern = str_replace( array( '\-', '\:' ), array( '-', ':' ), $pattern );
198
199
		// Switch generic values out for their regular expression
200
		$pattern = preg_replace( '/\d/', '\d', $pattern );
201
		$pattern = str_replace( 'a', '[a-z]', $pattern );
202
		$pattern = str_replace( 'A', '[A-Z]', $pattern );
203
		$pattern = str_replace( '*', 'w', $pattern );
204
		$pattern = str_replace( '/', '\/', $pattern );
205
206
		if ( strpos( $pattern, '\?' ) !== false ) {
207
			$parts = explode( '\?', $pattern );
208
			$pattern = '';
209
			foreach ( $parts as $part ) {
210
				if ( empty( $pattern ) ) {
211
					$pattern .= $part;
212
				} else {
213
					$pattern .= '(' . $part . ')?';
214
				}
215
			}
216
		}
217
		$pattern = '^' . $pattern . '$';
218
219
		return $pattern;
220
	}
221
222
	public static function validate_recaptcha( &$errors, $field, $args ) {
223
        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...
224
            return;
225
        }
226
227
		$frm_settings = FrmAppHelper::get_settings();
228
		if ( empty( $frm_settings->pubkey ) ) {
229
			// don't require the captcha if it shouldn't be shown
230
			return;
231
		}
232
233
        if ( ! isset($_POST['g-recaptcha-response']) ) {
234
            // If captcha is missing, check if it was already verified
235
			if ( ! isset( $_POST['recaptcha_checked'] ) || ! wp_verify_nonce( $_POST['recaptcha_checked'], 'frm_ajax' ) ) {
236
                // There was no captcha submitted
237
				$errors[ 'field' . $args['id'] ] = __( 'The captcha is missing from this form', 'formidable' );
238
            }
239
            return;
240
        }
241
242
        $arg_array = array(
243
            'body'      => array(
244
				'secret'   => $frm_settings->privkey,
245
				'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...
246
				'remoteip' => FrmAppHelper::get_ip_address(),
247
			),
248
		);
249
        $resp = wp_remote_post( 'https://www.google.com/recaptcha/api/siteverify', $arg_array );
250
        $response = json_decode(wp_remote_retrieve_body( $resp ), true);
251
252
        if ( isset( $response['success'] ) && ! $response['success'] ) {
253
            // What happens when the CAPTCHA was entered incorrectly
254
			$errors[ 'field' . $args['id'] ] = ( ! isset( $field->field_options['invalid'] ) || $field->field_options['invalid'] == '' ) ? $frm_settings->re_msg : $field->field_options['invalid'];
255
        } else if ( is_wp_error( $resp ) ) {
256
			$error_string = $resp->get_error_message();
257
			$errors[ 'field' . $args['id'] ] = __( 'There was a problem verifying your recaptcha', 'formidable' );
258
			$errors[ 'field' . $args['id'] ] .= ' ' . $error_string;
259
        }
260
    }
261
262
    /**
263
     * check for spam
264
     * @param boolean $exclude
265
     * @param array $values
266
     * @param array $errors by reference
267
     */
268
    public static function spam_check( $exclude, $values, &$errors ) {
269
        if ( ! empty( $exclude ) || ! isset( $values['item_meta'] ) || empty( $values['item_meta'] ) || ! empty( $errors ) ) {
270
            // only check spam if there are no other errors
271
            return;
272
        }
273
274
    	if ( self::blacklist_check( $values ) ) {
275
            $errors['spam'] = __( 'Your entry appears to be blacklist spam!', 'formidable' );
276
    	}
277
278
        if ( self::is_akismet_spam( $values ) ) {
279
			if ( self::is_akismet_enabled_for_user( $values['form_id'] ) ) {
280
				$errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
281
			}
282
	    }
283
    }
284
285
	private static function is_akismet_spam( $values ) {
286
		global $wpcom_api_key;
287
		return ( is_callable('Akismet::http_post') && ( get_option('wordpress_api_key') || $wpcom_api_key ) && self::akismet( $values ) );
288
	}
289
290
	private static function is_akismet_enabled_for_user( $form_id ) {
291
		$form = FrmForm::getOne( $form_id );
292
		return ( isset( $form->options['akismet'] ) && ! empty( $form->options['akismet'] ) && ( $form->options['akismet'] != 'logged' || ! is_user_logged_in() ) );
293
	}
294
295
    public static function blacklist_check( $values ) {
296
        if ( ! apply_filters('frm_check_blacklist', true, $values) ) {
297
            return false;
298
        }
299
300
    	$mod_keys = trim( get_option( 'blacklist_keys' ) );
301
    	if ( empty( $mod_keys ) ) {
302
    		return false;
303
    	}
304
305
		$content = FrmEntriesHelper::entry_array_to_string( $values );
306
		if ( empty( $content ) ) {
307
			return false;
308
		}
309
310
		$ip = FrmAppHelper::get_ip_address();
311
		$user_agent = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
312
		$user_info = self::get_spam_check_user_info( $values );
313
314
		return wp_blacklist_check( $user_info['comment_author'], $user_info['comment_author_email'], $user_info['comment_author_url'], $content, $ip, $user_agent );
315
    }
316
317
	/**
318
	 * Check entries for Akismet spam
319
	 *
320
	 * @return boolean true if is spam
321
	 */
322
	public static function akismet( $values ) {
323
		$content = FrmEntriesHelper::entry_array_to_string( $values );
324
		if ( empty( $content ) ) {
325
			return false;
326
		}
327
328
		$datas = array( 'comment_type' => 'formidable', 'comment_content' => $content );
329
		self::parse_akismet_array( $datas, $values );
330
331
		$query_string = _http_build_query( $datas, '', '&' );
332
		$response = Akismet::http_post( $query_string, 'comment-check' );
333
334
		return ( is_array( $response ) && $response[1] == 'true' );
335
	}
336
337
	/**
338
	 * @since 2.0
339
	 * @param string $content
0 ignored issues
show
Bug introduced by
There is no parameter named $content. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
340
	 */
341
	private  static function parse_akismet_array( &$datas, $values ) {
342
		self::add_site_info_to_akismet( $datas );
343
		self::add_user_info_to_akismet( $datas, $values );
344
		self::add_server_values_to_akismet( $datas );
345
	}
346
347
	private static function add_site_info_to_akismet( &$datas ) {
348
		$datas['blog'] = FrmAppHelper::site_url();
349
		$datas['user_ip'] = preg_replace( '/[^0-9., ]/', '', FrmAppHelper::get_ip_address() );
350
		$datas['user_agent'] = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
351
		$datas['referrer'] = isset( $_SERVER['HTTP_REFERER'] ) ? FrmAppHelper::get_server_value( 'HTTP_REFERER' ) : false;
352
		$datas['blog_lang'] = get_locale();
353
		$datas['blog_charset'] = get_option('blog_charset');
354
355
		if ( akismet_test_mode() ) {
356
			$datas['is_test'] = 'true';
357
		}
358
	}
359
360
	private static function add_user_info_to_akismet( &$datas, $values ) {
361
		$user_info = self::get_spam_check_user_info( $values );
362
		$datas = $datas + $user_info;
363
364
		if ( isset( $user_info['user_ID'] ) ) {
365
			$datas['user_role'] = Akismet::get_user_roles( $user_info['user_ID'] );
366
		}
367
	}
368
369
	private static function get_spam_check_user_info( $values ) {
370
		$datas = array();
371
372
		if ( is_user_logged_in() ) {
373
			$user = wp_get_current_user();
374
			$datas['user_ID'] = $datas['user_id'] = $user->ID;
375
			$datas['comment_author'] = $user->display_name;
376
			$datas['comment_author_email'] = $user->user_email;
377
			$datas['comment_author_url'] = $user->user_url;
378
		} else {
379
			$datas['comment_author'] = '';
380
			$datas['comment_author_email'] = '';
381
			$datas['comment_author_url'] = '';
382
383
			$values = array_filter( $values );
384
			foreach ( $values as $value ) {
385
				if ( ! is_array( $value ) ) {
386
					if ( $datas['comment_author_email'] == '' && strpos( $value, '@' ) && is_email( $value ) ) {
387
						$datas['comment_author_email'] = $value;
388
					} elseif ( $datas['comment_author_url'] == '' && strpos( $value, 'http' ) === 0 ) {
389
						$datas['comment_author_url'] = $value;
390
					} elseif ( $datas['comment_author'] == '' && ! is_numeric( $value ) && strlen( $value ) < 200 ) {
391
						$datas['comment_author'] = $value;
392
					}
393
				}
394
			}
395
		}
396
397
		return $datas;
398
	}
399
400
	private static function add_server_values_to_akismet( &$datas ) {
401
		foreach ( $_SERVER as $key => $value ) {
402
			$include_value = is_string( $value ) && ! preg_match( "/^HTTP_COOKIE/", $key ) && preg_match( "/^(HTTP_|REMOTE_ADDR|REQUEST_URI|DOCUMENT_URI)/", $key );
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal /^HTTP_COOKIE/ does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
Coding Style Comprehensibility introduced by
The string literal /^(HTTP_|REMOTE_ADDR|REQUEST_URI|DOCUMENT_URI)/ does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
403
404
			// Send any potentially useful $_SERVER vars, but avoid sending junk we don't need.
405
			if ( $include_value ) {
406
				$datas[ $key ] = $value;
407
			}
408
			unset( $key, $value );
409
		}
410
	}
411
}
412