Completed
Push — master ( 609861...30b340 )
by Stephanie
02:35
created

FrmEntryValidate::validate_phone_field()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 3
Ratio 30 %

Importance

Changes 0
Metric Value
cc 5
eloc 5
nc 3
nop 4
dl 3
loc 10
rs 8.8571
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
		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 ( $posted_field->required == '1' && ! is_array( $value ) && trim( $value ) == '' ) {
87
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $posted_field, 'blank' );
88
        } else if ( $posted_field->type == 'text' && ! isset( $_POST['item_name'] ) ) {
89
            $_POST['item_name'] = $value;
90
        }
91
92
		if ( $value != '' ) {
93
			self::validate_url_field( $errors, $posted_field, $value, $args );
94
			self::validate_email_field( $errors, $posted_field, $value, $args );
95
			self::validate_number_field( $errors, $posted_field, $value, $args );
96
			self::validate_phone_field( $errors, $posted_field, $value, $args );
97
		}
98
99
        FrmEntriesHelper::set_posted_value($posted_field, $value, $args);
100
101
        self::validate_recaptcha($errors, $posted_field, $args);
102
103
		$errors = apply_filters( 'frm_validate_' . $posted_field->type . '_field_entry', $errors, $posted_field, $value, $args );
104
		$errors = apply_filters( 'frm_validate_field_entry', $errors, $posted_field, $value, $args );
105
    }
106
107
	private static function maybe_clear_value_for_default_blank_setting( $field, &$value ) {
108
		$is_default = ( FrmField::is_option_true_in_object( $field, 'default_blank' ) && $value == $field->default_value );
109
		$is_label = false;
110
111
		if ( ! $is_default ) {
112
			$position = FrmField::get_option( $field, 'label' );
113
			if ( empty( $position ) ) {
114
				$position = FrmStylesController::get_style_val( 'position', $field->form_id );
115
			}
116
117
			$is_label = ( $position == 'inside' && FrmFieldsHelper::is_placeholder_field_type( $field->type ) && $value == $field->name );
118
		}
119
120
		if ( $is_label || $is_default ) {
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 ( strpos( $value, ',' ) ) {
161
			$value = str_replace( ',', '', $value );
162
		}
163
164
		if ( ! is_numeric( $value) ) {
165
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
166
		}
167
168
		// validate number settings
169
		if ( $value != '' ) {
170
			$frm_settings = FrmAppHelper::get_settings();
171
			// only check if options are available in settings
172
			if ( $frm_settings->use_html && isset( $field->field_options['minnum'] ) && isset( $field->field_options['maxnum'] ) ) {
173
				//minnum maxnum
174
				if ( (float) $value < $field->field_options['minnum'] ) {
175
					$errors[ 'field' . $args['id'] ] = __( 'Please select a higher number', 'formidable' );
176
				} else if ( (float) $value > $field->field_options['maxnum'] ) {
177
					$errors[ 'field' . $args['id'] ] = __( 'Please select a lower number', 'formidable' );
178
				}
179
			}
180
		}
181
	}
182
183
	public static function validate_phone_field( &$errors, $field, $value, $args ) {
184
		if ( $field->type == 'phone' || ( $field->type == 'text' && FrmField::is_option_true_in_object( $field, 'format' ) ) ) {
185
186
			$pattern = self::phone_format( $field );
187
188 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...
189
				$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
190
			}
191
		}
192
	}
193
194
	public static function phone_format( $field ) {
195
		$default_format = '^((\+\d{1,3}(-|.| )?\(?\d\)?(-| |.)?\d{1,5})|(\(?\d{2,6}\)?))(-|.| )?(\d{3,4})(-|.| )?(\d{4})(( x| ext)\d{1,5}){0,1}$';
196
		if ( FrmField::is_option_empty( $field, 'format' ) ) {
197
			$pattern = $default_format;
198
		} else {
199
			$pattern = FrmField::get_option( $field, 'format' );
200
		}
201
202
		$pattern = apply_filters( 'frm_phone_pattern', $pattern, $field );
203
204
		// Create a regexp if format is not already a regexp
205
		if ( strpos( $pattern, '^' ) !== 0 ) {
206
			$pattern = self::create_regular_expression_from_format( $pattern );
207
		}
208
209
		$pattern = '/' . $pattern . '/';
210
		return $pattern;
211
	}
212
213
	/**
214
	 * Create a regular expression from a phone number format
215
	 *
216
	 * @since 2.02.02
217
	 * @param string $pattern
218
	 * @return string
219
	 */
220
	private static function create_regular_expression_from_format( $pattern ) {
221
		$pattern = preg_quote( $pattern );
222
223
		// Firefox doesn't like escaped dashes or colons
224
		$pattern = str_replace( array( '\-', '\:' ), array( '-', ':' ), $pattern );
225
226
		// Switch generic values out for their regular expression
227
		$pattern = preg_replace( '/\d/', '\d', $pattern );
228
		$pattern = str_replace( 'a', '[a-z]', $pattern );
229
		$pattern = str_replace( 'A', '[A-Z]', $pattern );
230
		$pattern = str_replace( '*', 'w', $pattern );
231
		$pattern = str_replace( '/', '\/', $pattern );
232
233
		if ( strpos( $pattern, '\?' ) !== false ) {
234
			$parts = explode( '\?', $pattern );
235
			$pattern = '';
236
			foreach ( $parts as $part ) {
237
				if ( empty( $pattern ) ) {
238
					$pattern .= $part;
239
				} else {
240
					$pattern .= '(' . $part . ')?';
241
				}
242
			}
243
		}
244
		$pattern = '^' . $pattern . '$';
245
246
		return $pattern;
247
	}
248
249
	public static function validate_recaptcha( &$errors, $field, $args ) {
250
        if ( $field->type != 'captcha' || FrmAppHelper::is_admin() || apply_filters( 'frm_is_field_hidden', false, $field, stripslashes_deep( $_POST ) ) ) {
251
            return;
252
        }
253
254
		$frm_settings = FrmAppHelper::get_settings();
255
		if ( empty( $frm_settings->pubkey ) ) {
256
			// don't require the captcha if it shouldn't be shown
257
			return;
258
		}
259
260
        if ( ! isset($_POST['g-recaptcha-response']) ) {
261
            // If captcha is missing, check if it was already verified
262
			if ( ! isset( $_POST['recaptcha_checked'] ) || ! wp_verify_nonce( $_POST['recaptcha_checked'], 'frm_ajax' ) ) {
263
                // There was no captcha submitted
264
				$errors[ 'field' . $args['id'] ] = __( 'The captcha is missing from this form', 'formidable' );
265
            }
266
            return;
267
        }
268
269
        $arg_array = array(
270
            'body'      => array(
271
				'secret'   => $frm_settings->privkey,
272
				'response' => $_POST['g-recaptcha-response'],
273
				'remoteip' => FrmAppHelper::get_ip_address(),
274
			),
275
		);
276
        $resp = wp_remote_post( 'https://www.google.com/recaptcha/api/siteverify', $arg_array );
277
        $response = json_decode(wp_remote_retrieve_body( $resp ), true);
278
279
        if ( isset( $response['success'] ) && ! $response['success'] ) {
280
            // What happens when the CAPTCHA was entered incorrectly
281
			$errors[ 'field' . $args['id'] ] = ( ! isset( $field->field_options['invalid'] ) || $field->field_options['invalid'] == '' ) ? $frm_settings->re_msg : $field->field_options['invalid'];
282
        } else if ( is_wp_error( $resp ) ) {
283
			$error_string = $resp->get_error_message();
284
			$errors[ 'field' . $args['id'] ] = __( 'There was a problem verifying your recaptcha', 'formidable' );
285
			$errors[ 'field' . $args['id'] ] .= ' ' . $error_string;
286
        }
287
    }
288
289
    /**
290
     * check for spam
291
     * @param boolean $exclude
292
     * @param array $values
293
     * @param array $errors by reference
294
     */
295
    public static function spam_check( $exclude, $values, &$errors ) {
296
        if ( ! empty( $exclude ) || ! isset( $values['item_meta'] ) || empty( $values['item_meta'] ) || ! empty( $errors ) ) {
297
            // only check spam if there are no other errors
298
            return;
299
        }
300
301
		if ( self::is_honeypot_spam() || self::is_spam_bot() ) {
302
			$errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
303
		}
304
305
    	if ( self::blacklist_check( $values ) ) {
306
            $errors['spam'] = __( 'Your entry appears to be blacklist spam!', 'formidable' );
307
    	}
308
309
        if ( self::is_akismet_spam( $values ) ) {
310
			if ( self::is_akismet_enabled_for_user( $values['form_id'] ) ) {
311
				$errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
312
			}
313
	    }
314
    }
315
316
	private static function is_honeypot_spam() {
317
		$honeypot_value = FrmAppHelper::get_param( 'frm_verify', '', 'get', 'sanitize_text_field' );
318
		return ( $honeypot_value !== '' );
319
	}
320
321
	private static function is_spam_bot() {
322
		$ip = FrmAppHelper::get_ip_address();
323
		return empty( $ip );
324
	}
325
326
	private static function is_akismet_spam( $values ) {
327
		global $wpcom_api_key;
328
		return ( is_callable('Akismet::http_post') && ( get_option('wordpress_api_key') || $wpcom_api_key ) && self::akismet( $values ) );
329
	}
330
331
	private static function is_akismet_enabled_for_user( $form_id ) {
332
		$form = FrmForm::getOne( $form_id );
333
		return ( isset( $form->options['akismet'] ) && ! empty( $form->options['akismet'] ) && ( $form->options['akismet'] != 'logged' || ! is_user_logged_in() ) );
334
	}
335
336
    public static function blacklist_check( $values ) {
337
        if ( ! apply_filters('frm_check_blacklist', true, $values) ) {
338
            return false;
339
        }
340
341
    	$mod_keys = trim( get_option( 'blacklist_keys' ) );
342
    	if ( empty( $mod_keys ) ) {
343
    		return false;
344
    	}
345
346
		$content = FrmEntriesHelper::entry_array_to_string( $values );
347
		if ( empty( $content ) ) {
348
			return false;
349
		}
350
351
		$ip = FrmAppHelper::get_ip_address();
352
		$user_agent = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
353
		$user_info = self::get_spam_check_user_info( $values );
354
355
		return wp_blacklist_check( $user_info['comment_author'], $user_info['comment_author_email'], $user_info['comment_author_url'], $content, $ip, $user_agent );
356
    }
357
358
	/**
359
	 * Check entries for Akismet spam
360
	 *
361
	 * @return boolean true if is spam
362
	 */
363
	public static function akismet( $values ) {
364
		$content = FrmEntriesHelper::entry_array_to_string( $values );
365
		if ( empty( $content ) ) {
366
			return false;
367
		}
368
369
		$datas = array( 'comment_type' => 'formidable', 'comment_content' => $content );
370
		self::parse_akismet_array( $datas, $values );
371
372
		$query_string = _http_build_query( $datas, '', '&' );
373
		$response = Akismet::http_post( $query_string, 'comment-check' );
374
375
		return ( is_array( $response ) && $response[1] == 'true' );
376
	}
377
378
	/**
379
	 * @since 2.0
380
	 */
381
	private  static function parse_akismet_array( &$datas, $values ) {
382
		self::add_site_info_to_akismet( $datas );
383
		self::add_user_info_to_akismet( $datas, $values );
384
		self::add_server_values_to_akismet( $datas );
385
	}
386
387
	private static function add_site_info_to_akismet( &$datas ) {
388
		$datas['blog'] = FrmAppHelper::site_url();
389
		$datas['user_ip'] = preg_replace( '/[^0-9., ]/', '', FrmAppHelper::get_ip_address() );
390
		$datas['user_agent'] = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
391
		$datas['referrer'] = isset( $_SERVER['HTTP_REFERER'] ) ? FrmAppHelper::get_server_value( 'HTTP_REFERER' ) : false;
392
		$datas['blog_lang'] = get_locale();
393
		$datas['blog_charset'] = get_option('blog_charset');
394
395
		if ( akismet_test_mode() ) {
396
			$datas['is_test'] = 'true';
397
		}
398
	}
399
400
	private static function add_user_info_to_akismet( &$datas, $values ) {
401
		$user_info = self::get_spam_check_user_info( $values );
402
		$datas = $datas + $user_info;
403
404
		if ( isset( $user_info['user_ID'] ) ) {
405
			$datas['user_role'] = Akismet::get_user_roles( $user_info['user_ID'] );
406
		}
407
	}
408
409
	private static function get_spam_check_user_info( $values ) {
410
		$datas = array();
411
412
		if ( is_user_logged_in() ) {
413
			$user = wp_get_current_user();
414
			$datas['user_ID'] = $user->ID;
415
			$datas['user_id'] = $user->ID;
416
			$datas['comment_author'] = $user->display_name;
417
			$datas['comment_author_email'] = $user->user_email;
418
			$datas['comment_author_url'] = $user->user_url;
419
		} else {
420
			$datas['comment_author'] = '';
421
			$datas['comment_author_email'] = '';
422
			$datas['comment_author_url'] = '';
423
424
			$values = array_filter( $values );
425
			foreach ( $values as $value ) {
426
				if ( ! is_array( $value ) ) {
427
					if ( $datas['comment_author_email'] == '' && strpos( $value, '@' ) && is_email( $value ) ) {
428
						$datas['comment_author_email'] = $value;
429
					} elseif ( $datas['comment_author_url'] == '' && strpos( $value, 'http' ) === 0 ) {
430
						$datas['comment_author_url'] = $value;
431
					} elseif ( $datas['comment_author'] == '' && ! is_numeric( $value ) && strlen( $value ) < 200 ) {
432
						$datas['comment_author'] = $value;
433
					}
434
				}
435
			}
436
		}
437
438
		return $datas;
439
	}
440
441
	private static function add_server_values_to_akismet( &$datas ) {
442
		foreach ( $_SERVER as $key => $value ) {
443
			$include_value = is_string( $value ) && ! preg_match( '/^HTTP_COOKIE/', $key ) && preg_match( '/^(HTTP_|REMOTE_ADDR|REQUEST_URI|DOCUMENT_URI)/', $key );
444
445
			// Send any potentially useful $_SERVER vars, but avoid sending junk we don't need.
446
			if ( $include_value ) {
447
				$datas[ $key ] = $value;
448
			}
449
			unset( $key, $value );
450
		}
451
	}
452
}
453