Completed
Push — master ( 762520...3540af )
by Stephanie
04:40
created

FrmEntryValidate::set_item_key()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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